@unmap/themes 0.5.0 → 0.6.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.d.ts CHANGED
@@ -508,29 +508,42 @@ declare function validateTheme(input: unknown): ThemeDoc;
508
508
  */
509
509
  declare const THEME_CODE_PREFIX = "ut1.";
510
510
  declare const MAX_CODE_LENGTH = 4096;
511
- /** Validate and encode a theme document. Curated/simple themes become a short `u…` preset; anything else is `ut1.` + deflate. Throws `ThemeError`. */
511
+ /**
512
+ * Validate and encode a theme document. Curated/simple themes — a style, a named preset or
513
+ * custom seeds, with or without layer groups hidden or reordered — become a short `u…` preset;
514
+ * anything with hand-edited colours is `ut1.` + deflate. Throws `ThemeError`.
515
+ */
512
516
  declare function encodeTheme(doc: ThemeDoc): Promise<string>;
513
517
  /** Decode and validate a compact theme code (`u…` preset or `ut1.` document). Every failure lands as `ThemeError`. */
514
518
  declare function decodeTheme(code: string): Promise<ThemeDoc>;
515
519
 
516
520
  /**
517
- * Short preset codes, the same idea as shadcn's `b0` / `a2r6bw`: a versioned base62 packing of
521
+ * Short preset codes, the same idea as shadcn's `b0` / `a2r6bw`: a base62 packing of
518
522
  * enumerated choices that starts at three characters and grows only when the theme leaves the
519
523
  * curated set.
520
524
  *
521
- * u00, u01, u04 living styles from the original lineup (base, muted, outdoor)
522
- * u02, u03 retired styles (relief → outdoor, street → base)
523
- * u05, u06 the second wave (decimal, blueprint)
524
- * u5K, u5N, … the third wave (blush, orchid, canopy, lagoon) and any style after them
525
- * u08… named theme preset on one of the original style slots
526
- * u5L… named theme preset on a later style
527
- * longer custom seed colours (4× RGB) on any style
525
+ * u00 … u08 the nine styles, in lineup order
526
+ * u0W, u4e, … a named theme preset on a style (three characters for every pair)
527
+ * six to eight a style or preset with layer groups hidden or reordered
528
+ * up to 23 custom seed colours (4× RGB) on any style, layers included
528
529
  *
529
530
  * Advanced overrides — anything `generateTheme` would not emit — still use the `ut1.` deflate
530
- * document. `ut1.` is checked first on decode so an existing customer code never collides.
531
+ * document. `ut1.` is checked first on decode so the two forms never collide.
532
+ *
533
+ * Layout, low bits first, of the number the base62 digits spell:
534
+ *
535
+ * bits 0–4 style slot (`STYLE_SLOTS`; 32 styles)
536
+ * bits 5–6 kind: 0 bare style, 1 named preset, 2 custom seeds (3 unassigned)
537
+ * bit 7 layers flag
538
+ * bits 8… kind payload: preset → 8-bit preset index; custom → 4 × 24-bit RGB seeds
539
+ * then if the flag is set, 7 visibility bits (one per `LAYER_GROUPS` entry) followed
540
+ * by a 13-bit Lehmer index of the draw order (0 = stock order)
531
541
  *
532
- * Slot numbers are permanent once a code is published, which is why the layout below is a set
533
- * of appended regions rather than one formula: every code ever printed keeps decoding.
542
+ * Anything left above the last field is an error, so a truncated or padded code fails rather
543
+ * than decoding to a theme nobody asked for. Both lookup tables are append-only: a new style
544
+ * or preset goes on the end, so a code already printed keeps meaning the same theme.
545
+ * (The layout itself was re-cut on 2026-09-07 with no compatibility for the codes the earlier
546
+ * region-and-lane scheme printed; that is the one time it happens.)
534
547
  */
535
548
  declare const PRESET_CODE_PREFIX = "u";
536
549
  declare const PRESET_CODE_RE: RegExp;
package/dist/index.js CHANGED
@@ -2153,51 +2153,119 @@ function shuffleSeeds(rng = Math.random) {
2153
2153
  };
