@unmap/themes 0.1.2 → 0.2.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/dist/index.js CHANGED
@@ -23,6 +23,22 @@ var ink = {
23
23
  950: "#0E191C",
24
24
  1e3: "#030E10"
25
25
  };
26
+ var paper = {
27
+ 25: "#FBF5F2",
28
+ 50: "#F5EFEC",
29
+ 100: "#ECE6E3",
30
+ 200: "#E1DBD8",
31
+ 300: "#D4CECB",
32
+ 400: "#C3BDBA",
33
+ 500: "#A9A3A0",
34
+ 600: "#8A8582",
35
+ 700: "#696361",
36
+ 800: "#4A4543",
37
+ 850: "#373330",
38
+ 900: "#272220",
39
+ 950: "#1A1614",
40
+ 1e3: "#0E0B09"
41
+ };
26
42
  var wave = {
27
43
  100: "#C7F1F3",
28
44
  200: "#B3E2E4",
@@ -36,6 +52,19 @@ var wave = {
36
52
  950: "#001C1E",
37
53
  980: "#001012"
38
54
  };
55
+ var sky = {
56
+ 100: "#CCEEFF",
57
+ 200: "#A7E2FF",
58
+ 300: "#8ACFF0",
59
+ 400: "#73B7D7",
60
+ 500: "#5A9CBC",
61
+ 600: "#387D9C",
62
+ 700: "#155F7C",
63
+ 800: "#004057",
64
+ 900: "#002837",
65
+ 950: "#001B27",
66
+ 980: "#000F17"
67
+ };
39
68
  var verdant = {
40
69
  100: "#D3F1DA",
41
70
  200: "#C3E3CA",
@@ -68,9 +97,62 @@ var graphite = {
68
97
  900: "#232323",
69
98
  1e3: "#0B0B0B"
70
99
  };
100
+ var mist = {
101
+ 100: "#E7E7E7",
102
+ 200: "#D7D7D7",
103
+ 300: "#C4C4C4",
104
+ 400: "#ACACAC",
105
+ 500: "#929292",
106
+ 600: "#757575",
107
+ 700: "#575757",
108
+ 800: "#3A3A3A",
109
+ 900: "#242424",
110
+ 950: "#171717",
111
+ 980: "#0D0D0D"
112
+ };
113
+ var jade = {
114
+ 25: "#F1F9F4",
115
+ 50: "#E6F4ED",
116
+ 100: "#D7EEE2",
117
+ 200: "#C3E6D4",
118
+ 300: "#ACDDC4",
119
+ 400: "#8BCFAF",
120
+ 500: "#56BB90",
121
+ 600: "#459975",
122
+ 700: "#327357",
123
+ 800: "#21513D",
124
+ 850: "#173C2C",
125
+ 900: "#0E2A1E",
126
+ 950: "#071C13",
127
+ 1e3: "#030F09"
128
+ };
129
+ var cobalt = {
130
+ 25: "#F4F7FB",
131
+ 50: "#ECF0F9",
132
+ 100: "#E1E8F7",
133
+ 200: "#D2DCF5",
134
+ 300: "#C0CFF3",
135
+ 400: "#A8BDF1",
136
+ 500: "#84A2EE",
137
+ 600: "#587EEA",
138
+ 700: "#2C4EE5",
139
+ 800: "#243D93",
140
+ 850: "#1D2F65",
141
+ 900: "#162241",
142
+ 950: "#0F1629",
143
+ 1e3: "#080B15"
144
+ };
71
145
 
72
146
  // src/recipe.ts
73
- var THEME_BASES = ["base", "muted", "relief", "street", "outdoor"];
147
+ var THEME_BASES = ["base", "muted", "outdoor", "decimal", "blueprint"];
148
+ var RETIRED_BASES = {
149
+ relief: "outdoor",
150
+ street: "base"
151
+ };
152
+ function normalizeThemeBase(base) {
153
+ if (THEME_BASES.includes(base)) return base;
154
+ return RETIRED_BASES[base] ?? null;
155
+ }
74
156
  var MODES = ["light", "dark"];
75
157
  function makeFlavor(r) {
76
158
  return {
@@ -158,61 +240,73 @@ function makeFlavor(r) {
158
240
  }
159
241
 
160
242
  // src/recipes.ts
161
- var STOCK_RAMPS = { ink, wave, verdant, sandstone, graphite, ember };
243
+ var STOCK_RAMPS = {
244
+ ink,
245
+ paper,
246
+ wave,
247
+ sky,
248
+ verdant,
249
+ sandstone,
250
+ graphite,
251
+ mist,
252
+ jade,
253
+ cobalt,
254
+ ember
255
+ };
162
256
  function buildRecipes(r) {
163
257
  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],
258
+ background: r.paper[100],
259
+ earth: r.paper[25],
260
+ water: r.sky[200],
261
+ parkA: r.verdant[300],
262
+ parkB: r.verdant[400],
263
+ woodA: r.verdant[300],
264
+ woodB: r.verdant[400],
171
265
  scrubA: r.verdant[200],
172
266
  scrubB: r.verdant[300],
173
267
  sand: r.sandstone[200],
174
268
  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],
269
+ glacier: r.paper[25],
270
+ institutional: r.paper[100],
271
+ pedestrian: r.paper[100],
272
+ aerodrome: r.cobalt[200],
273
+ runway: r.cobalt[300],
274
+ pier: r.paper[300],
275
+ buildings: r.paper[200],
276
+ railway: r.graphite[400],
277
+ boundaries: r.cobalt[600],
184
278
  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],
279
+ other: r.paper[100],
280
+ otherCasing: r.graphite[200],
281
+ minor: "#FFFFFF",
282
+ minorCasing: r.graphite[200],
283
+ service: r.paper[100],
284
+ serviceCasing: r.graphite[200],
191
285
  major: "#FFFFFF",
192
- majorCasing: r.ink[400],
193
- highway: "#FFFFFF",
194
- highwayCasing: r.ink[500],
195
- link: "#FFFFFF",
196
- linkCasing: r.ink[400]
286
+ majorCasing: r.graphite[300],
287
+ highway: r.ember[400],
288
+ highwayCasing: r.ember[600],
289
+ link: r.ember[300],
290
+ linkCasing: r.ember[500]
197
291
  },
198
- tunnelFill: r.ink[200],
199
- tunnelCasing: r.ink[300],
200
- bridgeCasing: r.ink[500],
292
+ tunnelFill: r.paper[200],
293
+ tunnelCasing: r.graphite[200],
294
+ bridgeCasing: r.graphite[400],
201
295
  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]
296
+ city: r.paper[1e3],
297
+ cityHalo: r.paper[25],
298
+ state: r.paper[700],
299
+ stateHalo: r.paper[25],
300
+ country: r.paper[700],
301
+ subplace: r.paper[700],
302
+ subplaceHalo: r.paper[25],
303
+ address: r.paper[700],
304
+ addressHalo: r.paper[25],
305
+ roadMinor: r.paper[800],
306
+ roadMinorHalo: r.paper[25],
307
+ roadMajor: r.paper[900],
308
+ roadMajorHalo: r.paper[25],
309
+ ocean: r.sky[800]
216
310
  },
