@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.js ADDED
@@ -0,0 +1,1453 @@
1
+ // src/palette.ts
2
+ var ember = {
3
+ 200: "#ffe9c9",
4
+ 300: "#ffc08c",
5
+ 400: "#f7a35c",
6
+ 500: "#f97316",
7
+ 600: "#e07b2e",
8
+ 700: "#b45309"
9
+ };
10
+ var ink = {
11
+ 25: "#F1F8FA",
12
+ 50: "#EAF2F3",
13
+ 100: "#E2E9EB",
14
+ 200: "#D6DEE0",
15
+ 300: "#C9D1D3",
16
+ 400: "#B7C0C2",
17
+ 500: "#9DA7A9",
18
+ 600: "#7E888B",
19
+ 700: "#5C6769",
20
+ 800: "#3D494B",
21
+ 850: "#2B3639",
22
+ 900: "#1A2628",
23
+ 950: "#0E191C",
24
+ 1e3: "#030E10"
25
+ };
26
+ var wave = {
27
+ 100: "#C7F1F3",
28
+ 200: "#B3E2E4",
29
+ 300: "#9CCFD2",
30
+ 400: "#7FB8BB",
31
+ 500: "#5F9FA2",
32
+ 600: "#368286",
33
+ 700: "#1C6265",
34
+ 800: "#024347",
35
+ 900: "#002A2C",
36
+ 950: "#001C1E",
37
+ 980: "#001012"
38
+ };
39
+ var verdant = {
40
+ 100: "#D3F1DA",
41
+ 200: "#C3E3CA",
42
+ 300: "#AFD2B7",
43
+ 400: "#96BC9F",
44
+ 500: "#76A281",
45
+ 600: "#50825E",
46
+ 700: "#366042",
47
+ 800: "#1E4128",
48
+ 900: "#0B2815"
49
+ };
50
+ var sandstone = {
51
+ 100: "#F5E8D0",
52
+ 200: "#E7D9BF",
53
+ 300: "#D6C7AC",
54
+ 400: "#BFAF92",
55
+ 500: "#A19070"
56
+ };
57
+ var graphite = {
58
+ 25: "#FAFAFA",
59
+ 50: "#F2F2F2",
60
+ 100: "#E9E9E9",
61
+ 200: "#DEDEDE",
62
+ 300: "#D1D1D1",
63
+ 400: "#BEBEBE",
64
+ 500: "#A4A4A4",
65
+ 600: "#868686",
66
+ 700: "#656565",
67
+ 800: "#464646",
68
+ 900: "#232323",
69
+ 1e3: "#0B0B0B"
70
+ };
71
+
72
+ // src/recipe.ts
73
+ var THEME_BASES = ["base", "muted", "relief", "street", "outdoor"];
74
+ var MODES = ["light", "dark"];
75
+ function makeFlavor(r) {
76
+ return {
77
+ background: r.background,
78
+ earth: r.earth,
79
+ water: r.water,
80
+ park_a: r.parkA,
81
+ park_b: r.parkB,
82
+ wood_a: r.woodA,
83
+ wood_b: r.woodB,
84
+ scrub_a: r.scrubA,
85
+ scrub_b: r.scrubB,
86
+ sand: r.sand,
87
+ beach: r.beach,
88
+ glacier: r.glacier,
89
+ hospital: r.institutional,
90
+ industrial: r.institutional,
91
+ school: r.institutional,
92
+ zoo: r.institutional,
93
+ military: r.institutional,
94
+ pedestrian: r.pedestrian,
95
+ aerodrome: r.aerodrome,
96
+ runway: r.runway,
97
+ pier: r.pier,
98
+ buildings: r.buildings,
99
+ // Surface roads.
100
+ other: r.roads.other,
101
+ minor_service: r.roads.service,
102
+ minor_a: r.roads.minor,
103
+ minor_b: r.roads.minor,
104
+ link: r.roads.link,
105
+ major: r.roads.major,
106
+ highway: r.roads.highway,
107
+ minor_service_casing: r.roads.serviceCasing,
108
+ minor_casing: r.roads.minorCasing,
109
+ link_casing: r.roads.linkCasing,
110
+ // early/late are the same colour: they exist so Protomaps can fade casings in at
111
+ // different zooms, not to differentiate hue.
112
+ major_casing_early: r.roads.majorCasing,
113
+ major_casing_late: r.roads.majorCasing,
114
+ highway_casing_early: r.roads.highwayCasing,
115
+ highway_casing_late: r.roads.highwayCasing,
116
+ // Tunnels: recessive, one flat fill so the network reads as "beneath".
117
+ tunnel_other: r.tunnelFill,
118
+ tunnel_minor: r.tunnelFill,
119
+ tunnel_link: r.tunnelFill,
120
+ tunnel_major: r.tunnelFill,
121
+ tunnel_highway: r.tunnelFill,
122
+ tunnel_other_casing: r.tunnelCasing,
123
+ tunnel_minor_casing: r.tunnelCasing,
124
+ tunnel_link_casing: r.tunnelCasing,
125
+ tunnel_major_casing: r.tunnelCasing,
126
+ tunnel_highway_casing: r.tunnelCasing,
127
+ // Bridges: same fills as the surface road so the ribbon stays continuous, with a
128
+ // stronger casing that lifts it off whatever it crosses.
129
+ bridges_other: r.roads.other,
130
+ bridges_minor: r.roads.minor,
131
+ bridges_link: r.roads.link,
132
+ bridges_major: r.roads.major,
133
+ bridges_highway: r.roads.highway,
134
+ bridges_other_casing: r.bridgeCasing,
135
+ bridges_minor_casing: r.bridgeCasing,
136
+ bridges_link_casing: r.bridgeCasing,
137
+ bridges_major_casing: r.bridgeCasing,
138
+ bridges_highway_casing: r.bridgeCasing,
139
+ railway: r.railway,
140
+ boundaries: r.boundaries,
141
+ roads_label_minor: r.labels.roadMinor,
142
+ roads_label_minor_halo: r.labels.roadMinorHalo,
143
+ roads_label_major: r.labels.roadMajor,
144
+ roads_label_major_halo: r.labels.roadMajorHalo,
145
+ ocean_label: r.labels.ocean,
146
+ subplace_label: r.labels.subplace,
147
+ subplace_label_halo: r.labels.subplaceHalo,
148
+ city_label: r.labels.city,
149
+ city_label_halo: r.labels.cityHalo,
150
+ state_label: r.labels.state,
151
+ state_label_halo: r.labels.stateHalo,
152
+ country_label: r.labels.country,
153
+ address_label: r.labels.address,
154
+ address_label_halo: r.labels.addressHalo,
155
+ pois: r.pois,
156
+ landcover: r.landcover
157
+ };
158
+ }
159
+
160
+ // src/recipes.ts
161
+ var STOCK_RAMPS = { ink, wave, verdant, sandstone, graphite, ember };
162
+ function buildRecipes(r) {
163
+ const BASE_LIGHT = {
164
+ background: r.ink[200],
165
+ earth: r.ink[100],
166
+ water: r.wave[300],
167
+ parkA: r.verdant[200],
168
+ parkB: r.verdant[300],
169
+ woodA: r.verdant[200],
170
+ woodB: r.verdant[300],
171
+ scrubA: r.verdant[200],
172
+ scrubB: r.verdant[300],
173
+ sand: r.sandstone[200],
174
+ beach: r.sandstone[200],
175
+ glacier: r.ink[25],
176
+ institutional: r.ink[50],
177
+ pedestrian: r.ink[50],
178
+ aerodrome: r.ink[50],
179
+ runway: r.ink[200],
180
+ pier: r.ink[300],
181
+ buildings: r.ink[300],
182
+ railway: r.ink[500],
183
+ boundaries: r.ink[600],
184
+ roads: {
185
+ other: r.ink[50],
186
+ otherCasing: r.ink[300],
187
+ minor: r.ink[25],
188
+ minorCasing: r.ink[300],
189
+ service: r.ink[50],
190
+ serviceCasing: r.ink[300],
191
+ major: "#FFFFFF",
192
+ majorCasing: r.ink[400],
193
+ highway: "#FFFFFF",
194
+ highwayCasing: r.ink[500],
195
+ link: "#FFFFFF",
196
+ linkCasing: r.ink[400]
197
+ },
198
+ tunnelFill: r.ink[200],
199
+ tunnelCasing: r.ink[300],
200
+ bridgeCasing: r.ink[500],
201
+ labels: {
202
+ city: r.ink[1e3],
203
+ cityHalo: r.ink[25],
204
+ state: r.ink[700],
205
+ stateHalo: r.ink[25],
206
+ country: r.ink[700],
207
+ subplace: r.ink[700],
208
+ subplaceHalo: r.ink[25],
209
+ address: r.ink[700],
210
+ addressHalo: r.ink[25],
211
+ roadMinor: r.ink[700],
212
+ roadMinorHalo: r.ink[25],
213
+ roadMajor: r.ink[800],
214
+ roadMajorHalo: r.ink[25],
215
+ ocean: r.wave[800]
216
+ },
217
+ pois: {
218
+ blue: "#2D6E8F",
219
+ green: r.verdant[700],
220
+ lapis: "#3F5C93",
221
+ pink: "#8F4A6B",
222
+ red: "#9B4340",
223
+ slategray: r.ink[700],
224
+ tangerine: "#8F5322",
225
+ turquoise: r.wave[700]
226
+ },
227
+ landcover: {
228
+ grassland: r.verdant[200],
229
+ barren: r.sandstone[200],
230
+ urban_area: r.ink[50],
231
+ farmland: r.verdant[100],
232
+ glacier: r.ink[25],
233
+ scrub: r.verdant[100],
234
+ forest: r.verdant[300]
235
+ }
236
+ };
237
+ const BASE_DARK = {
238
+ background: r.ink[950],
239
+ earth: r.ink[900],
240
+ water: r.wave[980],
241
+ parkA: r.verdant[800],
242
+ parkB: r.verdant[700],
243
+ woodA: r.verdant[800],
244
+ woodB: r.verdant[700],
245
+ scrubA: r.verdant[800],
246
+ scrubB: r.verdant[700],
247
+ sand: r.sandstone[500],
248
+ beach: r.sandstone[500],
249
+ glacier: r.ink[700],
250
+ institutional: r.ink[950],
251
+ pedestrian: r.ink[950],
252
+ aerodrome: r.ink[950],
253
+ runway: r.ink[850],
254
+ pier: r.ink[850],
255
+ buildings: r.ink[850],
256
+ railway: r.ink[700],
257
+ boundaries: r.ink[600],
258
+ roads: {
259
+ other: r.ink[850],
260
+ otherCasing: r.ink[1e3],
261
+ minor: r.ink[800],
262
+ minorCasing: r.ink[1e3],
263
+ service: r.ink[850],
264
+ serviceCasing: r.ink[1e3],
265
+ major: r.ink[700],
266
+ majorCasing: r.ink[1e3],
267
+ highway: r.ink[600],
268
+ highwayCasing: r.ink[1e3],
269
+ link: r.ink[700],
270
+ linkCasing: r.ink[1e3]
271
+ },
272
+ tunnelFill: r.ink[900],
273
+ tunnelCasing: r.ink[1e3],
274
+ bridgeCasing: r.ink[1e3],
275
+ labels: {
276
+ city: r.ink[100],
277
+ cityHalo: r.ink[1e3],
278
+ state: r.ink[500],
279
+ stateHalo: r.ink[1e3],
280
+ country: r.ink[500],
281
+ subplace: r.ink[500],
282
+ subplaceHalo: r.ink[1e3],
283
+ address: r.ink[500],
284
+ addressHalo: r.ink[1e3],
285
+ roadMinor: r.ink[500],
286
+ roadMinorHalo: r.ink[1e3],
287
+ roadMajor: r.ink[400],
288
+ roadMajorHalo: r.ink[1e3],
289
+ ocean: r.wave[400]
290
+ },
291
+ pois: {
292
+ blue: "#7FC4E8",
293
+ green: r.verdant[400],
294
+ lapis: "#8FB0EA",
295
+ pink: "#E3A0C0",
296
+ red: "#EE968F",
297
+ slategray: r.ink[500],
298
+ tangerine: "#E8AC7C",
299
+ turquoise: r.wave[400]
300
+ },
301
+ landcover: {
302
+ grassland: r.verdant[800],
303
+ barren: r.sandstone[500],
304
+ urban_area: r.ink[950],
305
+ farmland: r.verdant[900],
306
+ glacier: r.ink[700],
307
+ scrub: r.verdant[900],
308
+ forest: r.verdant[700]
309
+ }
310
+ };
311
+ const MUTED_LIGHT = {
312
+ background: r.graphite[300],
313
+ earth: r.graphite[50],
314
+ water: r.graphite[200],
315
+ parkA: r.graphite[25],
316
+ parkB: r.graphite[50],
317
+ woodA: r.graphite[25],
318
+ woodB: r.graphite[50],
319
+ scrubA: r.graphite[25],
320
+ scrubB: r.graphite[50],
321
+ sand: r.graphite[100],
322
+ beach: r.graphite[100],
323
+ glacier: r.graphite[25],
324
+ institutional: r.graphite[100],
325
+ pedestrian: r.graphite[25],
326
+ aerodrome: r.graphite[100],
327
+ runway: r.graphite[200],
328
+ pier: r.graphite[200],
329
+ buildings: r.graphite[100],
330
+ railway: r.graphite[400],
331
+ boundaries: r.graphite[500],
332
+ roads: {
333
+ other: r.graphite[25],
334
+ otherCasing: r.graphite[200],
335
+ minor: "#FFFFFF",
336
+ minorCasing: r.graphite[200],
337
+ service: r.graphite[25],
338
+ serviceCasing: r.graphite[200],
339
+ major: "#FFFFFF",
340
+ majorCasing: r.graphite[300],
341
+ highway: "#FFFFFF",
342
+ highwayCasing: r.graphite[300],
343
+ link: "#FFFFFF",
344
+ linkCasing: r.graphite[300]
345
+ },
346
+ tunnelFill: r.graphite[100],
347
+ tunnelCasing: r.graphite[200],
348
+ bridgeCasing: r.graphite[300],
349
+ labels: {
350
+ city: r.graphite[1e3],
351
+ cityHalo: r.graphite[25],
352
+ state: r.graphite[700],
353
+ stateHalo: r.graphite[25],
354
+ country: r.graphite[700],
355
+ subplace: r.graphite[700],
356
+ subplaceHalo: r.graphite[25],
357
+ address: r.graphite[700],
358
+ addressHalo: r.graphite[25],
359
+ roadMinor: r.graphite[700],
360
+ roadMinorHalo: r.graphite[25],
361
+ roadMajor: r.graphite[800],
362
+ roadMajorHalo: r.graphite[25],
363
+ ocean: r.graphite[800]
364
+ },
365
+ pois: {
366
+ blue: r.graphite[600],
367
+ green: r.graphite[600],
368
+ lapis: r.graphite[700],
369
+ pink: r.graphite[600],
370
+ red: r.graphite[700],
371
+ slategray: r.graphite[700],
372
+ tangerine: r.graphite[600],
373
+ turquoise: r.graphite[600]
374
+ },
375
+ landcover: {
376
+ grassland: r.graphite[50],
377
+ barren: r.graphite[100],
378
+ urban_area: r.graphite[100],
379
+ farmland: r.graphite[50],
380
+ glacier: r.graphite[25],
381
+ scrub: r.graphite[50],
382
+ forest: r.graphite[100]
383
+ }
384
+ };
385
+ const MUTED_DARK = {
386
+ background: r.graphite[1e3],
387
+ earth: r.graphite[900],
388
+ water: r.graphite[1e3],
389
+ parkA: r.graphite[900],
390
+ parkB: r.graphite[800],
391
+ woodA: r.graphite[900],
392
+ woodB: r.graphite[800],
393
+ scrubA: r.graphite[900],
394
+ scrubB: r.graphite[800],
395
+ sand: r.graphite[800],
396
+ beach: r.graphite[800],
397
+ glacier: r.graphite[700],
398
+ institutional: r.graphite[900],
399
+ pedestrian: r.graphite[900],
400
+ aerodrome: r.graphite[900],
401
+ runway: r.graphite[800],
402
+ pier: r.graphite[800],
403
+ buildings: r.graphite[800],
404
+ railway: r.graphite[700],
405
+ boundaries: r.graphite[600],
406
+ roads: {
407
+ other: r.graphite[800],
408
+ otherCasing: r.graphite[1e3],
409
+ minor: r.graphite[700],
410
+ minorCasing: r.graphite[1e3],
411
+ service: r.graphite[800],
412
+ serviceCasing: r.graphite[1e3],
413
+ major: r.graphite[600],
414
+ majorCasing: r.graphite[1e3],
415
+ highway: r.graphite[500],
416
+ highwayCasing: r.graphite[1e3],
417
+ link: r.graphite[600],
418
+ linkCasing: r.graphite[1e3]
419
+ },
420
+ tunnelFill: r.graphite[900],
421
+ tunnelCasing: r.graphite[1e3],
422
+ bridgeCasing: r.graphite[1e3],
423
+ labels: {
424
+ city: r.graphite[100],
425
+ cityHalo: r.graphite[1e3],
426
+ state: r.graphite[500],
427
+ stateHalo: r.graphite[1e3],
428
+ country: r.graphite[500],
429
+ subplace: r.graphite[500],
430
+ subplaceHalo: r.graphite[1e3],
431
+ address: r.graphite[500],
432
+ addressHalo: r.graphite[1e3],
433
+ roadMinor: r.graphite[500],
434
+ roadMinorHalo: r.graphite[1e3],
435
+ roadMajor: r.graphite[400],
436
+ roadMajorHalo: r.graphite[1e3],
437
+ ocean: r.graphite[400]
438
+ },
439
+ pois: {
440
+ blue: r.graphite[400],
441
+ green: r.graphite[400],
442
+ lapis: r.graphite[500],
443
+ pink: r.graphite[400],
444
+ red: r.graphite[500],
445
+ slategray: r.graphite[500],
446
+ tangerine: r.graphite[400],
447
+ turquoise: r.graphite[400]
448
+ },
449
+ landcover: {
450
+ grassland: r.graphite[900],
451
+ barren: r.graphite[800],
452
+ urban_area: r.graphite[900],
453
+ farmland: r.graphite[900],
454
+ glacier: r.graphite[700],
455
+ scrub: r.graphite[900],
456
+ forest: r.graphite[800]
457
+ }
458
+ };
459
+ const RELIEF_LIGHT = {
460
+ background: r.ink[200],
461
+ earth: r.ink[50],
462
+ water: r.wave[300],
463
+ parkA: r.verdant[200],
464
+ parkB: r.verdant[300],
465
+ woodA: r.verdant[200],
466
+ woodB: r.verdant[300],
467
+ scrubA: r.verdant[200],
468
+ scrubB: r.verdant[300],
469
+ sand: r.sandstone[200],
470
+ beach: r.sandstone[200],
471
+ glacier: r.ink[25],
472
+ institutional: r.ink[25],
473
+ pedestrian: r.ink[50],
474
+ aerodrome: r.ink[50],
475
+ runway: r.ink[200],
476
+ pier: r.ink[300],
477
+ buildings: r.ink[300],
478
+ railway: r.ink[500],
479
+ boundaries: r.ink[600],
480
+ roads: {
481
+ other: r.ink[50],
482
+ otherCasing: r.ink[300],
483
+ minor: r.ink[25],
484
+ minorCasing: r.ink[300],
485
+ service: r.ink[50],
486
+ serviceCasing: r.ink[300],
487
+ major: "#FFFFFF",
488
+ majorCasing: r.ink[400],
489
+ highway: "#FFFFFF",
490
+ highwayCasing: r.ink[500],
491
+ link: "#FFFFFF",
492
+ linkCasing: r.ink[400]
493
+ },
494
+ tunnelFill: r.ink[200],
495
+ tunnelCasing: r.ink[300],
496
+ bridgeCasing: r.ink[500],
497
+ labels: {
498
+ city: r.ink[1e3],
499
+ cityHalo: r.ink[25],
500
+ state: r.ink[700],
501
+ stateHalo: r.ink[25],
502
+ country: r.ink[700],
503
+ subplace: r.ink[700],
504
+ subplaceHalo: r.ink[25],
505
+ address: r.ink[700],
506
+ addressHalo: r.ink[25],
507
+ roadMinor: r.ink[700],
508
+ roadMinorHalo: r.ink[25],
509
+ roadMajor: r.ink[800],
510
+ roadMajorHalo: r.ink[25],
511
+ ocean: r.wave[800]
512
+ },
513
+ pois: {
514
+ blue: "#2D6E8F",
515
+ green: r.verdant[700],
516
+ lapis: "#3F5C93",
517
+ pink: "#8F4A6B",
518
+ red: "#9B4340",
519
+ slategray: r.ink[700],
520
+ tangerine: "#8F5322",
521
+ turquoise: r.wave[700]
522
+ },
523
+ landcover: {
524
+ grassland: r.verdant[200],
525
+ barren: r.sandstone[200],
526
+ urban_area: r.ink[50],
527
+ farmland: r.verdant[100],
528
+ glacier: r.ink[25],
529
+ scrub: r.verdant[100],
530
+ forest: r.verdant[300]
531
+ }
532
+ };
533
+ const RELIEF_DARK = {
534
+ background: r.ink[1e3],
535
+ earth: r.ink[950],
536
+ water: r.wave[980],
537
+ parkA: r.verdant[800],
538
+ parkB: r.verdant[700],
539
+ woodA: r.verdant[800],
540
+ woodB: r.verdant[700],
541
+ scrubA: r.verdant[800],
542
+ scrubB: r.verdant[700],
543
+ sand: r.sandstone[500],
544
+ beach: r.sandstone[500],
545
+ glacier: r.ink[700],
546
+ institutional: r.ink[1e3],
547
+ pedestrian: r.ink[950],
548
+ aerodrome: r.ink[950],
549
+ runway: r.ink[850],
550
+ pier: r.ink[850],
551
+ buildings: r.ink[900],
552
+ railway: r.ink[700],
553
+ boundaries: r.ink[600],
554
+ roads: {
555
+ other: r.ink[850],
556
+ otherCasing: r.ink[1e3],
557
+ minor: r.ink[800],
558
+ minorCasing: r.ink[1e3],
559
+ service: r.ink[850],
560
+ serviceCasing: r.ink[1e3],
561
+ major: r.ink[700],
562
+ majorCasing: r.ink[1e3],
563
+ highway: r.ink[600],
564
+ highwayCasing: r.ink[1e3],
565
+ link: r.ink[700],
566
+ linkCasing: r.ink[1e3]
567
+ },
568
+ tunnelFill: r.ink[900],
569
+ tunnelCasing: r.ink[1e3],
570
+ bridgeCasing: r.ink[1e3],
571
+ labels: {
572
+ city: r.ink[100],
573
+ cityHalo: r.ink[1e3],
574
+ state: r.ink[500],
575
+ stateHalo: r.ink[1e3],
576
+ country: r.ink[500],
577
+ subplace: r.ink[500],
578
+ subplaceHalo: r.ink[1e3],
579
+ address: r.ink[500],
580
+ addressHalo: r.ink[1e3],
581
+ roadMinor: r.ink[500],
582
+ roadMinorHalo: r.ink[1e3],
583
+ roadMajor: r.ink[400],
584
+ roadMajorHalo: r.ink[1e3],
585
+ ocean: r.wave[400]
586
+ },
587
+ pois: {
588
+ blue: "#7FC4E8",
589
+ green: r.verdant[400],
590
+ lapis: "#8FB0EA",
591
+ pink: "#E3A0C0",
592
+ red: "#EE968F",
593
+ slategray: r.ink[500],
594
+ tangerine: "#E8AC7C",
595
+ turquoise: r.wave[400]
596
+ },
597
+ landcover: {
598
+ grassland: r.verdant[800],
599
+ barren: r.sandstone[500],
600
+ urban_area: r.ink[950],
601
+ farmland: r.verdant[900],
602
+ glacier: r.ink[700],
603
+ scrub: r.verdant[900],
604
+ forest: r.verdant[700]
605
+ }
606
+ };
607
+ const STREET_LIGHT = {
608
+ background: r.ink[300],
609
+ earth: r.sandstone[100],
610
+ water: r.wave[400],
611
+ parkA: r.verdant[200],
612
+ parkB: r.verdant[400],
613
+ woodA: r.verdant[200],
614
+ woodB: r.verdant[400],
615
+ scrubA: r.verdant[200],
616
+ scrubB: r.verdant[400],
617
+ sand: r.sandstone[300],
618
+ beach: r.sandstone[300],
619
+ glacier: r.ink[25],
620
+ institutional: r.sandstone[200],
621
+ pedestrian: r.ink[50],
622
+ aerodrome: r.ink[50],
623
+ runway: r.ink[200],
624
+ pier: r.ink[300],
625
+ buildings: r.ink[400],
626
+ railway: r.ink[500],
627
+ boundaries: r.ink[600],
628
+ roads: {
629
+ other: r.ink[50],
630
+ otherCasing: r.ink[300],
631
+ minor: r.ink[25],
632
+ minorCasing: r.ink[300],
633
+ service: r.ink[50],
634
+ serviceCasing: r.ink[300],
635
+ major: "#FFFFFF",
636
+ majorCasing: r.ink[400],
637
+ highway: r.ember[400],
638
+ highwayCasing: r.ink[500],
639
+ link: "#FFFFFF",
640
+ linkCasing: r.ink[400]
641
+ },
642
+ tunnelFill: r.ink[200],
643
+ tunnelCasing: r.ink[300],
644
+ bridgeCasing: r.ink[500],
645
+ labels: {
646
+ city: r.ink[1e3],
647
+ cityHalo: r.ink[25],
648
+ state: r.ink[700],
649
+ stateHalo: r.ink[25],
650
+ country: r.ink[700],
651
+ subplace: r.ink[700],
652
+ subplaceHalo: r.ink[25],
653
+ address: r.ink[700],
654
+ addressHalo: r.ink[25],
655
+ roadMinor: r.ink[700],
656
+ roadMinorHalo: r.ink[25],
657
+ roadMajor: r.ink[800],
658
+ roadMajorHalo: r.ink[25],
659
+ ocean: r.wave[800]
660
+ },
661
+ pois: {
662
+ blue: "#2D6E8F",
663
+ green: r.verdant[700],
664
+ lapis: "#3F5C93",
665
+ pink: "#8F4A6B",
666
+ red: "#9B4340",
667
+ slategray: r.ink[700],
668
+ tangerine: "#8F5322",
669
+ turquoise: r.wave[700]
670
+ },
671
+ landcover: {
672
+ grassland: r.verdant[300],
673
+ barren: r.sandstone[300],
674
+ urban_area: r.ink[100],
675
+ farmland: r.sandstone[200],
676
+ glacier: r.ink[25],
677
+ scrub: r.verdant[200],
678
+ forest: r.verdant[400]
679
+ }
680
+ };
681
+ const STREET_DARK = {
682
+ background: r.ink[1e3],
683
+ earth: r.ink[900],
684
+ water: r.wave[950],
685
+ parkA: r.verdant[800],
686
+ parkB: r.verdant[700],
687
+ woodA: r.verdant[800],
688
+ woodB: r.verdant[700],
689
+ scrubA: r.verdant[800],
690
+ scrubB: r.verdant[700],
691
+ sand: r.sandstone[500],
692
+ beach: r.sandstone[500],
693
+ glacier: r.ink[700],
694
+ institutional: r.ink[850],
695
+ pedestrian: r.ink[950],
696
+ aerodrome: r.ink[950],
697
+ runway: r.ink[850],
698
+ pier: r.ink[850],
699
+ buildings: r.ink[800],
700
+ railway: r.ink[700],
701
+ boundaries: r.ink[600],
702
+ roads: {
703
+ other: r.ink[850],
704
+ otherCasing: r.ink[1e3],
705
+ minor: r.ink[800],
706
+ minorCasing: r.ink[1e3],
707
+ service: r.ink[850],
708
+ serviceCasing: r.ink[1e3],
709
+ major: r.ink[700],
710
+ majorCasing: r.ink[1e3],
711
+ highway: r.ember[600],
712
+ highwayCasing: r.ink[1e3],
713
+ link: r.ink[700],
714
+ linkCasing: r.ink[1e3]
715
+ },
716
+ tunnelFill: r.ink[900],
717
+ tunnelCasing: r.ink[1e3],
718
+ bridgeCasing: r.ink[1e3],
719
+ labels: {
720
+ city: r.ink[100],
721
+ cityHalo: r.ink[1e3],
722
+ state: r.ink[500],
723
+ stateHalo: r.ink[1e3],
724
+ country: r.ink[500],
725
+ subplace: r.ink[500],
726
+ subplaceHalo: r.ink[1e3],
727
+ address: r.ink[500],
728
+ addressHalo: r.ink[1e3],
729
+ roadMinor: r.ink[500],
730
+ roadMinorHalo: r.ink[1e3],
731
+ roadMajor: r.ink[400],
732
+ roadMajorHalo: r.ink[1e3],
733
+ ocean: r.wave[400]
734
+ },
735
+ pois: {
736
+ blue: "#7FC4E8",
737
+ green: r.verdant[400],
738
+ lapis: "#8FB0EA",
739
+ pink: "#E3A0C0",
740
+ red: "#EE968F",
741
+ slategray: r.ink[500],
742
+ tangerine: "#E8AC7C",
743
+ turquoise: r.wave[400]
744
+ },
745
+ landcover: {
746
+ grassland: r.verdant[700],
747
+ barren: r.sandstone[500],
748
+ urban_area: r.ink[850],
749
+ farmland: r.verdant[800],
750
+ glacier: r.ink[700],
751
+ scrub: r.verdant[800],
752
+ forest: r.verdant[700]
753
+ }
754
+ };
755
+ const OUTDOOR_LIGHT = {
756
+ background: r.ink[200],
757
+ earth: r.ink[50],
758
+ water: r.wave[300],
759
+ parkA: r.verdant[300],
760
+ parkB: r.verdant[400],
761
+ woodA: r.verdant[300],
762
+ woodB: r.verdant[400],
763
+ scrubA: r.verdant[200],
764
+ scrubB: r.verdant[300],
765
+ sand: r.sandstone[300],
766
+ beach: r.sandstone[300],
767
+ glacier: r.ink[25],
768
+ institutional: r.ink[25],
769
+ pedestrian: r.ink[50],
770
+ aerodrome: r.ink[50],
771
+ runway: r.ink[200],
772
+ pier: r.ink[300],
773
+ // A step lighter than `relief`: outdoors, the building footprint is context, not content.
774
+ buildings: r.ink[200],
775
+ railway: r.ink[500],
776
+ boundaries: r.ink[600],
777
+ roads: {
778
+ other: r.ink[50],
779
+ otherCasing: r.ink[200],
780
+ minor: r.ink[25],
781
+ minorCasing: r.ink[200],
782
+ service: r.ink[50],
783
+ serviceCasing: r.ink[200],
784
+ major: "#FFFFFF",
785
+ majorCasing: r.ink[300],
786
+ highway: "#FFFFFF",
787
+ highwayCasing: r.ink[400],
788
+ link: "#FFFFFF",
789
+ linkCasing: r.ink[300]
790
+ },
791
+ tunnelFill: r.ink[200],
792
+ tunnelCasing: r.ink[300],
793
+ bridgeCasing: r.ink[400],
794
+ labels: {
795
+ city: r.ink[1e3],
796
+ cityHalo: r.ink[25],
797
+ state: r.ink[700],
798
+ stateHalo: r.ink[25],
799
+ country: r.ink[700],
800
+ subplace: r.ink[700],
801
+ subplaceHalo: r.ink[25],
802
+ address: r.ink[700],
803
+ addressHalo: r.ink[25],
804
+ roadMinor: r.ink[700],
805
+ roadMinorHalo: r.ink[25],
806
+ roadMajor: r.ink[800],
807
+ roadMajorHalo: r.ink[25],
808
+ ocean: r.wave[800]
809
+ },
810
+ pois: {
811
+ blue: "#2D6E8F",
812
+ // Peaks, parks, forests and gardens all come through green. On this flavour they are the
813
+ // POIs worth finding, so it carries a full step more weight than elsewhere.
814
+ green: r.verdant[800],
815
+ lapis: "#3F5C93",
816
+ pink: "#8F4A6B",
817
+ red: "#9B4340",
818
+ slategray: r.ink[700],
819
+ tangerine: "#8F5322",
820
+ turquoise: r.wave[700]
821
+ },
822
+ landcover: {
823
+ grassland: r.verdant[200],
824
+ barren: r.sandstone[300],
825
+ urban_area: r.ink[100],
826
+ farmland: r.sandstone[200],
827
+ glacier: r.ink[25],
828
+ scrub: r.verdant[300],
829
+ forest: r.verdant[400]
830
+ }
831
+ };
832
+ const OUTDOOR_DARK = {
833
+ background: r.ink[1e3],
834
+ earth: r.ink[950],
835
+ water: r.wave[980],
836
+ parkA: r.verdant[700],
837
+ parkB: r.verdant[600],
838
+ woodA: r.verdant[700],
839
+ woodB: r.verdant[600],
840
+ scrubA: r.verdant[800],
841
+ scrubB: r.verdant[700],
842
+ sand: r.sandstone[500],
843
+ beach: r.sandstone[500],
844
+ glacier: r.ink[700],
845
+ institutional: r.ink[1e3],
846
+ pedestrian: r.ink[950],
847
+ aerodrome: r.ink[950],
848
+ runway: r.ink[850],
849
+ pier: r.ink[850],
850
+ buildings: r.ink[900],
851
+ railway: r.ink[700],
852
+ boundaries: r.ink[600],
853
+ roads: {
854
+ // The minor tier recedes toward `earth`; majors and highways hold their `relief` values,
855
+ // because a road you cannot see is a different failure from a road that is merely quiet.
856
+ other: r.ink[900],
857
+ otherCasing: r.ink[1e3],
858
+ minor: r.ink[850],
859
+ minorCasing: r.ink[1e3],
860
+ service: r.ink[900],
861
+ serviceCasing: r.ink[1e3],
862
+ major: r.ink[700],
863
+ majorCasing: r.ink[1e3],
864
+ highway: r.ink[600],
865
+ highwayCasing: r.ink[1e3],
866
+ link: r.ink[700],
867
+ linkCasing: r.ink[1e3]
868
+ },
869
+ tunnelFill: r.ink[900],
870
+ tunnelCasing: r.ink[1e3],
871
+ bridgeCasing: r.ink[1e3],
872
+ labels: {
873
+ city: r.ink[100],
874
+ cityHalo: r.ink[1e3],
875
+ state: r.ink[500],
876
+ stateHalo: r.ink[1e3],
877
+ country: r.ink[500],
878
+ subplace: r.ink[500],
879
+ subplaceHalo: r.ink[1e3],
880
+ address: r.ink[500],
881
+ addressHalo: r.ink[1e3],
882
+ roadMinor: r.ink[500],
883
+ roadMinorHalo: r.ink[1e3],
884
+ roadMajor: r.ink[400],
885
+ roadMajorHalo: r.ink[1e3],
886
+ ocean: r.wave[400]
887
+ },
888
+ pois: {
889
+ blue: "#7FC4E8",
890
+ green: r.verdant[300],
891
+ lapis: "#8FB0EA",
892
+ pink: "#E3A0C0",
893
+ red: "#EE968F",
894
+ slategray: r.ink[500],
895
+ tangerine: "#E8AC7C",
896
+ turquoise: r.wave[400]
897
+ },
898
+ landcover: {
899
+ grassland: r.verdant[700],
900
+ barren: r.sandstone[500],
901
+ urban_area: r.ink[950],
902
+ farmland: r.verdant[900],
903
+ glacier: r.ink[700],
904
+ scrub: r.verdant[800],
905
+ forest: r.verdant[600]
906
+ }
907
+ };
908
+ return {
909
+ base: { light: BASE_LIGHT, dark: BASE_DARK },
910
+ muted: { light: MUTED_LIGHT, dark: MUTED_DARK },
911
+ relief: { light: RELIEF_LIGHT, dark: RELIEF_DARK },
912
+ street: { light: STREET_LIGHT, dark: STREET_DARK },
913
+ outdoor: { light: OUTDOOR_LIGHT, dark: OUTDOOR_DARK }
914
+ };
915
+ }
916
+ var RECIPES = buildRecipes(STOCK_RAMPS);
917
+
918
+ // src/validate.ts
919
+ var ThemeError = class extends Error {
920
+ detail;
921
+ constructor(message, detail) {
922
+ super(message);
923
+ this.name = "ThemeError";
924
+ this.detail = detail;
925
+ }
926
+ };
927
+ var HEX = /^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/;
928
+ var MAX_NAME_LENGTH = 40;
929
+ var MARKER_BOUNDS = {
930
+ scale: [0.5, 2]
931
+ };
932
+ var ROUTE_BOUNDS = {
933
+ width: [1, 12],
934
+ opacity: [0, 1],
935
+ casingWidth: [0, 24],
936
+ casingOpacity: [0, 1]
937
+ };
938
+ var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
939
+ function walkBasemap(value, reference, path, errors) {
940
+ if (!isRecord(value)) {
941
+ errors.push(`${path}: must be an object`);
942
+ return;
943
+ }
944
+ const ref = reference;
945
+ for (const [key, v] of Object.entries(value)) {
946
+ const target = ref[key];
947
+ const p = `${path}.${key}`;
948
+ if (target === void 0) {
949
+ errors.push(`${p}: unknown field`);
950
+ } else if (typeof target === "string") {
951
+ if (typeof v !== "string" || !HEX.test(v)) {
952
+ errors.push(`${p}: must be a hex colour (#RRGGBB or #RRGGBBAA)`);
953
+ }
954
+ } else {
955
+ walkBasemap(v, target, p, errors);
956
+ }
957
+ }
958
+ }
959
+ function walkLeaf(value, colorKeys, bounds, path, errors) {
960
+ if (!isRecord(value)) {
961
+ errors.push(`${path}: must be an object`);
962
+ return;
963
+ }
964
+ for (const [key, v] of Object.entries(value)) {
965
+ const p = `${path}.${key}`;
966
+ if (colorKeys.includes(key)) {
967
+ if (typeof v !== "string" || !HEX.test(v)) {
968
+ errors.push(`${p}: must be a hex colour (#RRGGBB or #RRGGBBAA)`);
969
+ }
970
+ } else if (key in bounds) {
971
+ const [lo, hi] = bounds[key];
972
+ if (typeof v !== "number" || !Number.isFinite(v) || v < lo || v > hi) {
973
+ errors.push(`${p}: out of bounds (${lo}\u2013${hi})`);
974
+ }
975
+ } else {
976
+ errors.push(`${p}: unknown field`);
977
+ }
978
+ }
979
+ }
980
+ var HALF_KEYS = ["basemap", "marker", "route"];
981
+ var DOC_KEYS = ["v", "name", "base", "seeds", "light", "dark"];
982
+ var SEED_COLOR_KEYS = ["land", "water", "nature", "accent"];
983
+ var REQUIRED_SEED_KEYS = ["land", "water", "nature"];
984
+ function validateTheme(input) {
985
+ const errors = [];
986
+ if (!isRecord(input)) {
987
+ throw new ThemeError("invalid theme", ["theme must be a JSON object"]);
988
+ }
989
+ for (const key of Object.keys(input)) {
990
+ if (!DOC_KEYS.includes(key)) errors.push(`${key}: unknown field`);
991
+ }
992
+ if (input.v !== 1) errors.push("v: must be 1");
993
+ if (typeof input.base !== "string" || !THEME_BASES.includes(input.base)) {
994
+ errors.push(`base: must be one of ${THEME_BASES.join(", ")}`);
995
+ }
996
+ if (input.name !== void 0) {
997
+ if (typeof input.name !== "string" || input.name.length > MAX_NAME_LENGTH) {
998
+ errors.push(`name: must be a string of at most ${MAX_NAME_LENGTH} characters`);
999
+ }
1000
+ }
1001
+ if (input.seeds !== void 0) {
1002
+ walkLeaf(input.seeds, SEED_COLOR_KEYS, {}, "seeds", errors);
1003
+ if (isRecord(input.seeds)) {
1004
+ for (const key of REQUIRED_SEED_KEYS) {
1005
+ if (input.seeds[key] === void 0) errors.push(`seeds.${key}: required`);
1006
+ }
1007
+ }
1008
+ }
1009
+ const reference = RECIPES.base.light;
1010
+ for (const mode of ["light", "dark"]) {
1011
+ const half = input[mode];
1012
+ if (half === void 0) continue;
1013
+ if (!isRecord(half)) {
1014
+ errors.push(`${mode}: must be an object`);
1015
+ continue;
1016
+ }
1017
+ for (const key of Object.keys(half)) {
1018
+ if (!HALF_KEYS.includes(key)) {
1019
+ errors.push(`${mode}.${key}: unknown field`);
1020
+ }
1021
+ }
1022
+ if (half.basemap !== void 0) walkBasemap(half.basemap, reference, `${mode}.basemap`, errors);
1023
+ if (half.marker !== void 0) {
1024
+ walkLeaf(half.marker, ["color"], MARKER_BOUNDS, `${mode}.marker`, errors);
1025
+ }
1026
+ if (half.route !== void 0) {
1027
+ walkLeaf(half.route, ["color", "casingColor"], ROUTE_BOUNDS, `${mode}.route`, errors);
1028
+ }
1029
+ }
1030
+ if (errors.length > 0) throw new ThemeError("invalid theme", errors);
1031
+ return input;
1032
+ }
1033
+
1034
+ // src/codec.ts
1035
+ var THEME_CODE_PREFIX = "ut1.";
1036
+ var MAX_CODE_LENGTH = 4096;
1037
+ var MAX_JSON_BYTES = 16 * 1024;
1038
+ async function pump(stream, cap) {
1039
+ const reader = stream.getReader();
1040
+ const chunks = [];
1041
+ let size = 0;
1042
+ for (; ; ) {
1043
+ const { done, value } = await reader.read();
1044
+ if (done) break;
1045
+ size += value.byteLength;
1046
+ if (size > cap) {
1047
+ await reader.cancel().catch(() => {
1048
+ });
1049
+ throw new ThemeError("invalid theme", ["theme document too large"]);
1050
+ }
1051
+ chunks.push(value);
1052
+ }
1053
+ const out = new Uint8Array(size);
1054
+ let off = 0;
1055
+ for (const c of chunks) {
1056
+ out.set(c, off);
1057
+ off += c.byteLength;
1058
+ }
1059
+ return out;
1060
+ }
1061
+ var toB64url = (b) => {
1062
+ let bin = "";
1063
+ for (let i = 0; i < b.length; i += 32768) {
1064
+ bin += String.fromCharCode(...b.subarray(i, i + 32768));
1065
+ }
1066
+ return btoa(bin).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
1067
+ };
1068
+ var fromB64url = (s) => {
1069
+ const bin = atob(s.replaceAll("-", "+").replaceAll("_", "/"));
1070
+ return Uint8Array.from(bin, (c) => c.charCodeAt(0));
1071
+ };
1072
+ async function through(bytes, transform, cap) {
1073
+ const stream = new Blob([bytes]).stream().pipeThrough(transform);
1074
+ return pump(stream, cap);
1075
+ }
1076
+ async function encodeTheme(doc) {
1077
+ const valid = validateTheme(doc);
1078
+ const json = new TextEncoder().encode(JSON.stringify(valid));
1079
+ const packed = await through(json, new CompressionStream("deflate"), Number.POSITIVE_INFINITY);
1080
+ const code = THEME_CODE_PREFIX + toB64url(packed);
1081
+ if (code.length > MAX_CODE_LENGTH) {
1082
+ throw new ThemeError("invalid theme", [
1083
+ `encoded theme exceeds ${MAX_CODE_LENGTH} characters`
1084
+ ]);
1085
+ }
1086
+ return code;
1087
+ }
1088
+ async function decodeTheme(code) {
1089
+ if (code.length > MAX_CODE_LENGTH) {
1090
+ throw new ThemeError("invalid theme", [`code exceeds ${MAX_CODE_LENGTH} characters`]);
1091
+ }
1092
+ if (!code.startsWith(THEME_CODE_PREFIX)) {
1093
+ throw new ThemeError("invalid theme", [`code must start with "${THEME_CODE_PREFIX}"`]);
1094
+ }
1095
+ let bytes;
1096
+ try {
1097
+ const packed = fromB64url(code.slice(THEME_CODE_PREFIX.length));
1098
+ bytes = await through(packed, new DecompressionStream("deflate"), MAX_JSON_BYTES);
1099
+ } catch (e) {
1100
+ if (e instanceof ThemeError) throw e;
1101
+ throw new ThemeError("invalid theme", ["code is not valid base64url deflate data"]);
1102
+ }
1103
+ let parsed;
1104
+ try {
1105
+ parsed = JSON.parse(new TextDecoder().decode(bytes));
1106
+ } catch {
1107
+ throw new ThemeError("invalid theme", ["decoded payload is not JSON"]);
1108
+ }
1109
+ return validateTheme(parsed);
1110
+ }
1111
+
1112
+ // src/compose.ts
1113
+ var DEFAULT_MARKER = { color: "#FF3E9A", scale: 1 };
1114
+ var DEFAULT_ROUTE = {
1115
+ color: "#FF3E9A",
1116
+ width: 3,
1117
+ opacity: 1,
1118
+ casingColor: "#FF3E9A",
1119
+ casingWidth: 10,
1120
+ casingOpacity: 0.2
1121
+ };
1122
+ function mergeRecipe(base, overrides) {
1123
+ if (!overrides) return structuredClone(base);
1124
+ const walk = (ref, over) => {
1125
+ const out = {};
1126
+ for (const [key, refValue] of Object.entries(ref)) {
1127
+ const o = over[key];
1128
+ if (refValue !== null && typeof refValue === "object") {
1129
+ out[key] = o !== null && typeof o === "object" && !Array.isArray(o) ? walk(refValue, o) : structuredClone(refValue);
1130
+ } else {
1131
+ out[key] = typeof o === "string" ? o : refValue;
1132
+ }
1133
+ }
1134
+ return out;
1135
+ };
1136
+ return walk(
1137
+ base,
1138
+ overrides
1139
+ );
1140
+ }
1141
+ function resolveTheme(doc, mode) {
1142
+ const half = doc[mode] ?? {};
1143
+ const recipe = mergeRecipe(RECIPES[doc.base][mode], half.basemap);
1144
+ return {
1145
+ flavorName: `${doc.base}-${mode}`,
1146
+ flavor: makeFlavor(recipe),
1147
+ marker: { ...DEFAULT_MARKER, ...half.marker },
1148
+ route: { ...DEFAULT_ROUTE, ...half.route }
1149
+ };
1150
+ }
1151
+
1152
+ // src/contrast.ts
1153
+ function parseHex(hex) {
1154
+ const h = hex.replace("#", "");
1155
+ return [
1156
+ parseInt(h.slice(0, 2), 16),
1157
+ parseInt(h.slice(2, 4), 16),
1158
+ parseInt(h.slice(4, 6), 16)
1159
+ ];
1160
+ }
1161
+ function luminance(hex) {
1162
+ const toLinear = (v) => {
1163
+ const c = v / 255;
1164
+ return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
1165
+ };
1166
+ const [r, g, b] = parseHex(hex);
1167
+ return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
1168
+ }
1169
+ function contrast(a, b) {
1170
+ const la = luminance(a);
1171
+ const lb = luminance(b);
1172
+ const [hi, lo] = la > lb ? [la, lb] : [lb, la];
1173
+ return (hi + 0.05) / (lo + 0.05);
1174
+ }
1175
+ var PRIMARY = [
1176
+ ["city_label", "city_label_halo"],
1177
+ ["state_label", "state_label_halo"],
1178
+ ["country_label", "earth"],
1179
+ ["roads_label_major", "roads_label_major_halo"]
1180
+ ];
1181
+ var SECONDARY = [
1182
+ ["subplace_label", "subplace_label_halo"],
1183
+ ["address_label", "address_label_halo"],
1184
+ ["roads_label_minor", "roads_label_minor_halo"],
1185
+ ["ocean_label", "water"]
1186
+ ];
1187
+ var LABEL_PAIRS = [
1188
+ ...PRIMARY.map(([l, b]) => [l, b, 4.5]),
1189
+ ...SECONDARY.map(([l, b]) => [l, b, 3])
1190
+ ];
1191
+ function contrastWarnings(doc) {
1192
+ const warnings = [];
1193
+ for (const mode of MODES) {
1194
+ const flavor = resolveTheme(doc, mode).flavor;
1195
+ const check = (pairs, floor) => {
1196
+ for (const [label, background] of pairs) {
1197
+ const ratio = contrast(flavor[label], flavor[background]);
1198
+ if (ratio < floor) {
1199
+ warnings.push({ mode, label: String(label), background: String(background), ratio, floor });
1200
+ }
1201
+ }
1202
+ };
1203
+ check(PRIMARY, 4.5);
1204
+ check(SECONDARY, 3);
1205
+ }
1206
+ return warnings;
1207
+ }
1208
+
1209
+ // src/presets.ts
1210
+ var PRESET_THEMES = {
1211
+ base: { v: 1, name: "ink", base: "base" },
1212
+ muted: { v: 1, name: "graphite", base: "muted" },
1213
+ relief: { v: 1, name: "relief", base: "relief" },
1214
+ street: { v: 1, name: "street", base: "street" },
1215
+ outdoor: { v: 1, name: "outdoor", base: "outdoor" }
1216
+ };
1217
+
1218
+ // src/oklch.ts
1219
+ var srgbToLinear = (v) => v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
1220
+ var linearToSrgb = (v) => v <= 31308e-7 ? 12.92 * v : 1.055 * v ** (1 / 2.4) - 0.055;
1221
+ function hexToRgb(hex) {
1222
+ const h = hex.replace("#", "");
1223
+ return [
1224
+ parseInt(h.slice(0, 2), 16) / 255,
1225
+ parseInt(h.slice(2, 4), 16) / 255,
1226
+ parseInt(h.slice(4, 6), 16) / 255
1227
+ ];
1228
+ }
1229
+ function hexToOklch(hex) {
1230
+ const [r, g, b] = hexToRgb(hex).map(srgbToLinear);
1231
+ const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
1232
+ const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
1233
+ const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
1234
+ const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
1235
+ const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
1236
+ const bb = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
1237
+ const c = Math.hypot(a, bb);
1238
+ let h = Math.atan2(bb, a) * 180 / Math.PI;
1239
+ if (h < 0) h += 360;
1240
+ return { l: L, c, h };
1241
+ }
1242
+ function oklchToLinear({ l: L, c, h }) {
1243
+ const hr = h * Math.PI / 180;
1244
+ const a = c * Math.cos(hr);
1245
+ const bb = c * Math.sin(hr);
1246
+ const l = (L + 0.3963377774 * a + 0.2158037573 * bb) ** 3;
1247
+ const m = (L - 0.1055613458 * a - 0.0638541728 * bb) ** 3;
1248
+ const s = (L - 0.0894841775 * a - 1.291485548 * bb) ** 3;
1249
+ return [
1250
+ 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
1251
+ -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
1252
+ -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
1253
+ ];
1254
+ }
1255
+ var inGamut = (rgb) => rgb.every((v) => v >= -1e-6 && v <= 1 + 1e-6);
1256
+ function oklchToHex(color) {
1257
+ let { c } = color;
1258
+ let rgb = oklchToLinear({ ...color, c });
1259
+ if (!inGamut(rgb)) {
1260
+ let lo = 0;
1261
+ let hi = c;
1262
+ for (let i = 0; i < 24; i++) {
1263
+ c = (lo + hi) / 2;
1264
+ rgb = oklchToLinear({ ...color, c });
1265
+ if (inGamut(rgb)) lo = c;
1266
+ else hi = c;
1267
+ }
1268
+ rgb = oklchToLinear({ ...color, c: lo });
1269
+ }
1270
+ const to255 = (v) => Math.round(Math.min(1, Math.max(0, linearToSrgb(v))) * 255);
1271
+ return "#" + rgb.map((v) => to255(v).toString(16).padStart(2, "0")).join("").toUpperCase();
1272
+ }
1273
+
1274
+ // src/generate.ts
1275
+ var NEUTRAL_CHROMA_CAP = 0.045;
1276
+ var MUTED_CHROMA_CAP = 0.012;
1277
+ var CHROMA_CAP = 0.32;
1278
+ var DEFAULT_SEEDS = {
1279
+ land: STOCK_RAMPS.ink[500],
1280
+ water: STOCK_RAMPS.wave[500],
1281
+ nature: STOCK_RAMPS.verdant[500],
1282
+ accent: "#FF3E9A"
1283
+ };
1284
+ function reRamp(ref, seedHex, cap) {
1285
+ const seed = hexToOklch(seedHex);
1286
+ const steps = Object.keys(ref);
1287
+ const midStep = steps[Math.floor(steps.length / 2)];
1288
+ const midChroma = Math.max(hexToOklch(ref[midStep]).c, 5e-3);
1289
+ const factor = Math.min(Math.max(seed.c / midChroma, 0.15), 3);
1290
+ const out = {};
1291
+ for (const step of steps) {
1292
+ const refColor = hexToOklch(ref[step]);
1293
+ out[step] = oklchToHex({
1294
+ l: refColor.l,
1295
+ c: Math.min(refColor.c * factor, cap),
1296
+ h: seed.h
1297
+ });
1298
+ }
1299
+ return out;
1300
+ }
1301
+ function enforceLabelContrast(recipe) {
1302
+ const fixed = structuredClone(recipe);
1303
+ for (const [labelSlot, bgSlot, floor] of LABEL_PAIRS) {
1304
+ for (let i = 0; i < 40; i++) {
1305
+ const flavor = makeFlavor(fixed);
1306
+ const label = flavor[labelSlot];
1307
+ const bg = flavor[bgSlot];
1308
+ if (contrast(label, bg) >= floor) break;
1309
+ const l = hexToOklch(label);
1310
+ const direction = hexToOklch(bg).l > l.l ? -1 : 1;
1311
+ const nudged = oklchToHex({ ...l, l: Math.min(1, Math.max(0, l.l + direction * 0.015)) });
1312
+ setLabelSlot(fixed, labelSlot, nudged);
1313
+ }
1314
+ }
1315
+ return fixed;
1316
+ }
1317
+ var LABEL_SLOT_TO_RECIPE = {
1318
+ city_label: (r, v) => r.labels.city = v,
1319
+ state_label: (r, v) => r.labels.state = v,
1320
+ country_label: (r, v) => r.labels.country = v,
1321
+ roads_label_major: (r, v) => r.labels.roadMajor = v,
1322
+ subplace_label: (r, v) => r.labels.subplace = v,
1323
+ address_label: (r, v) => r.labels.address = v,
1324
+ roads_label_minor: (r, v) => r.labels.roadMinor = v,
1325
+ ocean_label: (r, v) => r.labels.ocean = v
1326
+ };
1327
+ function setLabelSlot(recipe, slot, hex) {
1328
+ LABEL_SLOT_TO_RECIPE[slot]?.(recipe, hex);
1329
+ }
1330
+ function diffRecipe(stock, generated) {
1331
+ const walk = (a, b) => {
1332
+ const out = {};
1333
+ for (const [key, stockValue] of Object.entries(a)) {
1334
+ const genValue = b[key];
1335
+ if (stockValue !== null && typeof stockValue === "object") {
1336
+ const nested = walk(
1337
+ stockValue,
1338
+ genValue ?? {}
1339
+ );
1340
+ if (Object.keys(nested).length > 0) out[key] = nested;
1341
+ } else if (typeof genValue === "string" && genValue !== stockValue) {
1342
+ out[key] = genValue;
1343
+ }
1344
+ }
1345
+ return out;
1346
+ };
1347
+ const diff = walk(
1348
+ stock,
1349
+ generated
1350
+ );
1351
+ return Object.keys(diff).length > 0 ? diff : void 0;
1352
+ }
1353
+ function generateTheme(base, seeds) {
1354
+ const ramps = {
1355
+ ...STOCK_RAMPS,
1356
+ ink: reRamp(STOCK_RAMPS.ink, seeds.land, NEUTRAL_CHROMA_CAP),
1357
+ graphite: reRamp(STOCK_RAMPS.graphite, seeds.land, MUTED_CHROMA_CAP),
1358
+ wave: reRamp(STOCK_RAMPS.wave, seeds.water, CHROMA_CAP),
1359
+ verdant: reRamp(STOCK_RAMPS.verdant, seeds.nature, CHROMA_CAP)
1360
+ };
1361
+ const generated = buildRecipes(ramps);
1362
+ const doc = { v: 1, base, seeds: { ...seeds } };
1363
+ for (const mode of MODES) {
1364
+ const recipe = enforceLabelContrast(generated[base][mode]);
1365
+ const basemap = diffRecipe(RECIPES[base][mode], recipe);
1366
+ const half = {};
1367
+ if (basemap) half.basemap = basemap;
1368
+ if (seeds.accent) {
1369
+ half.marker = { color: seeds.accent };
1370
+ half.route = { color: seeds.accent, casingColor: seeds.accent };
1371
+ }
1372
+ if (Object.keys(half).length > 0) doc[mode] = half;
1373
+ }
1374
+ return doc;
1375
+ }
1376
+
1377
+ // src/palettes.ts
1378
+ var SEED_PALETTES = [
1379
+ { name: "process ink", seeds: DEFAULT_SEEDS },
1380
+ {
1381
+ name: "boreal",
1382
+ seeds: { land: "#7D8B87", water: "#33606B", nature: "#3F6B4F", accent: "#E4572E" }
1383
+ },
1384
+ {
1385
+ name: "prairie",
1386
+ seeds: { land: "#A08D6B", water: "#4E7E99", nature: "#7C8A4D", accent: "#C33149" }
1387
+ },
1388
+ {
1389
+ name: "tundra",
1390
+ seeds: { land: "#8B9AA6", water: "#4A7BA6", nature: "#6E8B74", accent: "#FF3E9A" }
1391
+ },
1392
+ {
1393
+ name: "badlands",
1394
+ seeds: { land: "#A67B5B", water: "#3E7C8F", nature: "#6B7F53", accent: "#22577A" }
1395
+ },
1396
+ {
1397
+ name: "aurora",
1398
+ seeds: { land: "#8C86A3", water: "#4F6D9E", nature: "#56828A", accent: "#58E1C1" }
1399
+ },
1400
+ {
1401
+ name: "pacific",
1402
+ seeds: { land: "#7F9089", water: "#2E7D74", nature: "#38755B", accent: "#F25F5C" }
1403
+ },
1404
+ {
1405
+ name: "chinook",
1406
+ seeds: { land: "#9C8A5D", water: "#56789C", nature: "#8A9A5B", accent: "#D95D39" }
1407
+ }
1408
+ ];
1409
+ var wrap = (h) => (h % 360 + 360) % 360;
1410
+ function shuffleSeeds(rng = Math.random) {
1411
+ const h = rng() * 360;
1412
+ return {
1413
+ land: oklchToHex({ l: 0.55, c: 0.03 + rng() * 0.05, h }),
1414
+ water: oklchToHex({ l: 0.5, c: 0.08 + rng() * 0.1, h: wrap(h + 150 + rng() * 60) }),
1415
+ nature: oklchToHex({ l: 0.5, c: 0.06 + rng() * 0.08, h: wrap(h + 60 + rng() * 80) }),
1416
+ accent: oklchToHex({ l: 0.65, c: 0.22 + rng() * 0.08, h: wrap(h + 170 + rng() * 40) })
1417
+ };
1418
+ }
1419
+ export {
1420
+ DEFAULT_MARKER,
1421
+ DEFAULT_ROUTE,
1422
+ DEFAULT_SEEDS,
1423
+ LABEL_PAIRS,
1424
+ MAX_CODE_LENGTH,
1425
+ MODES,
1426
+ PRESET_THEMES,
1427
+ RECIPES,
1428
+ SEED_PALETTES,
1429
+ STOCK_RAMPS,
1430
+ THEME_BASES,
1431
+ THEME_CODE_PREFIX,
1432
+ ThemeError,
1433
+ buildRecipes,
1434
+ contrast,
1435
+ contrastWarnings,
1436
+ decodeTheme,
1437
+ ember,
1438
+ encodeTheme,
1439
+ generateTheme,
1440
+ graphite,
1441
+ hexToOklch,
1442
+ ink,
1443
+ makeFlavor,
1444
+ mergeRecipe,
1445
+ oklchToHex,
1446
+ resolveTheme,
1447
+ sandstone,
1448
+ shuffleSeeds,
1449
+ validateTheme,
1450
+ verdant,
1451
+ wave
1452
+ };
1453
+ //# sourceMappingURL=index.js.map