2154
2154
  }
2155
2155
 
2156
+ // src/layers.ts
2157
+ var DEFAULT_LAYER_VISIBILITY = {
2158
+ land: true,
2159
+ water: true,
2160
+ buildings: true,
2161
+ roads: true,
2162
+ boundaries: true,
2163
+ buildings3d: false,
2164
+ labels: true
2165
+ };
2166
+ var BUILDINGS_3D_LAYER_ID = "buildings_3d";
2167
+ var LAND_LINES = /* @__PURE__ */ new Set(["roads_runway", "roads_taxiway"]);
2168
+ function layerGroupOf(layer) {
2169
+ const { id, type } = layer;
2170
+ if (type === "symbol") return "labels";
2171
+ if (id === "background") return null;
2172
+ if (id === BUILDINGS_3D_LAYER_ID) return "buildings3d";
2173
+ if (id === "buildings") return "buildings";
2174
+ if (id.startsWith("water")) return "water";
2175
+ if (id.startsWith("boundaries")) return "boundaries";
2176
+ if (LAND_LINES.has(id)) return "land";
2177
+ if (id.startsWith("roads")) return "roads";
2178
+ if (id === "earth" || id === "landcover" || id.startsWith("landuse")) return "land";
2179
+ return null;
2180
+ }
2181
+ function isLayerVisible(layers, group) {
2182
+ return layers?.visible?.[group] ?? DEFAULT_LAYER_VISIBILITY[group];
2183
+ }
2184
+ function effectiveLayerOrder(layers) {
2185
+ return layers?.order ?? LAYER_GROUPS;
2186
+ }
2187
+ function isStockLayerOrder(order) {
2188
+ return !order || order.length === LAYER_GROUPS.length && order.every((g, i) => g === LAYER_GROUPS[i]);
2189
+ }
2190
+ function isStockLayers(layers) {
2191
+ if (!layers) return true;
2192
+ if (!isStockLayerOrder(layers.order)) return false;
2193
+ return LAYER_GROUPS.every((g) => isLayerVisible(layers, g) === DEFAULT_LAYER_VISIBILITY[g]);
2194
+ }
2195
+ function buildings3dLayer(source, color) {
2196
+ return {
2197
+ id: BUILDINGS_3D_LAYER_ID,
2198
+ type: "fill-extrusion",
2199
+ source,
2200
+ "source-layer": "buildings",
2201
+ minzoom: 13,
2202
+ filter: ["in", "kind", "building", "building_part"],
2203
+ paint: {
2204
+ "fill-extrusion-color": color,
2205
+ "fill-extrusion-height": ["coalesce", ["get", "height"], 6],
2206
+ "fill-extrusion-base": ["coalesce", ["get", "min_height"], 0],
2207
+ "fill-extrusion-opacity": 0.85
2208
+ }
2209
+ };
2210
+ }
2211
+ function applyThemeLayers(styleLayers, layers, buildingsColor) {
2212
+ if (isStockLayers(layers)) return styleLayers;
2213
+ const visible = (g) => isLayerVisible(layers, g);
2214
+ const tagged = styleLayers.map((layer) => ({ layer, group: layerGroupOf(layer) }));
2215
+ const source = styleLayers.find((l) => l.id === "buildings")?.source ?? "unmap";
2216
+ const extrusion = visible("buildings3d") ? buildings3dLayer(source, buildingsColor) : null;
2217
+ if (isStockLayerOrder(layers?.order)) {
2218
+ const kept = tagged.filter(({ group }) => group === null || visible(group)).map((t) => t.layer);
2219
+ if (!extrusion) return kept;
2220
+ const firstLabel = kept.findIndex((l) => layerGroupOf(l) === "labels");
2221
+ const at = firstLabel === -1 ? kept.length : firstLabel;
2222
+ return [...kept.slice(0, at), extrusion, ...kept.slice(at)];
2223
+ }
2224
+ const out = tagged.filter(({ group }) => group === null).map((t) => t.layer);
2225
+ for (const group of effectiveLayerOrder(layers)) {
2226
+ if (!visible(group)) continue;
2227
+ if (group === "buildings3d") {
2228
+ if (extrusion) out.push(extrusion);
2229
+ continue;
2230
+ }
2231
+ for (const t of tagged) if (t.group === group) out.push(t.layer);
2232
+ }
2233
+ return out;
2234
+ }
2235
+
2156
2236
  // src/preset.ts