217
311
  pois: {
218
312
  blue: "#2D6E8F",
@@ -220,73 +314,73 @@ function buildRecipes(r) {
220
314
  lapis: "#3F5C93",
221
315
  pink: "#8F4A6B",
222
316
  red: "#9B4340",
223
- slategray: r.ink[700],
317
+ slategray: r.paper[700],
224
318
  tangerine: "#8F5322",
225
- turquoise: r.wave[700]
319
+ turquoise: r.sky[700]
226
320
  },
227
321
  landcover: {
228
322
  grassland: r.verdant[200],
229
323
  barren: r.sandstone[200],
230
- urban_area: r.ink[50],
324
+ urban_area: r.paper[50],
231
325
  farmland: r.verdant[100],
232
- glacier: r.ink[25],
233
- scrub: r.verdant[100],
234
- forest: r.verdant[300]
326
+ glacier: r.paper[25],
327
+ scrub: r.verdant[200],
328
+ forest: r.verdant[400]
235
329
  }
236
330
  };
237
331
  const BASE_DARK = {
238
- background: r.ink[950],
239
- earth: r.ink[900],
240
- water: r.wave[980],
332
+ background: r.paper[950],
333
+ earth: r.paper[900],
334
+ water: r.sky[980],
241
335
  parkA: r.verdant[800],
242
336
  parkB: r.verdant[700],
243
337
  woodA: r.verdant[800],
244
338
  woodB: r.verdant[700],
245
- scrubA: r.verdant[800],
246
- scrubB: r.verdant[700],
339
+ scrubA: r.verdant[900],
340
+ scrubB: r.verdant[800],
247
341
  sand: r.sandstone[500],
248
342
  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],
343
+ glacier: r.paper[700],
344
+ institutional: r.paper[950],
345
+ pedestrian: r.paper[950],
346
+ aerodrome: r.cobalt[900],
347
+ runway: r.cobalt[850],
348
+ pier: r.paper[850],
349
+ buildings: r.paper[850],
350
+ railway: r.paper[700],
351
+ boundaries: r.cobalt[500],
258
352
  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]
353
+ other: r.paper[850],
354
+ otherCasing: r.paper[1e3],
355
+ minor: r.paper[800],
356
+ minorCasing: r.paper[1e3],
357
+ service: r.paper[850],
358
+ serviceCasing: r.paper[1e3],
359
+ major: r.paper[700],
360
+ majorCasing: r.paper[1e3],
361
+ highway: r.ember[600],
362
+ highwayCasing: r.paper[1e3],
363
+ link: r.ember[700],
364
+ linkCasing: r.paper[1e3]
271
365
  },
272
- tunnelFill: r.ink[900],
273
- tunnelCasing: r.ink[1e3],
274
- bridgeCasing: r.ink[1e3],
366
+ tunnelFill: r.paper[900],
367
+ tunnelCasing: r.paper[1e3],
368
+ bridgeCasing: r.paper[1e3],
275
369
  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]
370
+ city: r.paper[100],
371
+ cityHalo: r.paper[1e3],
372
+ state: r.paper[500],
373
+ stateHalo: r.paper[1e3],
374
+ country: r.paper[500],
375
+ subplace: r.paper[500],
376
+ subplaceHalo: r.paper[1e3],
377
+ address: r.paper[500],
378
+ addressHalo: r.paper[1e3],
379
+ roadMinor: r.paper[500],
380
+ roadMinorHalo: r.paper[1e3],
381
+ roadMajor: r.paper[400],
382
+ roadMajorHalo: r.paper[1e3],
383
+ ocean: r.sky[400]
290
384
  },
291
385
  pois: {
292
386
  blue: "#7FC4E8",
@@ -294,35 +388,35 @@ function buildRecipes(r) {
294
388
  lapis: "#8FB0EA",
295
389
  pink: "#E3A0C0",
296
390
  red: "#EE968F",
297
- slategray: r.ink[500],
391
+ slategray: r.paper[500],
298
392
  tangerine: "#E8AC7C",
299
- turquoise: r.wave[400]
393
+ turquoise: r.sky[400]
300
394
  },
301
395
  landcover: {
302
396
  grassland: r.verdant[800],
303
397
  barren: r.sandstone[500],
304
- urban_area: r.ink[950],
398
+ urban_area: r.paper[950],
305
399
  farmland: r.verdant[900],
306
- glacier: r.ink[700],
400
+ glacier: r.paper[700],
307
401
  scrub: r.verdant[900],
308
402
  forest: r.verdant[700]
309
403
  }
310
404
  };
311
405
  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],
406
+ background: r.graphite[100],
407
+ earth: r.graphite[25],
408
+ water: r.mist[200],
409
+ parkA: r.graphite[50],
410
+ parkB: r.graphite[100],
411
+ woodA: r.graphite[50],
412
+ woodB: r.graphite[100],
413
+ scrubA: r.graphite[50],
414
+ scrubB: r.graphite[100],
321
415
  sand: r.graphite[100],
322
416
  beach: r.graphite[100],
323
417
  glacier: r.graphite[25],
324
418
  institutional: r.graphite[100],
325
- pedestrian: r.graphite[25],
419
+ pedestrian: r.graphite[50],
326
420
  aerodrome: r.graphite[100],
327
421
  runway: r.graphite[200],
328
422
  pier: r.graphite[200],
@@ -338,14 +432,14 @@ function buildRecipes(r) {
338
432
  serviceCasing: r.graphite[200],
339
433
  major: "#FFFFFF",
340
434
  majorCasing: r.graphite[300],
341
- highway: "#FFFFFF",
435
+ highway: r.graphite[200],
342
436
  highwayCasing: r.graphite[300],
343
437
  link: "#FFFFFF",
344
438
  linkCasing: r.graphite[300]
345
439
  },
346
440
  tunnelFill: r.graphite[100],
347
441
  tunnelCasing: r.graphite[200],