2157
2237
  var PRESET_CODE_PREFIX = "u";
2158
2238
  var PRESET_CODE_RE = /^u[0-9A-Za-z]{2,24}$/;
2159
2239
  var BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2160
2240
  var SEED_KEYS = ["land", "water", "nature", "accent"];
2161
- var STYLE_ENCODE = {
2162
- base: 0,
2163
- muted: 1,
2164
- outdoor: 4,
2165
- decimal: 5,
2166
- blueprint: 6,
2167
- blush: 8,
2168
- orchid: 9,
2169
- canopy: 10,
2170
- lagoon: 11
2171
- };
2172
- var STYLE_DECODE = [
2241
+ var STYLE_SLOTS = [
2173
2242
  "base",
2174
2243
  "muted",
2175
2244
  "outdoor",
2176
- "base",
2177
- "outdoor",
2178
2245
  "decimal",
2179
2246
  "blueprint",
2180
- void 0,
2181
2247
  "blush",
2182
2248
  "orchid",
2183
2249
  "canopy",
2184
2250
  "lagoon"
2185
2251
  ];
2186
- var PRESET_OFFSET = 8n;
2187
- var ORIGINAL_SLOTS = 5n;
2188
- var ORIGINAL_PRESET_LIMIT = 64n;
2189
- var LATER_OFFSET = PRESET_OFFSET + ORIGINAL_PRESET_LIMIT * ORIGINAL_SLOTS;
2190
- var LATER_LANES = 3n;
2191
- var LATER_ROWS = 232n;
2192
- var WAVE2_BASE = 5;
2193
- var WAVE3_BASE = 8;
2194
- var WAVE3_BARE_ROWS = 32n;
2195
- var WAVE3_STYLE_COLUMNS = 20n;
2196
- var WAVE3_PRESET_LIMIT = (LATER_ROWS - WAVE3_BARE_ROWS) / WAVE3_STYLE_COLUMNS;
2197
- var CUSTOM_OFFSET = 1024n;
2198
- var ESCAPE_SLOT = 7n;
2199
- var ESCAPE_BITS = 5n;
2252
+ var STYLE_BITS = 5n;
2253
+ var KIND_BITS = 2n;
2254
+ var KIND_BARE = 0n;
2255
+ var KIND_PRESET = 1n;
2256
+ var KIND_CUSTOM = 2n;
2257
+ var PRESET_INDEX_BITS = 8n;
2258
+ var RGB_BITS = 24n;
2259
+ var VISIBILITY_BITS = BigInt(LAYER_GROUPS.length);
2260
+ var ORDER_BITS = 13n;
2261
+ var ORDER_COUNT = factorial(LAYER_GROUPS.length);
2262
+ var mask = (bits) => (1n << bits) - 1n;
2200
2263
  var sameHex = (a, b) => a.toLowerCase() === b.toLowerCase();
2264
+ function factorial(n) {
2265
+ let f = 1n;
2266
+ for (let i = 2n; i <= BigInt(n); i++) f *= i;
2267
+ return f;
2268
+ }
2201
2269
  function isThemePresetCode(code) {
2202
2270
  return PRESET_CODE_RE.test(code);
2203
2271
  }
@@ -2261,105 +2329,121 @@ function generatedMatch(doc) {
2261
2329
  function findPresetIndex(seeds) {
2262
2330
  return THEME_PRESETS.findIndex((p) => seedsMatch(p.seeds, { ...DEFAULT_SEEDS, ...seeds }));
2263
2331
  }
2264
- function encodeBits(bits) {
2265
- return PRESET_CODE_PREFIX + toBase62(bits);
2332
+ function orderIndex(order) {
2333
+ const remaining = [...LAYER_GROUPS];
2334
+ let index = 0n;
2335
+ let radix = factorial(remaining.length);
2336
+ for (const group of order) {
2337
+ const at = remaining.indexOf(group);
2338
+ radix /= BigInt(remaining.length);
2339
+ index += BigInt(at) * radix;
2340
+ remaining.splice(at, 1);
2341
+ }
2342
+ return index;
2343
+ }
2344
+ function orderAt(index) {
2345
+ const remaining = [...LAYER_GROUPS];
2346
+ const order = [];
2347
+ let rest = index;
2348
+ let radix = factorial(remaining.length);
2349
+ while (remaining.length > 0) {
2350
+ radix /= BigInt(remaining.length);
2351
+ const at = Number(rest / radix);
2352
+ rest %= radix;
2353
+ order.push(remaining.splice(at, 1)[0]);
2354
+ }
2355
+ return order;
2356
+ }
2357
+ function packLayers(layers) {
2358
+ let bits = 0n;
2359
+ LAYER_GROUPS.forEach((group, i) => {
2360
+ const visible = layers.visible?.[group] ?? DEFAULT_LAYER_VISIBILITY[group];
2361
+ if (visible) bits |= 1n << BigInt(i);
2362
+ });
2363
+ const order = isStockLayerOrder(layers.order) ? 0n : orderIndex(layers.order);
2364
+ return bits | order << VISIBILITY_BITS;
2365
+ }
2366
+ function unpackLayers(bits) {
2367
+ const layers = {};
2368
+ const visible = {};
2369
+ LAYER_GROUPS.forEach((group, i) => {
2370
+ const on = (bits >> BigInt(i) & 1n) === 1n;
2371
+ if (on !== DEFAULT_LAYER_VISIBILITY[group]) visible[group] = on;
2372
+ });
2373
+ if (Object.keys(visible).length > 0) layers.visible = visible;
2374
+ const order = bits >> VISIBILITY_BITS;
2375
+ if (order >= ORDER_COUNT) {
2376
+ throw new ThemeError("invalid theme", ["preset layer order is out of range"]);
2377
+ }
2378
+ if (order !== 0n) layers.order = orderAt(order);
2379
+ return layers;
2266
2380
  }
2267
2381
  function encodePreset(doc) {
2268
- const index = STYLE_ENCODE[doc.base];
2269
- if (index === void 0) return null;
2270
- const s = BigInt(index);
2382
+ const slot = STYLE_SLOTS.indexOf(doc.base);
2383
+ if (slot < 0) return null;
2384
+ let kind;
2385
+ let payload = 0n;
2386
+ let payloadBits = 0n;
2271
2387
  if (isBareStyle(doc)) {
2272
- if (index < WAVE3_BASE) return encodeBits(s);
2273
- return encodeBits(LATER_OFFSET + (s - BigInt(WAVE3_BASE)) * LATER_LANES + 2n);
2274
- }
2275
- if (!doc.seeds || !generatedMatch(doc)) return null;
2276
- const presetIndex = findPresetIndex(doc.seeds);
2277
- if (presetIndex >= 0) {
2278
- const p = BigInt(presetIndex);
2279
- if (index < WAVE2_BASE) {
2280
- if (p < ORIGINAL_PRESET_LIMIT) return encodeBits(PRESET_OFFSET + p * ORIGINAL_SLOTS + s);
2281
- } else if (index < WAVE3_BASE) {
2282
- if (p < LATER_ROWS) {
2283
- return encodeBits(LATER_OFFSET + p * LATER_LANES + (s - BigInt(WAVE2_BASE)));
2388
+ kind = KIND_BARE;
2389
+ } else {
2390
+ if (!doc.seeds || !generatedMatch(doc)) return null;
2391
+ const presetIndex = findPresetIndex(doc.seeds);
2392
+ if (presetIndex >= 0 && presetIndex < Number(mask(PRESET_INDEX_BITS))) {
2393
+ kind = KIND_PRESET;
2394
+ payload = BigInt(presetIndex);
2395
+ payloadBits = PRESET_INDEX_BITS;
2396
+ } else {
2397
+ if (!doc.seeds.accent) return null;
2398
+ kind = KIND_CUSTOM;
2399
+ const filled = { ...DEFAULT_SEEDS, ...doc.seeds };
2400
+ for (const key of SEED_KEYS) {
2401
+ payload |= rgb24(filled[key]) << payloadBits;
2402
+ payloadBits += RGB_BITS;
2284
2403
  }
2285
- } else if (p < WAVE3_PRESET_LIMIT && s - BigInt(WAVE3_BASE) < WAVE3_STYLE_COLUMNS) {
2286
- const row = WAVE3_BARE_ROWS + p * WAVE3_STYLE_COLUMNS + (s - BigInt(WAVE3_BASE));
2287
- return encodeBits(LATER_OFFSET + row * LATER_LANES + 2n);
2288
2404
  }
2289
2405
  }
2290
- if (!doc.seeds.accent) return null;
2291
- const filled = { ...DEFAULT_SEEDS, ...doc.seeds };
2292
- let bits;
2293
- let shift;
2294
- if (index < WAVE3_BASE) {
2295
- bits = CUSTOM_OFFSET + s;
2296
- shift = 3n;
2297
- } else {
2298
- bits = CUSTOM_OFFSET + ESCAPE_SLOT + (s - BigInt(WAVE3_BASE) << 3n);
2299
- shift = 3n + ESCAPE_BITS;
2300
- }
2301
- for (const key of SEED_KEYS) {
2302
- bits += rgb24(filled[key]) << shift;
2303
- shift += 24n;
2304
- }
2305
- return encodeBits(bits);
2406
+ const hasLayers = !isStockLayers(doc.layers);
2407
+ let bits = BigInt(slot) | kind << STYLE_BITS | (hasLayers ? 1n : 0n) << STYLE_BITS + KIND_BITS;
2408
+ let shift = STYLE_BITS + KIND_BITS + 1n;
2409
+ bits |= payload << shift;
2410
+ shift += payloadBits;
2411
+ if (hasLayers) bits |= packLayers(doc.layers) << shift;
2412
+ return PRESET_CODE_PREFIX + toBase62(bits);
2306
2413
  }
2307
2414
  function decodePreset(code) {
2308
2415
  if (!isThemePresetCode(code)) {
2309
2416
  throw new ThemeError("invalid theme", ["code is not a valid preset"]);
2310
2417
  }
2311
- const bits = fromBase62(code.slice(PRESET_CODE_PREFIX.length));
2312
- const styleAt = (index) => {
2313
- const base2 = STYLE_DECODE[index];
2314
- if (!base2) throw new ThemeError("invalid theme", ["preset style is out of range"]);
2315
- return base2;
2418
+ let rest = fromBase62(code.slice(PRESET_CODE_PREFIX.length));
2419
+ const take = (bits) => {
2420
+ const value = rest & mask(bits);
2421
+ rest >>= bits;
2422
+ return value;
2316
2423
  };
2317
- const presetAt = (index) => {
2318
- const preset = THEME_PRESETS[index];
2424
+ const base = STYLE_SLOTS[Number(take(STYLE_BITS))];
2425
+ if (!base) throw new ThemeError("invalid theme", ["preset style is out of range"]);
2426
+ const kind = take(KIND_BITS);
2427
+ const hasLayers = take(1n) === 1n;
2428
+ let doc;
2429
+ if (kind === KIND_BARE) {
2430
+ doc = structuredClone(STYLE_THEMES[base]);
2431
+ } else if (kind === KIND_PRESET) {
2432
+ const preset = THEME_PRESETS[Number(take(PRESET_INDEX_BITS))];
2319
2433
  if (!preset) throw new ThemeError("invalid theme", ["preset is out of range"]);
2320
- return preset;
2321
- };
2322
- if (bits < PRESET_OFFSET) {
2323
- return structuredClone(STYLE_THEMES[styleAt(Number(bits))]);
2324
- }
2325
- if (bits < LATER_OFFSET) {
2326
- const n = bits - PRESET_OFFSET;
2327
- return generateTheme(styleAt(Number(n % ORIGINAL_SLOTS)), presetAt(Number(n / ORIGINAL_SLOTS)).seeds);
2328
- }
2329
- if (bits < CUSTOM_OFFSET) {
2330
- const n = bits - LATER_OFFSET;
2331
- const lane = Number(n % LATER_LANES);
2332
- const row = n / LATER_LANES;
2333
- if (lane < 2) {
2334
- return generateTheme(styleAt(WAVE2_BASE + lane), presetAt(Number(row)).seeds);
2335
- }
2336
- if (row < WAVE3_BARE_ROWS) {
2337
- return structuredClone(STYLE_THEMES[styleAt(WAVE3_BASE + Number(row))]);
2338
- }
2339
- const j = row - WAVE3_BARE_ROWS;
2340
- const base2 = styleAt(WAVE3_BASE + Number(j % WAVE3_STYLE_COLUMNS));
2341
- return generateTheme(base2, presetAt(Number(j / WAVE3_STYLE_COLUMNS)).seeds);
2342
- }
2343
- const payload = bits - CUSTOM_OFFSET;
2344
- const slot = payload & 7n;
2345
- let base;
2346
- let rest;
2347
- if (slot === ESCAPE_SLOT) {
2348
- base = styleAt(WAVE3_BASE + Number(payload >> 3n & (1n << ESCAPE_BITS) - 1n));
2349
- rest = payload >> 3n + ESCAPE_BITS;
2434
+ doc = generateTheme(base, preset.seeds);
2435
+ } else if (kind === KIND_CUSTOM) {
2436
+ const seeds = { ...DEFAULT_SEEDS };
2437
+ for (const key of SEED_KEYS) seeds[key] = fromRgb24(take(RGB_BITS));
2438
+ doc = generateTheme(base, seeds);
2350
2439
  } else {
2351
- base = styleAt(Number(slot));
2352
- rest = payload >> 3n;
2353
- }
2354
- const seeds = { ...DEFAULT_SEEDS };
2355
- for (const key of SEED_KEYS) {
2356
- seeds[key] = fromRgb24(rest & 0xffffffn);
2357
- rest >>= 24n;
2440
+ throw new ThemeError("invalid theme", ["preset kind is unassigned"]);
2358
2441
  }
2442
+ if (hasLayers) doc.layers = unpackLayers(take(VISIBILITY_BITS + ORDER_BITS));
2359
2443
  if (rest !== 0n) {
2360
2444
  throw new ThemeError("invalid theme", ["preset payload has unused bits"]);
2361
2445
  }
2362
- return generateTheme(base, seeds);
2446
+ return doc;
2363
2447
  }
2364
2448
  function registryThemeKey(doc) {
2365
2449
  if (isBareStyle(doc)) return STYLE_THEMES[doc.base]?.name;
@@ -2369,86 +2453,6 @@ function registryThemeKey(doc) {
2369
2453
  return presetId(THEME_PRESETS[presetIndex].name);
2370
2454
  }
2371
2455
 
2372
- // src/layers.ts
2373
- var DEFAULT_LAYER_VISIBILITY = {
2374
- land: true,
2375
- water: true,
2376
- buildings: true,
2377
- roads: true,
2378
- boundaries: true,
2379
- buildings3d: false,
2380
- labels: true
2381
- };
2382
- var BUILDINGS_3D_LAYER_ID = "buildings_3d";
2383
- var LAND_LINES = /* @__PURE__ */ new Set(["roads_runway", "roads_taxiway"]);
2384
- function layerGroupOf(layer) {
2385
- const { id, type } = layer;
2386
- if (type === "symbol") return "labels";
2387
- if (id === "background") return null;
2388
- if (id === BUILDINGS_3D_LAYER_ID) return "buildings3d";
2389
- if (id === "buildings") return "buildings";
2390
- if (id.startsWith("water")) return "water";
2391
- if (id.startsWith("boundaries")) return "boundaries";
2392
- if (LAND_LINES.has(id)) return "land";
2393
- if (id.startsWith("roads")) return "roads";
2394
- if (id === "earth" || id === "landcover" || id.startsWith("landuse")) return "land";
2395
- return null;
2396
- }
2397
- function isLayerVisible(layers, group) {
2398
- return layers?.visible?.[group] ?? DEFAULT_LAYER_VISIBILITY[group];
2399
- }
2400
- function effectiveLayerOrder(layers) {
2401
- return layers?.order ?? LAYER_GROUPS;
2402
- }
2403
- function isStockLayerOrder(order) {
2404
- return !order || order.length === LAYER_GROUPS.length && order.every((g, i) => g === LAYER_GROUPS[i]);
2405
- }
2406
- function isStockLayers(layers) {
2407
- if (!layers) return true;
2408
- if (!isStockLayerOrder(layers.order)) return false;
2409
- return LAYER_GROUPS.every((g) => isLayerVisible(layers, g) === DEFAULT_LAYER_VISIBILITY[g]);
2410
- }
2411
- function buildings3dLayer(source, color) {
2412
- return {
2413
- id: BUILDINGS_3D_LAYER_ID,
2414
- type: "fill-extrusion",
2415
- source,
2416
- "source-layer": "buildings",
2417
- minzoom: 13,
2418
- filter: ["in", "kind", "building", "building_part"],
2419
- paint: {
2420
- "fill-extrusion-color": color,
2421
- "fill-extrusion-height": ["coalesce", ["get", "height"], 6],
2422
- "fill-extrusion-base": ["coalesce", ["get", "min_height"], 0],
2423
- "fill-extrusion-opacity": 0.85
2424
- }
2425
- };
2426
- }
2427
- function applyThemeLayers(styleLayers, layers, buildingsColor) {
2428
- if (isStockLayers(layers)) return styleLayers;
2429
- const visible = (g) => isLayerVisible(layers, g);
2430
- const tagged = styleLayers.map((layer) => ({ layer, group: layerGroupOf(layer) }));
2431
- const source = styleLayers.find((l) => l.id === "buildings")?.source ?? "unmap";
2432
- const extrusion = visible("buildings3d") ? buildings3dLayer(source, buildingsColor) : null;
2433
- if (isStockLayerOrder(layers?.order)) {
2434
- const kept = tagged.filter(({ group }) => group === null || visible(group)).map((t) => t.layer);
2435
- if (!extrusion) return kept;
2436
- const firstLabel = kept.findIndex((l) => layerGroupOf(l) === "labels");
2437
- const at = firstLabel === -1 ? kept.length : firstLabel;
2438
- return [...kept.slice(0, at), extrusion, ...kept.slice(at)];
2439
- }
2440
- const out = tagged.filter(({ group }) => group === null).map((t) => t.layer);
2441
- for (const group of effectiveLayerOrder(layers)) {
2442
- if (!visible(group)) continue;
2443
- if (group === "buildings3d") {
2444
- if (extrusion) out.push(extrusion);
2445
- continue;
2446
- }
2447
- for (const t of tagged) if (t.group === group) out.push(t.layer);
2448
- }
2449
- return out;
2450
- }
2451
-
2452
2456
  // src/codec.ts
2453
2457
  var THEME_CODE_PREFIX = "ut1.";
2454
2458
  var MAX_CODE_LENGTH = 4096;
@@ -2493,7 +2497,7 @@ async function through(bytes, transform, cap) {
2493
2497
  }
2494
2498
  async function encodeTheme(doc) {
2495
2499
  const valid = validateTheme(doc);
2496
- const preset = isStockLayers(valid.layers) ? encodePreset(valid) : null;
2500
+ const preset = encodePreset(valid);
2497
2501
  if (preset) return preset;
2498
2502
  const json = new TextEncoder().encode(JSON.stringify(valid));
2499
2503
  const packed = await through(json, new CompressionStream("deflate"), Number.POSITIVE_INFINITY);