348
- bridgeCasing: r.graphite[300],
442
+ bridgeCasing: r.graphite[400],
349
443
  labels: {
350
444
  city: r.graphite[1e3],
351
445
  cityHalo: r.graphite[25],
@@ -375,7 +469,7 @@ function buildRecipes(r) {
375
469
  landcover: {
376
470
  grassland: r.graphite[50],
377
471
  barren: r.graphite[100],
378
- urban_area: r.graphite[100],
472
+ urban_area: r.graphite[50],
379
473
  farmland: r.graphite[50],
380
474
  glacier: r.graphite[25],
381
475
  scrub: r.graphite[50],
@@ -385,7 +479,7 @@ function buildRecipes(r) {
385
479
  const MUTED_DARK = {
386
480
  background: r.graphite[1e3],
387
481
  earth: r.graphite[900],
388
- water: r.graphite[1e3],
482
+ water: r.mist[980],
389
483
  parkA: r.graphite[900],
390
484
  parkB: r.graphite[800],
391
485
  woodA: r.graphite[900],
@@ -456,44 +550,45 @@ function buildRecipes(r) {
456
550
  forest: r.graphite[800]
457
551
  }
458
552
  };
459
- const RELIEF_LIGHT = {
553
+ const OUTDOOR_LIGHT = {
460
554
  background: r.ink[200],
461
555
  earth: r.ink[50],
462
556
  water: r.wave[300],
463
- parkA: r.verdant[200],
464
- parkB: r.verdant[300],
465
- woodA: r.verdant[200],
466
- woodB: r.verdant[300],
557
+ parkA: r.verdant[300],
558
+ parkB: r.verdant[400],
559
+ woodA: r.verdant[300],
560
+ woodB: r.verdant[400],
467
561
  scrubA: r.verdant[200],
468
562
  scrubB: r.verdant[300],
469
- sand: r.sandstone[200],
470
- beach: r.sandstone[200],
563
+ sand: r.sandstone[300],
564
+ beach: r.sandstone[300],
471
565
  glacier: r.ink[25],
472
566
  institutional: r.ink[25],
473
567
  pedestrian: r.ink[50],
474
568
  aerodrome: r.ink[50],
475
569
  runway: r.ink[200],
476
570
  pier: r.ink[300],
477
- buildings: r.ink[300],
571
+ // A step lighter than `base`: outdoors, the building footprint is context, not content.
572
+ buildings: r.ink[200],
478
573
  railway: r.ink[500],
479
574
  boundaries: r.ink[600],
480
575
  roads: {
481
576
  other: r.ink[50],
482
- otherCasing: r.ink[300],
577
+ otherCasing: r.ink[200],
483
578
  minor: r.ink[25],
484
- minorCasing: r.ink[300],
579
+ minorCasing: r.ink[200],
485
580
  service: r.ink[50],
486
- serviceCasing: r.ink[300],
581
+ serviceCasing: r.ink[200],
487
582
  major: "#FFFFFF",
488
- majorCasing: r.ink[400],
583
+ majorCasing: r.ink[300],
489
584
  highway: "#FFFFFF",
490
- highwayCasing: r.ink[500],
585
+ highwayCasing: r.ink[400],
491
586
  link: "#FFFFFF",
492
- linkCasing: r.ink[400]
587
+ linkCasing: r.ink[300]
493
588
  },
494
589
  tunnelFill: r.ink[200],
495
590
  tunnelCasing: r.ink[300],
496
- bridgeCasing: r.ink[500],
591
+ bridgeCasing: r.ink[400],
497
592
  labels: {
498
593
  city: r.ink[1e3],
499
594
  cityHalo: r.ink[25],
@@ -512,7 +607,9 @@ function buildRecipes(r) {
512
607
  },
513
608
  pois: {
514
609
  blue: "#2D6E8F",
515
- green: r.verdant[700],
610
+ // Peaks, parks, forests and gardens all come through green. On this flavour they are the
611
+ // POIs worth finding, so it carries a full step more weight than elsewhere.
612
+ green: r.verdant[800],
516
613
  lapis: "#3F5C93",
517
614
  pink: "#8F4A6B",
518
615
  red: "#9B4340",
@@ -522,22 +619,22 @@ function buildRecipes(r) {
522
619
  },
523
620
  landcover: {
524
621
  grassland: r.verdant[200],
525
- barren: r.sandstone[200],
526
- urban_area: r.ink[50],
527
- farmland: r.verdant[100],
622
+ barren: r.sandstone[300],
623
+ urban_area: r.ink[100],
624
+ farmland: r.sandstone[200],
528
625
  glacier: r.ink[25],
529
- scrub: r.verdant[100],
530
- forest: r.verdant[300]
626
+ scrub: r.verdant[300],
627
+ forest: r.verdant[400]
531
628
  }
532
629
  };
533
- const RELIEF_DARK = {
630
+ const OUTDOOR_DARK = {
534
631
  background: r.ink[1e3],
535
632
  earth: r.ink[950],
536
633
  water: r.wave[980],
537
- parkA: r.verdant[800],
538
- parkB: r.verdant[700],
539
- woodA: r.verdant[800],
540
- woodB: r.verdant[700],
634
+ parkA: r.verdant[700],
635
+ parkB: r.verdant[600],
636
+ woodA: r.verdant[700],
637
+ woodB: r.verdant[600],
541
638
  scrubA: r.verdant[800],
542
639
  scrubB: r.verdant[700],
543
640
  sand: r.sandstone[500],
@@ -552,11 +649,13 @@ function buildRecipes(r) {
552
649
  railway: r.ink[700],
553
650
  boundaries: r.ink[600],
554
651
  roads: {
555
- other: r.ink[850],
652
+ // The minor tier recedes toward `earth`; majors and highways hold their contrast,
653
+ // because a road you cannot see is a different failure from a road that is merely quiet.
654
+ other: r.ink[900],
556
655
  otherCasing: r.ink[1e3],
557
- minor: r.ink[800],
656
+ minor: r.ink[850],
558
657
  minorCasing: r.ink[1e3],
559
- service: r.ink[850],
658
+ service: r.ink[900],
560
659
  serviceCasing: r.ink[1e3],
561
660
  major: r.ink[700],
562
661
  majorCasing: r.ink[1e3],
@@ -586,7 +685,7 @@ function buildRecipes(r) {
586
685
  },
587
686
  pois: {
588
687
  blue: "#7FC4E8",
589
- green: r.verdant[400],
688
+ green: r.verdant[300],
590
689
  lapis: "#8FB0EA",
591
690
  pink: "#E3A0C0",
592
691
  red: "#EE968F",
@@ -595,322 +694,317 @@ function buildRecipes(r) {
595
694
  turquoise: r.wave[400]
596
695
  },
597
696
  landcover: {
598
- grassland: r.verdant[800],
697
+ grassland: r.verdant[700],
599
698
  barren: r.sandstone[500],
600
699
  urban_area: r.ink[950],
601
700
  farmland: r.verdant[900],
602
701
  glacier: r.ink[700],
603
- scrub: r.verdant[900],
604
- forest: r.verdant[700]
702
+ scrub: r.verdant[800],
703
+ forest: r.verdant[600]
605
704
  }
606
705
  };
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],
706
+ const DECIMAL_LIGHT = {
707
+ background: r.jade[100],
708
+ earth: r.jade[25],
709
+ water: r.jade[200],
710
+ parkA: r.jade[50],
711
+ parkB: r.jade[100],
712
+ woodA: r.jade[50],
713
+ woodB: r.jade[100],
714
+ scrubA: r.jade[50],
715
+ scrubB: r.jade[100],
716
+ sand: r.jade[50],
717
+ beach: r.jade[50],
718
+ glacier: r.jade[25],
719
+ institutional: r.jade[50],
720
+ pedestrian: r.jade[50],
721
+ aerodrome: r.jade[50],
722
+ runway: r.jade[200],
723
+ pier: r.jade[200],
724
+ buildings: r.jade[100],
725
+ railway: r.jade[500],
726
+ boundaries: r.jade[600],
628
727
  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]
728
+ other: r.jade[200],
729
+ otherCasing: r.jade[25],
730
+ minor: r.jade[300],
731
+ minorCasing: r.jade[25],
732
+ service: r.jade[200],
733
+ serviceCasing: r.jade[25],
734
+ major: r.jade[400],
735
+ majorCasing: r.jade[25],
736
+ highway: r.jade[500],
737
+ highwayCasing: r.jade[25],
738
+ link: r.jade[400],
739
+ linkCasing: r.jade[25]
641
740
  },
642
- tunnelFill: r.ink[200],
643
- tunnelCasing: r.ink[300],
644
- bridgeCasing: r.ink[500],
741
+ tunnelFill: r.jade[100],
742
+ tunnelCasing: r.jade[200],
743
+ bridgeCasing: r.jade[600],
645
744
  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]
745
+ city: r.jade[1e3],
746
+ cityHalo: r.jade[25],
747
+ state: r.jade[700],
748
+ stateHalo: r.jade[25],
749
+ country: r.jade[700],
750
+ subplace: r.jade[700],
751
+ subplaceHalo: r.jade[25],
752
+ address: r.jade[700],
753
+ addressHalo: r.jade[25],
754
+ roadMinor: r.jade[700],
755
+ roadMinorHalo: r.jade[25],
756
+ roadMajor: r.jade[800],
757
+ roadMajorHalo: r.jade[25],
758
+ ocean: r.jade[800]
660
759
  },
661
760
  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]
761
+ blue: r.jade[700],
762
+ green: r.jade[700],
763
+ lapis: r.jade[700],
764
+ pink: r.jade[700],
765
+ red: r.jade[700],
766
+ slategray: r.jade[700],
767
+ tangerine: r.jade[700],
768
+ turquoise: r.jade[700]
670
769
  },
671
770
  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]
771
+ grassland: r.jade[50],
772
+ barren: r.jade[50],
773
+ urban_area: r.jade[50],
774
+ farmland: r.jade[50],
775
+ glacier: r.jade[25],
776
+ scrub: r.jade[50],
777
+ forest: r.jade[100]
679
778
  }
680
779
  };
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],
780
+ const DECIMAL_DARK = {
781
+ background: r.jade[1e3],
782
+ earth: r.jade[1e3],
783
+ water: r.jade[900],
784
+ parkA: r.jade[950],
785
+ parkB: r.jade[900],
786
+ woodA: r.jade[950],
787
+ woodB: r.jade[900],
788
+ scrubA: r.jade[950],
789
+ scrubB: r.jade[900],
790
+ sand: r.jade[950],
791
+ beach: r.jade[950],
792
+ glacier: r.jade[900],
793
+ institutional: r.jade[950],
794
+ pedestrian: r.jade[950],
795
+ aerodrome: r.jade[950],
796
+ runway: r.jade[850],
797
+ pier: r.jade[850],
798
+ buildings: r.jade[900],
799
+ railway: r.jade[800],
800
+ boundaries: r.jade[700],
702
801
  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]
802
+ other: r.jade[850],
803
+ otherCasing: r.jade[1e3],
804
+ minor: r.jade[800],
805
+ minorCasing: r.jade[1e3],
806
+ service: r.jade[850],
807
+ serviceCasing: r.jade[1e3],
808
+ major: r.jade[700],
809
+ majorCasing: r.jade[1e3],
810
+ highway: r.jade[600],
811
+ highwayCasing: r.jade[1e3],
812
+ link: r.jade[700],
813
+ linkCasing: r.jade[1e3]
715
814
  },
716
- tunnelFill: r.ink[900],
717
- tunnelCasing: r.ink[1e3],
718
- bridgeCasing: r.ink[1e3],
815
+ tunnelFill: r.jade[900],
816
+ tunnelCasing: r.jade[1e3],
817
+ bridgeCasing: r.jade[1e3],
719
818
  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]
819
+ city: r.jade[500],
820
+ cityHalo: r.jade[1e3],
821
+ state: r.jade[600],
822
+ stateHalo: r.jade[1e3],
823
+ country: r.jade[600],
824
+ subplace: r.jade[600],
825
+ subplaceHalo: r.jade[1e3],
826
+ address: r.jade[600],
827
+ addressHalo: r.jade[1e3],
828
+ roadMinor: r.jade[600],
829
+ roadMinorHalo: r.jade[1e3],
830
+ roadMajor: r.jade[500],
831
+ roadMajorHalo: r.jade[1e3],
832
+ ocean: r.jade[500]
734
833
  },
735
834
  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]
835
+ blue: r.jade[600],
836
+ green: r.jade[600],
837
+ lapis: r.jade[600],
838
+ pink: r.jade[600],
839
+ red: r.jade[600],
840
+ slategray: r.jade[600],
841
+ tangerine: r.jade[600],
842
+ turquoise: r.jade[600]
744
843
  },
745
844
  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]
845
+ grassland: r.jade[950],
846
+ barren: r.jade[950],
847
+ urban_area: r.jade[950],
848
+ farmland: r.jade[950],
849
+ glacier: r.jade[900],
850
+ scrub: r.jade[950],
851
+ forest: r.jade[900]
753
852
  }
754
853
  };
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],
854
+ const BLUEPRINT_LIGHT = {
855
+ background: r.cobalt[100],
856
+ earth: r.cobalt[25],
857
+ water: r.cobalt[200],
858
+ parkA: r.cobalt[50],
859
+ parkB: r.cobalt[100],
860
+ woodA: r.cobalt[50],
861
+ woodB: r.cobalt[100],
862
+ scrubA: r.cobalt[50],
863
+ scrubB: r.cobalt[100],
864
+ sand: r.cobalt[50],
865
+ beach: r.cobalt[50],
866
+ glacier: r.cobalt[25],
867
+ institutional: r.cobalt[50],
868
+ pedestrian: r.cobalt[50],
869
+ aerodrome: r.cobalt[50],
870
+ runway: r.cobalt[200],
871
+ pier: r.cobalt[200],
872
+ buildings: r.cobalt[100],
873
+ railway: r.cobalt[500],
874
+ boundaries: r.cobalt[600],
777
875
  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]
876
+ other: r.cobalt[200],
877
+ otherCasing: r.cobalt[25],
878
+ minor: r.cobalt[300],
879
+ minorCasing: r.cobalt[25],
880
+ service: r.cobalt[200],
881
+ serviceCasing: r.cobalt[25],
882
+ major: r.cobalt[400],
883
+ majorCasing: r.cobalt[25],
884
+ highway: r.cobalt[500],
885
+ highwayCasing: r.cobalt[25],
886
+ link: r.cobalt[400],
887
+ linkCasing: r.cobalt[25]
790
888
  },
791
- tunnelFill: r.ink[200],
792
- tunnelCasing: r.ink[300],
793
- bridgeCasing: r.ink[400],
889
+ tunnelFill: r.cobalt[100],
890
+ tunnelCasing: r.cobalt[200],
891
+ bridgeCasing: r.cobalt[600],
794
892
  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]
893
+ city: r.cobalt[1e3],
894
+ cityHalo: r.cobalt[25],
895
+ state: r.cobalt[700],
896
+ stateHalo: r.cobalt[25],
897
+ country: r.cobalt[700],
898
+ subplace: r.cobalt[700],
899
+ subplaceHalo: r.cobalt[25],
900
+ address: r.cobalt[700],
901
+ addressHalo: r.cobalt[25],
902
+ roadMinor: r.cobalt[700],
903
+ roadMinorHalo: r.cobalt[25],
904
+ roadMajor: r.cobalt[800],
905
+ roadMajorHalo: r.cobalt[25],
906
+ ocean: r.cobalt[800]
809
907
  },
810
908
  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]
909
+ blue: r.cobalt[700],
910
+ green: r.cobalt[700],
911
+ lapis: r.cobalt[700],
912
+ pink: r.cobalt[700],
913
+ red: r.cobalt[700],
914
+ slategray: r.cobalt[700],
915
+ tangerine: r.cobalt[700],
916
+ turquoise: r.cobalt[700]
821
917
  },
822
918
  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]
919
+ grassland: r.cobalt[50],
920
+ barren: r.cobalt[50],
921
+ urban_area: r.cobalt[50],
922
+ farmland: r.cobalt[50],
923
+ glacier: r.cobalt[25],
924
+ scrub: r.cobalt[50],
925
+ forest: r.cobalt[100]
830
926
  }
831
927
  };
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],
928
+ const BLUEPRINT_DARK = {
929
+ background: r.cobalt[700],
930
+ earth: r.cobalt[700],
931
+ water: r.cobalt[400],
932
+ parkA: r.cobalt[600],
933
+ parkB: r.cobalt[600],
934
+ woodA: r.cobalt[600],
935
+ woodB: r.cobalt[600],
936
+ scrubA: r.cobalt[600],
937
+ scrubB: r.cobalt[600],
938
+ sand: r.cobalt[600],
939
+ beach: r.cobalt[600],
940
+ glacier: r.cobalt[500],
941
+ institutional: r.cobalt[600],
942
+ pedestrian: r.cobalt[700],
943
+ aerodrome: r.cobalt[600],
944
+ runway: r.cobalt[800],
945
+ pier: r.cobalt[800],
946
+ buildings: r.cobalt[600],
947
+ railway: r.cobalt[400],
948
+ boundaries: r.cobalt[300],
853
949
  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]
950
+ other: r.cobalt[800],
951
+ otherCasing: r.cobalt[700],
952
+ minor: r.cobalt[850],
953
+ minorCasing: r.cobalt[600],
954
+ service: r.cobalt[800],
955
+ serviceCasing: r.cobalt[700],
956
+ major: r.cobalt[900],
957
+ majorCasing: r.cobalt[600],
958
+ highway: r.cobalt[950],
959
+ highwayCasing: r.cobalt[500],
960
+ link: r.cobalt[900],
961
+ linkCasing: r.cobalt[600]
868
962
  },
869
- tunnelFill: r.ink[900],
870
- tunnelCasing: r.ink[1e3],
871
- bridgeCasing: r.ink[1e3],
963
+ tunnelFill: r.cobalt[800],
964
+ tunnelCasing: r.cobalt[700],
965
+ bridgeCasing: r.cobalt[500],
872
966
  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]
967
+ city: r.cobalt[25],
968
+ cityHalo: r.cobalt[700],
969
+ state: r.cobalt[200],
970
+ stateHalo: r.cobalt[700],
971
+ country: r.cobalt[200],
972
+ subplace: r.cobalt[300],
973
+ subplaceHalo: r.cobalt[700],
974
+ address: r.cobalt[300],
975
+ addressHalo: r.cobalt[700],
976
+ roadMinor: r.cobalt[300],
977
+ roadMinorHalo: r.cobalt[700],
978
+ roadMajor: r.cobalt[100],
979
+ roadMajorHalo: r.cobalt[700],
980
+ ocean: r.cobalt[900]
887
981
  },
888
982
  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]
983
+ blue: r.cobalt[200],
984
+ green: r.cobalt[200],
985
+ lapis: r.cobalt[200],
986
+ pink: r.cobalt[200],
987
+ red: r.cobalt[200],
988
+ slategray: r.cobalt[200],
989
+ tangerine: r.cobalt[200],
990
+ turquoise: r.cobalt[200]
897
991
  },
898
992
  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]
993
+ grassland: r.cobalt[600],
994
+ barren: r.cobalt[600],
995
+ urban_area: r.cobalt[700],
996
+ farmland: r.cobalt[600],
997
+ glacier: r.cobalt[500],
998
+ scrub: r.cobalt[600],
999
+ forest: r.cobalt[600]
906
1000
  }
907
1001
  };
908
1002
  return {
909
1003
  base: { light: BASE_LIGHT, dark: BASE_DARK },
910
1004
  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 }
1005
+ outdoor: { light: OUTDOOR_LIGHT, dark: OUTDOOR_DARK },
1006
+ decimal: { light: DECIMAL_LIGHT, dark: DECIMAL_DARK },
1007
+ blueprint: { light: BLUEPRINT_LIGHT, dark: BLUEPRINT_DARK }
914
1008
  };
915
1009
  }
916
1010
  var RECIPES = buildRecipes(STOCK_RAMPS);
@@ -990,7 +1084,8 @@ function validateTheme(input) {
990
1084
  if (!DOC_KEYS.includes(key)) errors.push(`${key}: unknown field`);
991
1085
  }
992
1086
  if (input.v !== 1) errors.push("v: must be 1");
993
- if (typeof input.base !== "string" || !THEME_BASES.includes(input.base)) {
1087
+ const base = typeof input.base === "string" ? normalizeThemeBase(input.base) : null;
1088
+ if (!base) {
994
1089
  errors.push(`base: must be one of ${THEME_BASES.join(", ")}`);
995
1090
  }
996
1091
  if (input.name !== void 0) {
@@ -1028,85 +1123,73 @@ function validateTheme(input) {
1028
1123
  }
1029
1124
  }
1030
1125
  if (errors.length > 0) throw new ThemeError("invalid theme", errors);
1126
+ if (base && base !== input.base) return { ...input, base };
1031
1127
  return input;
1032
1128
  }
1033
1129
 
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));
1130
+ // src/presets.ts
1131
+ var STYLE_THEMES = {
1132
+ base: { v: 1, name: "base", base: "base" },
1133
+ muted: { v: 1, name: "muted", base: "muted" },
1134
+ outdoor: { v: 1, name: "outdoor", base: "outdoor" },
1135
+ decimal: { v: 1, name: "decimal", base: "decimal" },
1136
+ blueprint: { v: 1, name: "blueprint", base: "blueprint" }
1071
1137
  };
1072
- async function through(bytes, transform, cap) {
1073
- const stream = new Blob([bytes]).stream().pipeThrough(transform);
1074
- return pump(stream, cap);
1138
+
1139
+ // src/oklch.ts
1140
+ var srgbToLinear = (v) => v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
1141
+ var linearToSrgb = (v) => v <= 31308e-7 ? 12.92 * v : 1.055 * v ** (1 / 2.4) - 0.055;
1142
+ function hexToRgb(hex) {
1143
+ const h = hex.replace("#", "");
1144
+ return [
1145
+ parseInt(h.slice(0, 2), 16) / 255,
1146
+ parseInt(h.slice(2, 4), 16) / 255,
1147
+ parseInt(h.slice(4, 6), 16) / 255
1148
+ ];
1075
1149
  }
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;
1150
+ function hexToOklch(hex) {
1151
+ const [r, g, b] = hexToRgb(hex).map(srgbToLinear);
1152
+ const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
1153
+ const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
1154
+ const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
1155
+ const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
1156
+ const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
1157
+ const bb = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
1158
+ const c = Math.hypot(a, bb);
1159
+ let h = Math.atan2(bb, a) * 180 / Math.PI;
1160
+ if (h < 0) h += 360;
1161
+ return { l: L, c, h };
1087
1162
  }
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"]);
1163
+ function oklchToLinear({ l: L, c, h }) {
1164
+ const hr = h * Math.PI / 180;
1165
+ const a = c * Math.cos(hr);
1166
+ const bb = c * Math.sin(hr);
1167
+ const l = (L + 0.3963377774 * a + 0.2158037573 * bb) ** 3;
1168
+ const m = (L - 0.1055613458 * a - 0.0638541728 * bb) ** 3;
1169
+ const s = (L - 0.0894841775 * a - 1.291485548 * bb) ** 3;
1170
+ return [
1171
+ 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
1172
+ -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
1173
+ -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
1174
+ ];
1175
+ }
1176
+ var inGamut = (rgb) => rgb.every((v) => v >= -1e-6 && v <= 1 + 1e-6);
1177
+ function oklchToHex(color) {
1178
+ let { c } = color;
1179
+ let rgb = oklchToLinear({ ...color, c });
1180
+ if (!inGamut(rgb)) {
1181
+ let lo = 0;
1182
+ let hi = c;
1183
+ for (let i = 0; i < 24; i++) {
1184
+ c = (lo + hi) / 2;
1185
+ rgb = oklchToLinear({ ...color, c });
1186
+ if (inGamut(rgb)) lo = c;
1187
+ else hi = c;
1188
+ }
1189
+ rgb = oklchToLinear({ ...color, c: lo });
1108
1190
  }
1109
- return validateTheme(parsed);
1191
+ const to255 = (v) => Math.round(Math.min(1, Math.max(0, linearToSrgb(v))) * 255);
1192
+ return "#" + rgb.map((v) => to255(v).toString(16).padStart(2, "0")).join("").toUpperCase();
1110
1193
  }
1111
1194
 
1112
1195
  // src/compose.ts
@@ -1206,75 +1289,11 @@ function contrastWarnings(doc) {
1206
1289
  return warnings;
1207
1290
  }
1208
1291
 
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
1292
  // src/generate.ts
1275
1293
  var NEUTRAL_CHROMA_CAP = 0.045;
1276
1294
  var MUTED_CHROMA_CAP = 0.012;
1277
1295
  var CHROMA_CAP = 0.32;
1296
+ var NEUTRAL_RAMP_CHROMA = 5e-3;
1278
1297
  var DEFAULT_SEEDS = {
1279
1298
  land: STOCK_RAMPS.ink[500],
1280
1299
  water: STOCK_RAMPS.wave[500],
@@ -1285,14 +1304,15 @@ function reRamp(ref, seedHex, cap) {
1285
1304
  const seed = hexToOklch(seedHex);
1286
1305
  const steps = Object.keys(ref);
1287
1306
  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);
1307
+ const midChroma = hexToOklch(ref[midStep]).c;
1308
+ const neutral = midChroma < NEUTRAL_RAMP_CHROMA;
1309
+ const factor = Math.min(Math.max(seed.c / Math.max(midChroma, 5e-3), 0.15), 3);
1290
1310
  const out = {};
1291
1311
  for (const step of steps) {
1292
1312
  const refColor = hexToOklch(ref[step]);
1293
1313
  out[step] = oklchToHex({
1294
1314
  l: refColor.l,
1295
- c: Math.min(refColor.c * factor, cap),
1315
+ c: Math.min(neutral ? seed.c : refColor.c * factor, cap),
1296
1316
  h: seed.h
1297
1317
  });
1298
1318
  }
@@ -1308,7 +1328,10 @@ function enforceLabelContrast(recipe) {
1308
1328
  if (contrast(label, bg) >= floor) break;
1309
1329
  const l = hexToOklch(label);
1310
1330
  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)) });
1331
+ const nudged = oklchToHex({
1332
+ ...l,
1333
+ l: Math.min(1, Math.max(0, l.l + direction * 0.015))
1334
+ });
1312
1335
  setLabelSlot(fixed, labelSlot, nudged);
1313
1336
  }
1314
1337
  }
@@ -1354,8 +1377,13 @@ function generateTheme(base, seeds) {
1354
1377
  const ramps = {
1355
1378
  ...STOCK_RAMPS,
1356
1379
  ink: reRamp(STOCK_RAMPS.ink, seeds.land, NEUTRAL_CHROMA_CAP),
1380
+ paper: reRamp(STOCK_RAMPS.paper, seeds.land, NEUTRAL_CHROMA_CAP),
1357
1381
  graphite: reRamp(STOCK_RAMPS.graphite, seeds.land, MUTED_CHROMA_CAP),
1382
+ jade: reRamp(STOCK_RAMPS.jade, seeds.land, CHROMA_CAP),
1383
+ cobalt: reRamp(STOCK_RAMPS.cobalt, seeds.land, CHROMA_CAP),
1358
1384
  wave: reRamp(STOCK_RAMPS.wave, seeds.water, CHROMA_CAP),
1385
+ sky: reRamp(STOCK_RAMPS.sky, seeds.water, CHROMA_CAP),
1386
+ mist: reRamp(STOCK_RAMPS.mist, seeds.water, CHROMA_CAP),
1359
1387
  verdant: reRamp(STOCK_RAMPS.verdant, seeds.nature, CHROMA_CAP)
1360
1388
  };
1361
1389
  const generated = buildRecipes(ramps);
@@ -1375,7 +1403,7 @@ function generateTheme(base, seeds) {
1375
1403
  }
1376
1404
 
1377
1405
  // src/palettes.ts
1378
- var SEED_PALETTES = [
1406
+ var THEME_PRESETS = [
1379
1407
  { name: "process ink", seeds: DEFAULT_SEEDS },
1380
1408
  {
1381
1409
  name: "boreal",
@@ -1416,6 +1444,274 @@ function shuffleSeeds(rng = Math.random) {
1416
1444
  accent: oklchToHex({ l: 0.65, c: 0.22 + rng() * 0.08, h: wrap(h + 170 + rng() * 40) })
1417
1445
  };
1418
1446
  }
1447
+
1448
+ // src/preset.ts
1449
+ var PRESET_CODE_PREFIX = "u";
1450
+ var PRESET_CODE_RE = /^u[0-9A-Za-z]{2,24}$/;
1451
+ var BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
1452
+ var SEED_KEYS = ["land", "water", "nature", "accent"];
1453
+ var STYLE_ENCODE = {
1454
+ base: 0,
1455
+ muted: 1,
1456
+ outdoor: 4,
1457
+ decimal: 5,
1458
+ blueprint: 6
1459
+ };
1460
+ var STYLE_DECODE = [
1461
+ "base",
1462
+ "muted",
1463
+ "outdoor",
1464
+ "base",
1465
+ "outdoor",
1466
+ "decimal",
1467
+ "blueprint",
1468
+ void 0
1469
+ ];
1470
+ var PRESET_OFFSET = 8n;
1471
+ var ORIGINAL_SLOTS = 5n;
1472
+ var ORIGINAL_PRESET_LIMIT = 64n;
1473
+ var LATER_PRESET_OFFSET = PRESET_OFFSET + ORIGINAL_PRESET_LIMIT * ORIGINAL_SLOTS;
1474
+ var LATER_SLOT_BASE = 5;
1475
+ var LATER_SLOTS = 3n;
1476
+ var CUSTOM_OFFSET = 1024n;
1477
+ var LATER_PRESET_LIMIT = (CUSTOM_OFFSET - LATER_PRESET_OFFSET) / LATER_SLOTS;
1478
+ var sameHex = (a, b) => a.toLowerCase() === b.toLowerCase();
1479
+ function isThemePresetCode(code) {
1480
+ return PRESET_CODE_RE.test(code);
1481
+ }
1482
+ function presetId(name) {
1483
+ return name.replace(/\s+([a-zA-Z])/g, (_, c) => c.toUpperCase()).replace(/\s/g, "");
1484
+ }
1485
+ function toBase62(n, min = 2) {
1486
+ if (n === 0n) return "0".repeat(min);
1487
+ let s = "";
1488
+ let x = n;
1489
+ while (x > 0n) {
1490
+ s = BASE62[Number(x % 62n)] + s;
1491
+ x /= 62n;
1492
+ }
1493
+ return s.padStart(min, "0");
1494
+ }
1495
+ function fromBase62(s) {
1496
+ let n = 0n;
1497
+ for (const ch of s) {
1498
+ const i = BASE62.indexOf(ch);
1499
+ if (i < 0) {
1500
+ throw new ThemeError("invalid theme", ["code is not a valid preset"]);
1501
+ }
1502
+ n = n * 62n + BigInt(i);
1503
+ }
1504
+ return n;
1505
+ }
1506
+ function rgb24(hex) {
1507
+ return BigInt(parseInt(hex.replace("#", "").slice(0, 6), 16));
1508
+ }
1509
+ function fromRgb24(n) {
1510
+ return `#${n.toString(16).toUpperCase().padStart(6, "0")}`;
1511
+ }
1512
+ function isBareStyle(doc) {
1513
+ return doc.seeds === void 0 && doc.light === void 0 && doc.dark === void 0;
1514
+ }
1515
+ function deepEqual(a, b) {
1516
+ if (a === b) return true;
1517
+ if (typeof a !== typeof b) return false;
1518
+ if (a && b && typeof a === "object") {
1519
+ const ak = Object.keys(a).sort();
1520
+ const bk = Object.keys(b).sort();
1521
+ return ak.length === bk.length && ak.every((k, i) => k === bk[i] && deepEqual(a[k], b[k]));
1522
+ }
1523
+ return false;
1524
+ }
1525
+ function seedsMatch(a, b) {
1526
+ return SEED_KEYS.every((k) => {
1527
+ const left = a[k];
1528
+ const right = b[k];
1529
+ if (left === void 0 && right === void 0) return true;
1530
+ if (typeof left !== "string" || typeof right !== "string") return left === right;
1531
+ return sameHex(left, right);
1532
+ });
1533
+ }
1534
+ function generatedMatch(doc) {
1535
+ if (!doc.seeds) return false;
1536
+ const generated = generateTheme(doc.base, doc.seeds);
1537
+ return generated.base === doc.base && seedsMatch(generated.seeds, doc.seeds) && deepEqual(generated.light, doc.light) && deepEqual(generated.dark, doc.dark);
1538
+ }
1539
+ function findPresetIndex(seeds) {
1540
+ return THEME_PRESETS.findIndex((p) => seedsMatch(p.seeds, { ...DEFAULT_SEEDS, ...seeds }));
1541
+ }
1542
+ function encodeBits(bits) {
1543
+ return PRESET_CODE_PREFIX + toBase62(bits);
1544
+ }
1545
+ function encodePreset(doc) {
1546
+ const slot = STYLE_ENCODE[doc.base];
1547
+ if (slot === void 0) return null;
1548
+ if (isBareStyle(doc)) {
1549
+ return encodeBits(BigInt(slot));
1550
+ }
1551
+ if (!doc.seeds || !generatedMatch(doc)) return null;
1552
+ const presetIndex = findPresetIndex(doc.seeds);
1553
+ if (presetIndex >= 0) {
1554
+ const p = BigInt(presetIndex);
1555
+ if (slot < LATER_SLOT_BASE) {
1556
+ if (p < ORIGINAL_PRESET_LIMIT) {
1557
+ return encodeBits(PRESET_OFFSET + p * ORIGINAL_SLOTS + BigInt(slot));
1558
+ }
1559
+ } else if (p < LATER_PRESET_LIMIT) {
1560
+ return encodeBits(LATER_PRESET_OFFSET + p * LATER_SLOTS + BigInt(slot - LATER_SLOT_BASE));
1561
+ }
1562
+ }
1563
+ if (!doc.seeds.accent) return null;
1564
+ const filled = { ...DEFAULT_SEEDS, ...doc.seeds };
1565
+ let bits = CUSTOM_OFFSET + BigInt(slot);
1566
+ let shift = 3n;
1567
+ for (const key of SEED_KEYS) {
1568
+ bits += rgb24(filled[key]) << shift;
1569
+ shift += 24n;
1570
+ }
1571
+ return encodeBits(bits);
1572
+ }
1573
+ function decodePreset(code) {
1574
+ if (!isThemePresetCode(code)) {
1575
+ throw new ThemeError("invalid theme", ["code is not a valid preset"]);
1576
+ }
1577
+ const bits = fromBase62(code.slice(PRESET_CODE_PREFIX.length));
1578
+ if (bits < PRESET_OFFSET) {
1579
+ const base2 = STYLE_DECODE[Number(bits)];
1580
+ if (!base2) {
1581
+ throw new ThemeError("invalid theme", ["preset style slot is out of range"]);
1582
+ }
1583
+ return structuredClone(STYLE_THEMES[base2]);
1584
+ }
1585
+ if (bits < CUSTOM_OFFSET) {
1586
+ let slot;
1587
+ let presetIndex;
1588
+ if (bits < LATER_PRESET_OFFSET) {
1589
+ const n = bits - PRESET_OFFSET;
1590
+ slot = Number(n % ORIGINAL_SLOTS);
1591
+ presetIndex = Number(n / ORIGINAL_SLOTS);
1592
+ } else {
1593
+ const n = bits - LATER_PRESET_OFFSET;
1594
+ slot = LATER_SLOT_BASE + Number(n % LATER_SLOTS);
1595
+ presetIndex = Number(n / LATER_SLOTS);
1596
+ }
1597
+ const base2 = STYLE_DECODE[slot];
1598
+ const preset = THEME_PRESETS[presetIndex];
1599
+ if (!base2 || !preset) {
1600
+ throw new ThemeError("invalid theme", ["preset is out of range"]);
1601
+ }
1602
+ return generateTheme(base2, preset.seeds);
1603
+ }
1604
+ const payload = bits - CUSTOM_OFFSET;
1605
+ const base = STYLE_DECODE[Number(payload & 7n)];
1606
+ if (!base) {
1607
+ throw new ThemeError("invalid theme", ["preset style slot is out of range"]);
1608
+ }
1609
+ let rest = payload >> 3n;
1610
+ const seeds = { ...DEFAULT_SEEDS };
1611
+ for (const key of SEED_KEYS) {
1612
+ seeds[key] = fromRgb24(rest & 0xffffffn);
1613
+ rest >>= 24n;
1614
+ }
1615
+ if (rest !== 0n) {
1616
+ throw new ThemeError("invalid theme", ["preset payload has unused bits"]);
1617
+ }
1618
+ return generateTheme(base, seeds);
1619
+ }
1620
+ function registryThemeKey(doc) {
1621
+ if (isBareStyle(doc)) return STYLE_THEMES[doc.base]?.name;
1622
+ if (!doc.seeds || doc.base !== "base" || !generatedMatch(doc)) return void 0;
1623
+ const presetIndex = findPresetIndex(doc.seeds);
1624
+ if (presetIndex < 0) return void 0;
1625
+ return presetId(THEME_PRESETS[presetIndex].name);
1626
+ }
1627
+
1628
+ // src/codec.ts
1629
+ var THEME_CODE_PREFIX = "ut1.";
1630
+ var MAX_CODE_LENGTH = 4096;
1631
+ var MAX_JSON_BYTES = 16 * 1024;
1632
+ async function pump(stream, cap) {
1633
+ const reader = stream.getReader();
1634
+ const chunks = [];
1635
+ let size = 0;
1636
+ for (; ; ) {
1637
+ const { done, value } = await reader.read();
1638
+ if (done) break;
1639
+ size += value.byteLength;
1640
+ if (size > cap) {
1641
+ await reader.cancel().catch(() => {
1642
+ });
1643
+ throw new ThemeError("invalid theme", ["theme document too large"]);
1644
+ }
1645
+ chunks.push(value);
1646
+ }
1647
+ const out = new Uint8Array(size);
1648
+ let off = 0;
1649
+ for (const c of chunks) {
1650
+ out.set(c, off);
1651
+ off += c.byteLength;
1652
+ }
1653
+ return out;
1654
+ }
1655
+ var toB64url = (b) => {
1656
+ let bin = "";
1657
+ for (let i = 0; i < b.length; i += 32768) {
1658
+ bin += String.fromCharCode(...b.subarray(i, i + 32768));
1659
+ }
1660
+ return btoa(bin).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
1661
+ };
1662
+ var fromB64url = (s) => {
1663
+ const bin = atob(s.replaceAll("-", "+").replaceAll("_", "/"));
1664
+ return Uint8Array.from(bin, (c) => c.charCodeAt(0));
1665
+ };
1666
+ async function through(bytes, transform, cap) {
1667
+ const stream = new Blob([bytes]).stream().pipeThrough(transform);
1668
+ return pump(stream, cap);
1669
+ }
1670
+ async function encodeTheme(doc) {
1671
+ const valid = validateTheme(doc);
1672
+ const preset = encodePreset(valid);
1673
+ if (preset) return preset;
1674
+ const json = new TextEncoder().encode(JSON.stringify(valid));
1675
+ const packed = await through(json, new CompressionStream("deflate"), Number.POSITIVE_INFINITY);
1676
+ const code = THEME_CODE_PREFIX + toB64url(packed);
1677
+ if (code.length > MAX_CODE_LENGTH) {
1678
+ throw new ThemeError("invalid theme", [
1679
+ `encoded theme exceeds ${MAX_CODE_LENGTH} characters`
1680
+ ]);
1681
+ }
1682
+ return code;
1683
+ }
1684
+ async function decodeTheme(code) {
1685
+ if (code.length > MAX_CODE_LENGTH) {
1686
+ throw new ThemeError("invalid theme", [`code exceeds ${MAX_CODE_LENGTH} characters`]);
1687
+ }
1688
+ if (code.startsWith(THEME_CODE_PREFIX)) {
1689
+ return decodeDeflate(code);
1690
+ }
1691
+ if (isThemePresetCode(code)) {
1692
+ return validateTheme(decodePreset(code));
1693
+ }
1694
+ throw new ThemeError("invalid theme", [
1695
+ `code must start with "${THEME_CODE_PREFIX}" or be a short preset`
1696
+ ]);
1697
+ }
1698
+ async function decodeDeflate(code) {
1699
+ let bytes;
1700
+ try {
1701
+ const packed = fromB64url(code.slice(THEME_CODE_PREFIX.length));
1702
+ bytes = await through(packed, new DecompressionStream("deflate"), MAX_JSON_BYTES);
1703
+ } catch (e) {
1704
+ if (e instanceof ThemeError) throw e;
1705
+ throw new ThemeError("invalid theme", ["code is not valid base64url deflate data"]);
1706
+ }
1707
+ let parsed;
1708
+ try {
1709
+ parsed = JSON.parse(new TextDecoder().decode(bytes));
1710
+ } catch {
1711
+ throw new ThemeError("invalid theme", ["decoded payload is not JSON"]);
1712
+ }
1713
+ return validateTheme(parsed);
1714
+ }
1419
1715
  export {
1420
1716
  DEFAULT_MARKER,
1421
1717
  DEFAULT_ROUTE,
@@ -1423,29 +1719,42 @@ export {
1423
1719
  LABEL_PAIRS,
1424
1720
  MAX_CODE_LENGTH,
1425
1721
  MODES,
1426
- PRESET_THEMES,
1722
+ PRESET_CODE_PREFIX,
1723
+ PRESET_CODE_RE,
1427
1724
  RECIPES,
1428
- SEED_PALETTES,
1429
1725
  STOCK_RAMPS,
1726
+ STYLE_THEMES,
1430
1727
  THEME_BASES,
1431
1728
  THEME_CODE_PREFIX,
1729
+ THEME_PRESETS,
1432
1730
  ThemeError,
1433
1731
  buildRecipes,
1732
+ cobalt,
1434
1733
  contrast,
1435
1734
  contrastWarnings,
1735
+ decodePreset,
1436
1736
  decodeTheme,
1437
1737
  ember,
1738
+ encodePreset,
1438
1739
  encodeTheme,
1439
1740
  generateTheme,
1440
1741
  graphite,
1441
1742
  hexToOklch,
1442
1743
  ink,
1744
+ isThemePresetCode,
1745
+ jade,
1443
1746
  makeFlavor,
1444
1747
  mergeRecipe,
1748
+ mist,
1749
+ normalizeThemeBase,
1445
1750
  oklchToHex,
1751
+ paper,
1752
+ presetId,
1753
+ registryThemeKey,
1446
1754
  resolveTheme,
1447
1755
  sandstone,
1448
1756
  shuffleSeeds,
1757
+ sky,
1449
1758
  validateTheme,
1450
1759
  verdant,
1451
1760
  wave