@tileflow/core 0.1.0-alpha.44 → 0.1.0-alpha.45

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
@@ -13,241 +13,6 @@ var __export = (target, all2) => {
13
13
  __defProp(target, name, { get: all2[name], enumerable: true });
14
14
  };
15
15
 
16
- // src/theme-blend.ts
17
- function mixTileflowColours(from, to, t) {
18
- const a = oklab(from);
19
- const b = oklab(to);
20
- if (!a || !b) return void 0;
21
- return colourFromOklab(a.map((value, channel) => value + (b[channel] - value) * t));
22
- }
23
- function oklab(colour) {
24
- const cached = oklabCache.get(colour);
25
- if (cached !== void 0) return cached;
26
- const rgba = parseColour(colour);
27
- let result = null;
28
- if (rgba) {
29
- const [r, g, b] = rgba.slice(0, 3).map((channel) => {
30
- const value = channel / 255;
31
- return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
32
- });
33
- const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
34
- const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
35
- const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
36
- result = [
37
- 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
38
- 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
39
- 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
40
- rgba[3]
41
- ];
42
- }
43
- if (oklabCache.size > 4096) oklabCache.clear();
44
- oklabCache.set(colour, result);
45
- return result;
46
- }
47
- function colourFromOklab([lightness, a, b, alpha2]) {
48
- const l = (lightness + 0.3963377774 * a + 0.2158037573 * b) ** 3;
49
- const m = (lightness - 0.1055613458 * a - 0.0638541728 * b) ** 3;
50
- const s = (lightness - 0.0894841775 * a - 1.291485548 * b) ** 3;
51
- const [red, green, blue] = [
52
- 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
53
- -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
54
- -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
55
- ].map((value) => {
56
- const clamped = Math.min(1, Math.max(0, value));
57
- const encoded = clamped <= 31308e-7 ? clamped * 12.92 : 1.055 * clamped ** (1 / 2.4) - 0.055;
58
- return Math.round(encoded * 255);
59
- });
60
- return `rgba(${red}, ${green}, ${blue}, ${Number(alpha2.toFixed(3))})`;
61
- }
62
- function tileflowColourChannels(colour) {
63
- const rgba = parseColour(colour);
64
- if (!rgba) return null;
65
- return [
66
- Math.min(1, Math.max(0, rgba[0] / 255)),
67
- Math.min(1, Math.max(0, rgba[1] / 255)),
68
- Math.min(1, Math.max(0, rgba[2] / 255)),
69
- Math.min(1, Math.max(0, rgba[3]))
70
- ];
71
- }
72
- function parseColour(colour) {
73
- const text = colour.trim().toLowerCase();
74
- const hex = /^#([\da-f]{3,8})$/u.exec(text)?.[1];
75
- if (hex && [3, 4, 6, 8].includes(hex.length)) {
76
- const full = hex.length <= 4 ? [...hex].map((digit) => digit + digit).join("") : hex;
77
- const channels = full.match(/../gu).map((pair) => Number.parseInt(pair, 16));
78
- return [channels[0], channels[1], channels[2], channels.length > 3 ? channels[3] / 255 : 1];
79
- }
80
- const functional = /^(rgba?|hsla?)\(([^)]+)\)$/u.exec(text);
81
- if (!functional) return null;
82
- const parts = functional[2].split(/[\s,/]+/u).filter(Boolean);
83
- if (parts.length < 3 || parts.length > 4) return null;
84
- const alpha2 = parts[3] === void 0 ? 1 : parseUnit(parts[3], 1);
85
- if (functional[1].startsWith("rgb")) {
86
- const [r, g, b] = parts.slice(0, 3).map((part) => parseUnit(part, 255));
87
- if ([r, g, b, alpha2].some((value) => value === null)) return null;
88
- return [r, g, b, alpha2];
89
- }
90
- const hue = Number.parseFloat(parts[0]);
91
- const saturation = parseUnit(parts[1], 1);
92
- const light = parseUnit(parts[2], 1);
93
- if (!Number.isFinite(hue) || saturation === null || light === null || alpha2 === null) return null;
94
- const chroma = (1 - Math.abs(2 * light - 1)) * saturation;
95
- const sector = (hue % 360 + 360) % 360 / 60;
96
- const x = chroma * (1 - Math.abs(sector % 2 - 1));
97
- const [r1, g1, b1] = sector < 1 ? [chroma, x, 0] : sector < 2 ? [x, chroma, 0] : sector < 3 ? [0, chroma, x] : sector < 4 ? [0, x, chroma] : sector < 5 ? [x, 0, chroma] : [chroma, 0, x];
98
- const offset = light - chroma / 2;
99
- return [(r1 + offset) * 255, (g1 + offset) * 255, (b1 + offset) * 255, alpha2];
100
- }
101
- function parseUnit(part, scale) {
102
- const percentage = part.endsWith("%");
103
- const value = Number.parseFloat(part);
104
- if (!Number.isFinite(value)) return null;
105
- return percentage ? value / 100 * scale : value;
106
- }
107
- var oklabCache;
108
- var init_theme_blend = __esm({
109
- "src/theme-blend.ts"() {
110
- "use strict";
111
- oklabCache = /* @__PURE__ */ new Map();
112
- }
113
- });
114
-
115
- // src/camera-limits.ts
116
- var tileflowMaxPitch;
117
- var init_camera_limits = __esm({
118
- "src/camera-limits.ts"() {
119
- "use strict";
120
- tileflowMaxPitch = 85;
121
- }
122
- });
123
-
124
- // src/native-zod-object.ts
125
- import { z } from "zod";
126
- function strictNativeObject(shape) {
127
- const object = new z.core.$ZodObject({
128
- type: "object",
129
- shape,
130
- catchall: z.never()
131
- });
132
- return z.lazy(() => object);
133
- }
134
- var init_native_zod_object = __esm({
135
- "src/native-zod-object.ts"() {
136
- "use strict";
137
- }
138
- });
139
-
140
- // src/manifest-schema-classic.ts
141
- import { z as z2 } from "zod";
142
- function classic(schema) {
143
- return schema;
144
- }
145
- var classicManifestStringOperations, classicManifestSchemaOperations;
146
- var init_manifest_schema_classic = __esm({
147
- "src/manifest-schema-classic.ts"() {
148
- "use strict";
149
- init_native_zod_object();
150
- classicManifestStringOperations = {
151
- string: () => z2.string(),
152
- minLength(schema, minimum2) {
153
- return schema.min(
154
- minimum2
155
- );
156
- },
157
- maxLength(schema, maximum2) {
158
- return schema.max(
159
- maximum2
160
- );
161
- },
162
- regex: (schema, pattern, message) => schema.regex(pattern, { message }),
163
- refine: (schema, check, message) => classic(schema).refine(check, message)
164
- };
165
- classicManifestSchemaOperations = {
166
- ...classicManifestStringOperations,
167
- number: (minimum2, maximum2) => z2.number().finite().min(minimum2).max(maximum2),
168
- unknown: () => z2.unknown(),
169
- optional: (schema) => classic(schema).optional(),
170
- array: (schema) => z2.array(classic(schema)),
171
- enum(values) {
172
- return z2.enum(values);
173
- },
174
- literal: (value) => z2.literal(value),
175
- union: (left, right) => z2.union([classic(left), classic(right)]),
176
- tuplePair(left, right) {
177
- return z2.tuple([classic(left), classic(right)]);
178
- },
179
- object(shape) {
180
- return strictNativeObject(shape);
181
- },
182
- record(key, value) {
183
- return z2.record(classic(key), classic(value));
184
- },
185
- superRefine: (schema, check) => classic(schema).superRefine(check),
186
- pipe: (input, output) => classic(input).pipe(classic(output))
187
- };
188
- }
189
- });
190
-
191
- // src/portable-identity-rules.ts
192
- function isTileflowPortableId(value) {
193
- return typeof value === "string" && value.length <= tileflowPortableIdMaximumLength && tileflowPortableIdPattern.test(value) && !["constructor", "prototype"].includes(value) && !/^(?:AUX|CON|NUL|PRN|COM[1-9]|LPT[1-9])$/i.test(value);
194
- }
195
- function isTileflowThemeName(value) {
196
- return isTileflowPortableId(value) && value !== "system";
197
- }
198
- var tileflowPortableIdMaximumLength, tileflowPortableIdPattern;
199
- var init_portable_identity_rules = __esm({
200
- "src/portable-identity-rules.ts"() {
201
- "use strict";
202
- tileflowPortableIdMaximumLength = 64;
203
- tileflowPortableIdPattern = /^[a-z][a-z0-9-]{0,63}$/u;
204
- }
205
- });
206
-
207
- // src/portable-identity-schema.ts
208
- function createPortableIdentitySchemas(operations2) {
209
- const bounded2 = operations2.maxLength(
210
- operations2.minLength(operations2.string(), 1),
211
- tileflowPortableIdMaximumLength
212
- );
213
- const pattern = operations2.regex(
214
- bounded2,
215
- tileflowPortableIdPattern,
216
- "Expected lowercase kebab-case beginning with a letter"
217
- );
218
- const tileflowPortableIdSchema2 = operations2.refine(
219
- pattern,
220
- (name) => !["constructor", "prototype"].includes(name) && !/^(?:AUX|CON|NUL|PRN|COM[1-9]|LPT[1-9])$/i.test(name),
221
- "Expected a portable identifier that is not a reserved filename or prototype key"
222
- );
223
- const tileflowThemeNameSchema2 = operations2.refine(
224
- tileflowPortableIdSchema2,
225
- isTileflowThemeName,
226
- "Expected a concrete theme name; system is browser-only"
227
- );
228
- return { tileflowPortableIdSchema: tileflowPortableIdSchema2, tileflowThemeNameSchema: tileflowThemeNameSchema2 };
229
- }
230
- var init_portable_identity_schema = __esm({
231
- "src/portable-identity-schema.ts"() {
232
- "use strict";
233
- init_portable_identity_rules();
234
- }
235
- });
236
-
237
- // src/portable-identity.ts
238
- var schemas, tileflowPortableIdSchema, tileflowThemeNameSchema;
239
- var init_portable_identity = __esm({
240
- "src/portable-identity.ts"() {
241
- "use strict";
242
- init_manifest_schema_classic();
243
- init_portable_identity_schema();
244
- init_portable_identity_rules();
245
- schemas = createPortableIdentitySchemas(classicManifestStringOperations);
246
- tileflowPortableIdSchema = schemas.tileflowPortableIdSchema;
247
- tileflowThemeNameSchema = schemas.tileflowThemeNameSchema;
248
- }
249
- });
250
-
251
16
  // src/cartography/expression-operators.ts
252
17
  function isMapLibreExpressionOperator(value) {
253
18
  return typeof value === "string" && mapLibreExpressionOperators.has(value);
@@ -370,16 +135,16 @@ var init_expression_operators = __esm({
370
135
  function fixed(value, options) {
371
136
  const reason = options.reason.trim();
372
137
  if (!reason) throw new Error("Tileflow fixed values require a non-empty reason.");
373
- cloneJson2(value);
138
+ cloneJson(value);
374
139
  return { kind: "theme-fixed", reason, value };
375
140
  }
376
141
  function expression(value) {
377
142
  validateExpressionArray(value, "expression");
378
- return { kind: "expression", value: cloneJson2(value) };
143
+ return { kind: "expression", value: cloneJson(value) };
379
144
  }
380
145
  function toMapLibreStyleValue(value) {
381
146
  if (isTileflowExpression(value)) {
382
- return cloneJson2(value.value);
147
+ return cloneJson(value.value);
383
148
  }
384
149
  if (isTileflowZoomValue(value)) {
385
150
  if (value.interpolation === "step") {
@@ -387,8 +152,8 @@ function toMapLibreStyleValue(value) {
387
152
  return [
388
153
  "step",
389
154
  ["zoom"],
390
- cloneJson2(firstValue),
391
- ...rest.flatMap(([stop, stopValue]) => [stop, cloneJson2(stopValue)])
155
+ cloneJson(firstValue),
156
+ ...rest.flatMap(([stop, stopValue]) => [stop, cloneJson(stopValue)])
392
157
  ];
393
158
  }
394
159
  const interpolation = value.interpolation === "exponential" ? ["exponential", value.base] : [value.interpolation];
@@ -396,13 +161,13 @@ function toMapLibreStyleValue(value) {
396
161
  "interpolate",
397
162
  interpolation,
398
163
  ["zoom"],
399
- ...value.stops.flatMap(([stop, stopValue]) => [stop, cloneJson2(stopValue)])
164
+ ...value.stops.flatMap(([stop, stopValue]) => [stop, cloneJson(stopValue)])
400
165
  ];
401
166
  }
402
167
  if (isTileflowThemeValueNode(value)) {
403
168
  throw new Error("Tileflow theme values must be resolved before MapLibre style compilation.");
404
169
  }
405
- return cloneJson2(value);
170
+ return cloneJson(value);
406
171
  }
407
172
  function isTileflowExpression(value) {
408
173
  return isRecord(value) && value.kind === "expression" && Array.isArray(value.value);
@@ -439,7 +204,7 @@ function createZoomValue(interpolation, stops, base) {
439
204
  throw new Error("Tileflow zoom stops must be strictly increasing.");
440
205
  }
441
206
  previous = stop;
442
- return [stop, cloneJson2(value)];
207
+ return [stop, cloneJson(value)];
443
208
  });
444
209
  return {
445
210
  kind: "zoom",
@@ -468,9 +233,9 @@ function validateExpressionArray(value, name) {
468
233
  if (!isMapLibreExpressionOperator(value[0])) {
469
234
  throw new Error(`Unknown MapLibre expression operator: ${value[0]}`);
470
235
  }
471
- cloneJson2(value);
236
+ cloneJson(value);
472
237
  }
473
- function cloneJson2(value) {
238
+ function cloneJson(value) {
474
239
  const serialized = JSON.stringify(value);
475
240
  if (serialized === void 0) {
476
241
  throw new Error("Tileflow style values must be JSON-serializable.");
@@ -512,9 +277,9 @@ var init_values = __esm({
512
277
  color = {
513
278
  alpha(value, opacity) {
514
279
  return {
515
- color: cloneJson2(value),
280
+ color: cloneJson(value),
516
281
  kind: "theme-color",
517
- opacity: cloneJson2(opacity),
282
+ opacity: cloneJson(opacity),
518
283
  operation: "alpha"
519
284
  };
520
285
  },
@@ -523,12 +288,12 @@ var init_values = __esm({
523
288
  throw new Error("Tileflow color mixing supports only the deterministic OKLCH color space.");
524
289
  }
525
290
  return {
526
- amount: cloneJson2(options.amount),
527
- from: cloneJson2(from),
291
+ amount: cloneJson(options.amount),
292
+ from: cloneJson(from),
528
293
  kind: "theme-color",
529
294
  operation: "mix",
530
295
  space: "oklch",
531
- to: cloneJson2(to)
296
+ to: cloneJson(to)
532
297
  };
533
298
  }
534
299
  };
@@ -591,6 +356,22 @@ var init_operations = __esm({
591
356
  }
592
357
  });
593
358
 
359
+ // src/portable-identity-rules.ts
360
+ function isTileflowPortableId(value) {
361
+ return typeof value === "string" && value.length <= tileflowPortableIdMaximumLength && tileflowPortableIdPattern.test(value) && !["constructor", "prototype"].includes(value) && !/^(?:AUX|CON|NUL|PRN|COM[1-9]|LPT[1-9])$/i.test(value);
362
+ }
363
+ function isTileflowThemeName(value) {
364
+ return isTileflowPortableId(value) && value !== "system";
365
+ }
366
+ var tileflowPortableIdMaximumLength, tileflowPortableIdPattern;
367
+ var init_portable_identity_rules = __esm({
368
+ "src/portable-identity-rules.ts"() {
369
+ "use strict";
370
+ tileflowPortableIdMaximumLength = 64;
371
+ tileflowPortableIdPattern = /^[a-z][a-z0-9-]{0,63}$/u;
372
+ }
373
+ });
374
+
594
375
  // src/sun.ts
595
376
  function normalizeTileflowAzimuth(azimuth) {
596
377
  const turned = azimuth % 360;
@@ -661,7 +442,7 @@ function tileflowSunDirection(azimuth, altitude) {
661
442
  function resolveTileflowSun(preset, direction = preset, source = "theme") {
662
443
  const azimuth = normalizeTileflowAzimuth(direction.azimuth);
663
444
  const altitude = Math.min(90, Math.max(-90, direction.altitude));
664
- const horizon = smoothstep2(-0.5, 3, altitude);
445
+ const horizon = smoothstep(-0.5, 3, altitude);
665
446
  const intensity = clamp01(preset.intensity) * horizon;
666
447
  const rgb = rgbOf(preset.color);
667
448
  const ambientRgb = rgbOf(preset.ambient.color);
@@ -714,9 +495,9 @@ function tileflowSunMetadata(preset) {
714
495
  return Object.freeze({ version: 1, ...preset });
715
496
  }
716
497
  function readTileflowSunPreset(metadata) {
717
- if (!isRecord4(metadata)) return void 0;
498
+ if (!isRecord2(metadata)) return void 0;
718
499
  const value = metadata[tileflowSunMetadataKey];
719
- if (!isRecord4(value) || value.version !== 1 || !isRecord4(value.ambient)) return void 0;
500
+ if (!isRecord2(value) || value.version !== 1 || !isRecord2(value.ambient)) return void 0;
720
501
  const { azimuth, altitude, color: color3, intensity, shadowIntensity } = value;
721
502
  const ambient = value.ambient;
722
503
  if (!isFiniteNumber(azimuth) || !isFiniteNumber(altitude) || !isUnit(intensity) || !isUnit(shadowIntensity) || !isUnit(ambient.intensity) || !isColour(color3) || !isColour(ambient.color))
@@ -733,7 +514,7 @@ function readTileflowSunPreset(metadata) {
733
514
  function tileflowStyleSunPreset(style) {
734
515
  const published = readTileflowSunPreset(style?.metadata);
735
516
  if (published) return published;
736
- const light = isRecord4(style?.light) ? style.light : void 0;
517
+ const light = isRecord2(style?.light) ? style.light : void 0;
737
518
  const position = light?.position;
738
519
  if (!position) return tileflowDefaultSunPreset;
739
520
  return Object.freeze({
@@ -764,14 +545,14 @@ function luminance([r, g, b]) {
764
545
  const linear = (value) => value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
765
546
  return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);
766
547
  }
767
- function smoothstep2(from, to, value) {
548
+ function smoothstep(from, to, value) {
768
549
  const t = clamp01((value - from) / (to - from));
769
550
  return t * t * (3 - 2 * t);
770
551
  }
771
552
  function clamp01(value) {
772
553
  return Math.min(1, Math.max(0, Number.isFinite(value) ? value : 0));
773
554
  }
774
- function isRecord4(value) {
555
+ function isRecord2(value) {
775
556
  return typeof value === "object" && value !== null && !Array.isArray(value);
776
557
  }
777
558
  function isFiniteNumber(value) {
@@ -1034,7 +815,7 @@ function defineTheme(baseOrDefinition, maybeDefinition) {
1034
815
  version: definition.version
1035
816
  };
1036
817
  resolveTileflowTheme(theme);
1037
- return cloneJson14(theme);
818
+ return cloneJson2(theme);
1038
819
  }
1039
820
  function resolveTileflowTheme(theme) {
1040
821
  assertThemeDocument(theme);
@@ -1096,7 +877,7 @@ function auditTileflowThemeValues(value, path = "value", context = inferAuditCon
1096
877
  return diagnostics.sort(compareAuditDiagnostics);
1097
878
  }
1098
879
  function auditTileflowMapThemeValues(map) {
1099
- const modules = isRecord5(map.modules) ? map.modules : void 0;
880
+ const modules = isRecord3(map.modules) ? map.modules : void 0;
1100
881
  const moduleDiagnostics = modules ? Object.keys(modules).sort(compareCodeUnits).flatMap(
1101
882
  (owner) => auditTileflowThemeValues(modules[owner], `modules.${owner}`, {
1102
883
  owner,
@@ -1203,7 +984,7 @@ function isThemePrimitiveForCategory(value, category) {
1203
984
  return category === "number" ? typeof value === "number" && Number.isFinite(value) || Array.isArray(value) && value.every((entry) => typeof entry === "number" && Number.isFinite(entry)) : typeof value === "string" && value.length > 0;
1204
985
  }
1205
986
  function isThemeValueCandidate(value) {
1206
- return isRecord5(value) && (value.kind === "theme-token" || value.kind === "theme-fixed" || value.kind === "theme-color");
987
+ return isRecord3(value) && (value.kind === "theme-token" || value.kind === "theme-fixed" || value.kind === "theme-color");
1207
988
  }
1208
989
  function visitAuditableValue(value, path, category, state, intentRequired = false) {
1209
990
  if (isThemeStructuralPath(path)) category = void 0;
@@ -1219,7 +1000,7 @@ function visitAuditableValue(value, path, category, state, intentRequired = fals
1219
1000
  visitAuditableArray(value, path, category, state, false, intentRequired);
1220
1001
  return;
1221
1002
  }
1222
- if (!isRecord5(value)) return;
1003
+ if (!isRecord3(value)) return;
1223
1004
  if (value.kind === "expression" && Array.isArray(value.value)) {
1224
1005
  visitAuditableArray(value.value, `${path}.value`, category, state, true, intentRequired);
1225
1006
  return;
@@ -1254,8 +1035,8 @@ function lastPathKey(path) {
1254
1035
  }
1255
1036
  function visitAuditableArray(value, path, category, state, forceExpression = false, intentRequired = false) {
1256
1037
  const operator = typeof value[0] === "string" ? value[0] : void 0;
1257
- const isExpression = forceExpression || isMapLibreExpressionOperator(operator);
1258
- if (!isExpression) {
1038
+ const isExpression2 = forceExpression || isMapLibreExpressionOperator(operator);
1039
+ if (!isExpression2) {
1259
1040
  for (const [index, entry] of value.entries()) {
1260
1041
  visitAuditableValue(entry, `${path}[${index}]`, category, state, intentRequired);
1261
1042
  }
@@ -1492,30 +1273,30 @@ function assertThemeIdentity(theme) {
1492
1273
  }
1493
1274
  }
1494
1275
  function assertThemeDefinition(definition) {
1495
- const record4 = assertPlainThemeRecord(definition, "theme");
1496
- assertExactThemeKeys(record4, "theme", themeDocumentKeys);
1497
- assertRequiredThemeKeys(record4, "theme", themeDefinitionRequiredKeys);
1276
+ const record5 = assertPlainThemeRecord(definition, "theme");
1277
+ assertExactThemeKeys(record5, "theme", themeDocumentKeys);
1278
+ assertRequiredThemeKeys(record5, "theme", themeDefinitionRequiredKeys);
1498
1279
  assertThemeIdentity(definition);
1499
- if (Object.hasOwn(record4, "tokens")) {
1500
- assertThemeTokens(record4.tokens, "theme.tokens", false);
1280
+ if (Object.hasOwn(record5, "tokens")) {
1281
+ assertThemeTokens(record5.tokens, "theme.tokens", false);
1501
1282
  }
1502
- if (Object.hasOwn(record4, "typography")) {
1503
- assertThemeTypography(record4.typography, "theme.typography");
1283
+ if (Object.hasOwn(record5, "typography")) {
1284
+ assertThemeTypography(record5.typography, "theme.typography");
1504
1285
  }
1505
- if (Object.hasOwn(record4, "lighting")) {
1506
- assertThemeLighting(record4.lighting, "theme.lighting", true);
1286
+ if (Object.hasOwn(record5, "lighting")) {
1287
+ assertThemeLighting(record5.lighting, "theme.lighting", true);
1507
1288
  }
1508
- cloneJson14(definition);
1289
+ cloneJson2(definition);
1509
1290
  }
1510
1291
  function assertThemeDocument(theme) {
1511
- const record4 = assertPlainThemeRecord(theme, "theme");
1512
- assertExactThemeKeys(record4, "theme", themeDocumentKeys);
1513
- assertRequiredThemeKeys(record4, "theme", themeDocumentKeys);
1292
+ const record5 = assertPlainThemeRecord(theme, "theme");
1293
+ assertExactThemeKeys(record5, "theme", themeDocumentKeys);
1294
+ assertRequiredThemeKeys(record5, "theme", themeDocumentKeys);
1514
1295
  assertThemeIdentity(theme);
1515
- assertThemeTokens(record4.tokens, "theme.tokens", true);
1516
- assertThemeTypography(record4.typography, "theme.typography");
1517
- assertThemeLighting(record4.lighting, "theme.lighting");
1518
- cloneJson14(theme);
1296
+ assertThemeTokens(record5.tokens, "theme.tokens", true);
1297
+ assertThemeTypography(record5.typography, "theme.typography");
1298
+ assertThemeLighting(record5.lighting, "theme.lighting");
1299
+ cloneJson2(theme);
1519
1300
  }
1520
1301
  function assertThemeTokens(value, path, complete) {
1521
1302
  const tokens = assertPlainThemeRecord(value, path);
@@ -1608,7 +1389,7 @@ function assertThemeLighting(value, path, allowReset = false) {
1608
1389
  }
1609
1390
  }
1610
1391
  function assertThemeCategoryValue(value, expectedCategory, path) {
1611
- if (!isRecord5(value)) {
1392
+ if (!isRecord3(value)) {
1612
1393
  assertTokenPrimitive(expectedCategory, value, path);
1613
1394
  return;
1614
1395
  }
@@ -1785,8 +1566,8 @@ function isThemeName(value) {
1785
1566
  }
1786
1567
  function mergeTypography(base, overlay) {
1787
1568
  return {
1788
- ...cloneJson14(base),
1789
- ...cloneJson14(overlay ?? {}),
1569
+ ...cloneJson2(base),
1570
+ ...cloneJson2(overlay ?? {}),
1790
1571
  ...mergeOptionalStyle("places", base, overlay),
1791
1572
  ...mergeOptionalStyle("poi", base, overlay),
1792
1573
  ...mergeOptionalStyle("roads", base, overlay),
@@ -1799,13 +1580,13 @@ function mergeOptionalStyle(key, base, overlay) {
1799
1580
  }
1800
1581
  function mergeLighting(base, overlay) {
1801
1582
  if (isTileflowReset(overlay)) return {};
1802
- const merged = cloneJson14(base);
1583
+ const merged = cloneJson2(base);
1803
1584
  for (const key of themeLightingKeys) {
1804
1585
  const value = overlay[key];
1805
1586
  if (isTileflowReset(value)) {
1806
1587
  delete merged[key];
1807
1588
  } else if (value !== void 0) {
1808
- merged[key] = (key === "sun" || key === "ambient") && base[key] !== void 0 ? mergeRecord(base[key], value) : cloneJson14(value);
1589
+ merged[key] = (key === "sun" || key === "ambient") && base[key] !== void 0 ? mergeRecord(base[key], value) : cloneJson2(value);
1809
1590
  }
1810
1591
  }
1811
1592
  return merged;
@@ -1814,7 +1595,7 @@ function mergeRecord(base, overlay) {
1814
1595
  if (!isPlainRecord2(base) || !isPlainRecord2(overlay)) {
1815
1596
  throw new Error("Tileflow theme composition accepts only plain JSON objects.");
1816
1597
  }
1817
- return { ...cloneJson14(base), ...cloneJson14(overlay) };
1598
+ return { ...cloneJson2(base), ...cloneJson2(overlay) };
1818
1599
  }
1819
1600
  function mixOklch(from, to, amount) {
1820
1601
  const a = rgbaToOklch(parseColor(from));
@@ -1968,16 +1749,16 @@ function round(value, digits) {
1968
1749
  const power = 10 ** digits;
1969
1750
  return Math.round(value * power) / power;
1970
1751
  }
1971
- function cloneJson14(value) {
1752
+ function cloneJson2(value) {
1972
1753
  const serialized = JSON.stringify(value);
1973
1754
  if (serialized === void 0) throw new Error("Tileflow themes must be JSON-serializable.");
1974
1755
  return JSON.parse(serialized);
1975
1756
  }
1976
- function isRecord5(value) {
1757
+ function isRecord3(value) {
1977
1758
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
1978
1759
  }
1979
1760
  function isPlainRecord2(value) {
1980
- if (!isRecord5(value)) return false;
1761
+ if (!isRecord3(value)) return false;
1981
1762
  const prototype = Object.getPrototypeOf(value);
1982
1763
  return prototype === Object.prototype || prototype === null;
1983
1764
  }
@@ -2092,7 +1873,7 @@ var init_model = __esm({
2092
1873
  if (Array.isArray(value)) {
2093
1874
  return value.map((entry, index) => this.resolveTree(entry, `${path}[${index}]`, category));
2094
1875
  }
2095
- if (!isRecord5(value)) return value;
1876
+ if (!isRecord3(value)) return value;
2096
1877
  if (value.kind === "expression" && Array.isArray(value.value)) {
2097
1878
  return {
2098
1879
  ...value,
@@ -2292,26 +2073,379 @@ var init_model = __esm({
2292
2073
  return mixOklch(from, to, amount);
2293
2074
  }
2294
2075
  };
2295
- themeStructuralPathSegments = /* @__PURE__ */ new Set([
2296
- "after",
2297
- "before",
2298
- "filter",
2299
- "id",
2300
- "maxZoom",
2301
- "maxzoom",
2302
- "metadata",
2303
- "minZoom",
2304
- "minzoom",
2305
- "owner",
2306
- "placement",
2307
- "requires",
2308
- "source",
2309
- "source-layer",
2310
- "sourceLayer",
2311
- "target",
2312
- "type",
2313
- "visibility"
2314
- ]);
2076
+ themeStructuralPathSegments = /* @__PURE__ */ new Set([
2077
+ "after",
2078
+ "before",
2079
+ "filter",
2080
+ "id",
2081
+ "maxZoom",
2082
+ "maxzoom",
2083
+ "metadata",
2084
+ "minZoom",
2085
+ "minzoom",
2086
+ "owner",
2087
+ "placement",
2088
+ "requires",
2089
+ "source",
2090
+ "source-layer",
2091
+ "sourceLayer",
2092
+ "target",
2093
+ "type",
2094
+ "visibility"
2095
+ ]);
2096
+ }
2097
+ });
2098
+
2099
+ // src/color-program.ts
2100
+ function colorProgramMarker(value, program) {
2101
+ return [
2102
+ "let",
2103
+ marker,
2104
+ ["literal", program],
2105
+ "__tileflow_color_value",
2106
+ value,
2107
+ ["var", "__tileflow_color_value"]
2108
+ ];
2109
+ }
2110
+ function isColorProgramMarker(value) {
2111
+ return Array.isArray(value) && value.length === 6 && value[0] === "let" && value[1] === marker && Array.isArray(value[2]) && value[2][0] === "literal";
2112
+ }
2113
+ function colorProgramValue(value) {
2114
+ return isColorProgramMarker(value) ? value[4] : value;
2115
+ }
2116
+ function colorProgramOf(value) {
2117
+ return isColorProgramMarker(value) ? value[2][1] : value;
2118
+ }
2119
+ function preserveThemeColorPrograms(authored, resolved, theme, insideExpression = false) {
2120
+ if (isTileflowThemeTokenReference(authored) && authored.category === "color" || isTileflowThemeColorOperation(authored)) {
2121
+ const value = colorProgramMarker(resolved, resolveProgramNumbers(authored, theme));
2122
+ return insideExpression ? value : { kind: "expression", value };
2123
+ }
2124
+ if (Array.isArray(authored) && Array.isArray(resolved))
2125
+ return resolved.map(
2126
+ (value, index) => preserveThemeColorPrograms(authored[index], value, theme, insideExpression)
2127
+ );
2128
+ if (record(authored) && record(resolved)) {
2129
+ if (authored.kind === "theme-fixed") return resolved;
2130
+ return Object.fromEntries(
2131
+ Object.entries(resolved).map(([key, value]) => [
2132
+ key,
2133
+ preserveThemeColorPrograms(
2134
+ authored[key],
2135
+ value,
2136
+ theme,
2137
+ insideExpression || authored.kind === "expression" || authored.kind === "zoom" && key === "stops"
2138
+ )
2139
+ ])
2140
+ );
2141
+ }
2142
+ return resolved;
2143
+ }
2144
+ function resolveProgramNumbers(value, theme) {
2145
+ if (isTileflowThemeTokenReference(value) && value.category === "number")
2146
+ return resolveThemeValues({ opacity: value }, theme).opacity;
2147
+ if (Array.isArray(value)) return value.map((node2) => resolveProgramNumbers(node2, theme));
2148
+ if (record(value))
2149
+ return Object.fromEntries(
2150
+ Object.entries(value).map(([key, node2]) => [key, resolveProgramNumbers(node2, theme)])
2151
+ );
2152
+ return value;
2153
+ }
2154
+ function extractTileflowColorPrograms(style, tokens) {
2155
+ const programs = [];
2156
+ const ids = /* @__PURE__ */ new Map();
2157
+ const bindings = [];
2158
+ const visit2 = (node2, path) => {
2159
+ if (record(node2) && node2.kind === "expression" && isColorProgramMarker(node2.value))
2160
+ return visit2(node2.value, path);
2161
+ if (isColorProgramMarker(node2)) {
2162
+ const program = colorProgramOf(node2);
2163
+ const key = JSON.stringify(program);
2164
+ let id2 = ids.get(key);
2165
+ if (id2 === void 0) {
2166
+ id2 = programs.length;
2167
+ ids.set(key, id2);
2168
+ programs.push(program);
2169
+ }
2170
+ bindings.push([
2171
+ path[0] === "layers" && path[2] === "paint" ? [path[1], ...path.slice(3)] : path,
2172
+ id2
2173
+ ]);
2174
+ return colorProgramValue(node2);
2175
+ }
2176
+ if (Array.isArray(node2)) return node2.map((v, i) => visit2(v, [...path, i]));
2177
+ if (record(node2))
2178
+ return Object.fromEntries(
2179
+ Object.entries(node2).filter(([key]) => key !== tileflowColorMetadataKey).map(([key, v]) => [key, visit2(v, [...path, key])])
2180
+ );
2181
+ return node2;
2182
+ };
2183
+ const result = visit2(style, []);
2184
+ if (bindings.length)
2185
+ result.metadata = {
2186
+ ...result.metadata,
2187
+ // Keep the declared catalog shape across presets. Per-preset pruning makes a shared
2188
+ // object structurally different and repeats the entire catalog in the transport.
2189
+ [tileflowColorMetadataKey]: { version: 1, programs, bindings, tokens }
2190
+ };
2191
+ return result;
2192
+ }
2193
+ function record(value) {
2194
+ return !!value && typeof value === "object" && !Array.isArray(value);
2195
+ }
2196
+ function mapColorProgramsThrough(input, reader) {
2197
+ return overlayColorPrograms(input, reader(stripColorPrograms(input)));
2198
+ }
2199
+ function stripColorPrograms(value) {
2200
+ if (isColorProgramMarker(value)) return colorProgramValue(value);
2201
+ if (record(value) && value.kind === "expression" && isColorProgramMarker(value.value))
2202
+ return colorProgramValue(value.value);
2203
+ if (Array.isArray(value)) return value.map(stripColorPrograms);
2204
+ if (record(value))
2205
+ return Object.fromEntries(
2206
+ Object.entries(value).map(([key, node2]) => [key, stripColorPrograms(node2)])
2207
+ );
2208
+ return value;
2209
+ }
2210
+ function overlayColorPrograms(input, output) {
2211
+ const candidate = record(input) && input.kind === "expression" ? input.value : input;
2212
+ if (isColorProgramMarker(candidate) && typeof output === "string")
2213
+ return colorProgramMarker(output, colorProgramOf(candidate));
2214
+ if (Array.isArray(input) && Array.isArray(output))
2215
+ return output.map((value, index) => overlayColorPrograms(input[index], value));
2216
+ if (record(input) && record(output))
2217
+ return Object.fromEntries(
2218
+ Object.entries(output).map(([key, node2]) => [key, overlayColorPrograms(input[key], node2)])
2219
+ );
2220
+ return output;
2221
+ }
2222
+ var tileflowColorMetadataKey, marker;
2223
+ var init_color_program = __esm({
2224
+ "src/color-program.ts"() {
2225
+ "use strict";
2226
+ init_values();
2227
+ init_model();
2228
+ tileflowColorMetadataKey = "tileflow:colors";
2229
+ marker = "__tileflow_color_program";
2230
+ }
2231
+ });
2232
+
2233
+ // src/theme-blend.ts
2234
+ function mixTileflowColours(from, to, t) {
2235
+ const a = oklab(from);
2236
+ const b = oklab(to);
2237
+ if (!a || !b) return void 0;
2238
+ return colourFromOklab(a.map((value, channel) => value + (b[channel] - value) * t));
2239
+ }
2240
+ function oklab(colour) {
2241
+ const cached = oklabCache.get(colour);
2242
+ if (cached !== void 0) return cached;
2243
+ const rgba = parseColour(colour);
2244
+ let result = null;
2245
+ if (rgba) {
2246
+ const [r, g, b] = rgba.slice(0, 3).map((channel) => {
2247
+ const value = channel / 255;
2248
+ return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
2249
+ });
2250
+ const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
2251
+ const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
2252
+ const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
2253
+ result = [
2254
+ 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
2255
+ 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
2256
+ 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
2257
+ rgba[3]
2258
+ ];
2259
+ }
2260
+ if (oklabCache.size > 4096) oklabCache.clear();
2261
+ oklabCache.set(colour, result);
2262
+ return result;
2263
+ }
2264
+ function colourFromOklab([lightness, a, b, alpha2]) {
2265
+ const l = (lightness + 0.3963377774 * a + 0.2158037573 * b) ** 3;
2266
+ const m = (lightness - 0.1055613458 * a - 0.0638541728 * b) ** 3;
2267
+ const s = (lightness - 0.0894841775 * a - 1.291485548 * b) ** 3;
2268
+ const [red, green, blue] = [
2269
+ 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
2270
+ -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
2271
+ -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
2272
+ ].map((value) => {
2273
+ const clamped = Math.min(1, Math.max(0, value));
2274
+ const encoded = clamped <= 31308e-7 ? clamped * 12.92 : 1.055 * clamped ** (1 / 2.4) - 0.055;
2275
+ return Math.round(encoded * 255);
2276
+ });
2277
+ return `rgba(${red}, ${green}, ${blue}, ${Number(alpha2.toFixed(3))})`;
2278
+ }
2279
+ function tileflowColourChannels(colour) {
2280
+ const rgba = parseColour(colour);
2281
+ if (!rgba) return null;
2282
+ return [
2283
+ Math.min(1, Math.max(0, rgba[0] / 255)),
2284
+ Math.min(1, Math.max(0, rgba[1] / 255)),
2285
+ Math.min(1, Math.max(0, rgba[2] / 255)),
2286
+ Math.min(1, Math.max(0, rgba[3]))
2287
+ ];
2288
+ }
2289
+ function parseColour(colour) {
2290
+ const text = colour.trim().toLowerCase();
2291
+ const hex = /^#([\da-f]{3,8})$/u.exec(text)?.[1];
2292
+ if (hex && [3, 4, 6, 8].includes(hex.length)) {
2293
+ const full = hex.length <= 4 ? [...hex].map((digit) => digit + digit).join("") : hex;
2294
+ const channels = full.match(/../gu).map((pair) => Number.parseInt(pair, 16));
2295
+ return [channels[0], channels[1], channels[2], channels.length > 3 ? channels[3] / 255 : 1];
2296
+ }
2297
+ const functional = /^(rgba?|hsla?)\(([^)]+)\)$/u.exec(text);
2298
+ if (!functional) return null;
2299
+ const parts = functional[2].split(/[\s,/]+/u).filter(Boolean);
2300
+ if (parts.length < 3 || parts.length > 4) return null;
2301
+ const alpha2 = parts[3] === void 0 ? 1 : parseUnit(parts[3], 1);
2302
+ if (functional[1].startsWith("rgb")) {
2303
+ const [r, g, b] = parts.slice(0, 3).map((part) => parseUnit(part, 255));
2304
+ if ([r, g, b, alpha2].some((value) => value === null)) return null;
2305
+ return [r, g, b, alpha2];
2306
+ }
2307
+ const hue = Number.parseFloat(parts[0]);
2308
+ const saturation = parseUnit(parts[1], 1);
2309
+ const light = parseUnit(parts[2], 1);
2310
+ if (!Number.isFinite(hue) || saturation === null || light === null || alpha2 === null) return null;
2311
+ const chroma = (1 - Math.abs(2 * light - 1)) * saturation;
2312
+ const sector = (hue % 360 + 360) % 360 / 60;
2313
+ const x = chroma * (1 - Math.abs(sector % 2 - 1));
2314
+ const [r1, g1, b1] = sector < 1 ? [chroma, x, 0] : sector < 2 ? [x, chroma, 0] : sector < 3 ? [0, chroma, x] : sector < 4 ? [0, x, chroma] : sector < 5 ? [x, 0, chroma] : [chroma, 0, x];
2315
+ const offset = light - chroma / 2;
2316
+ return [(r1 + offset) * 255, (g1 + offset) * 255, (b1 + offset) * 255, alpha2];
2317
+ }
2318
+ function parseUnit(part, scale) {
2319
+ const percentage = part.endsWith("%");
2320
+ const value = Number.parseFloat(part);
2321
+ if (!Number.isFinite(value)) return null;
2322
+ return percentage ? value / 100 * scale : value;
2323
+ }
2324
+ var oklabCache;
2325
+ var init_theme_blend = __esm({
2326
+ "src/theme-blend.ts"() {
2327
+ "use strict";
2328
+ oklabCache = /* @__PURE__ */ new Map();
2329
+ }
2330
+ });
2331
+
2332
+ // src/camera-limits.ts
2333
+ var tileflowMaxPitch;
2334
+ var init_camera_limits = __esm({
2335
+ "src/camera-limits.ts"() {
2336
+ "use strict";
2337
+ tileflowMaxPitch = 85;
2338
+ }
2339
+ });
2340
+
2341
+ // src/native-zod-object.ts
2342
+ import { z } from "zod";
2343
+ function strictNativeObject(shape) {
2344
+ const object = new z.core.$ZodObject({
2345
+ type: "object",
2346
+ shape,
2347
+ catchall: z.never()
2348
+ });
2349
+ return z.lazy(() => object);
2350
+ }
2351
+ var init_native_zod_object = __esm({
2352
+ "src/native-zod-object.ts"() {
2353
+ "use strict";
2354
+ }
2355
+ });
2356
+
2357
+ // src/manifest-schema-classic.ts
2358
+ import { z as z2 } from "zod";
2359
+ function classic(schema) {
2360
+ return schema;
2361
+ }
2362
+ var classicManifestStringOperations, classicManifestSchemaOperations;
2363
+ var init_manifest_schema_classic = __esm({
2364
+ "src/manifest-schema-classic.ts"() {
2365
+ "use strict";
2366
+ init_native_zod_object();
2367
+ classicManifestStringOperations = {
2368
+ string: () => z2.string(),
2369
+ minLength(schema, minimum2) {
2370
+ return schema.min(
2371
+ minimum2
2372
+ );
2373
+ },
2374
+ maxLength(schema, maximum2) {
2375
+ return schema.max(
2376
+ maximum2
2377
+ );
2378
+ },
2379
+ regex: (schema, pattern, message) => schema.regex(pattern, { message }),
2380
+ refine: (schema, check, message) => classic(schema).refine(check, message)
2381
+ };
2382
+ classicManifestSchemaOperations = {
2383
+ ...classicManifestStringOperations,
2384
+ number: (minimum2, maximum2) => z2.number().finite().min(minimum2).max(maximum2),
2385
+ unknown: () => z2.unknown(),
2386
+ optional: (schema) => classic(schema).optional(),
2387
+ array: (schema) => z2.array(classic(schema)),
2388
+ enum(values) {
2389
+ return z2.enum(values);
2390
+ },
2391
+ literal: (value) => z2.literal(value),
2392
+ union: (left, right) => z2.union([classic(left), classic(right)]),
2393
+ tuplePair(left, right) {
2394
+ return z2.tuple([classic(left), classic(right)]);
2395
+ },
2396
+ object(shape) {
2397
+ return strictNativeObject(shape);
2398
+ },
2399
+ record(key, value) {
2400
+ return z2.record(classic(key), classic(value));
2401
+ },
2402
+ superRefine: (schema, check) => classic(schema).superRefine(check),
2403
+ pipe: (input, output) => classic(input).pipe(classic(output))
2404
+ };
2405
+ }
2406
+ });
2407
+
2408
+ // src/portable-identity-schema.ts
2409
+ function createPortableIdentitySchemas(operations2) {
2410
+ const bounded2 = operations2.maxLength(
2411
+ operations2.minLength(operations2.string(), 1),
2412
+ tileflowPortableIdMaximumLength
2413
+ );
2414
+ const pattern = operations2.regex(
2415
+ bounded2,
2416
+ tileflowPortableIdPattern,
2417
+ "Expected lowercase kebab-case beginning with a letter"
2418
+ );
2419
+ const tileflowPortableIdSchema2 = operations2.refine(
2420
+ pattern,
2421
+ (name) => !["constructor", "prototype"].includes(name) && !/^(?:AUX|CON|NUL|PRN|COM[1-9]|LPT[1-9])$/i.test(name),
2422
+ "Expected a portable identifier that is not a reserved filename or prototype key"
2423
+ );
2424
+ const tileflowThemeNameSchema2 = operations2.refine(
2425
+ tileflowPortableIdSchema2,
2426
+ isTileflowThemeName,
2427
+ "Expected a concrete theme name; system is browser-only"
2428
+ );
2429
+ return { tileflowPortableIdSchema: tileflowPortableIdSchema2, tileflowThemeNameSchema: tileflowThemeNameSchema2 };
2430
+ }
2431
+ var init_portable_identity_schema = __esm({
2432
+ "src/portable-identity-schema.ts"() {
2433
+ "use strict";
2434
+ init_portable_identity_rules();
2435
+ }
2436
+ });
2437
+
2438
+ // src/portable-identity.ts
2439
+ var schemas, tileflowPortableIdSchema, tileflowThemeNameSchema;
2440
+ var init_portable_identity = __esm({
2441
+ "src/portable-identity.ts"() {
2442
+ "use strict";
2443
+ init_manifest_schema_classic();
2444
+ init_portable_identity_schema();
2445
+ init_portable_identity_rules();
2446
+ schemas = createPortableIdentitySchemas(classicManifestStringOperations);
2447
+ tileflowPortableIdSchema = schemas.tileflowPortableIdSchema;
2448
+ tileflowThemeNameSchema = schemas.tileflowThemeNameSchema;
2315
2449
  }
2316
2450
  });
2317
2451
 
@@ -2334,12 +2468,13 @@ var init_time_of_day_schema = __esm({
2334
2468
  });
2335
2469
 
2336
2470
  // src/manifest-types.ts
2337
- var tileflowRuntimeManifestVersion, tileflowTimeOfDayManifestVersion, tileflowRuntimeManifestLimits;
2471
+ var tileflowRuntimeManifestVersion, tileflowTimeOfDayManifestVersion, tileflowFamilyManifestVersion, tileflowRuntimeManifestLimits;
2338
2472
  var init_manifest_types = __esm({
2339
2473
  "src/manifest-types.ts"() {
2340
2474
  "use strict";
2341
2475
  tileflowRuntimeManifestVersion = 1;
2342
2476
  tileflowTimeOfDayManifestVersion = 2;
2477
+ tileflowFamilyManifestVersion = 3;
2343
2478
  tileflowRuntimeManifestLimits = Object.freeze({ maximumBytes: 1024 * 1024 });
2344
2479
  }
2345
2480
  });
@@ -2409,6 +2544,7 @@ function createTileflowRuntimeManifestSchema(primitives, operations2, identities
2409
2544
  apiUrl: operations2.optional(apiOriginSchema),
2410
2545
  defaultTheme: tileflowThemeNameSchema2,
2411
2546
  environment: operations2.optional(operations2.maxLength(safeTextSchema, 128)),
2547
+ familyUrl: operations2.optional(publicResourceUrlSchema),
2412
2548
  mapId: operations2.optional(operations2.maxLength(safeTextSchema, 128)),
2413
2549
  systemThemes: operations2.optional(
2414
2550
  operations2.object({
@@ -2490,11 +2626,20 @@ function createTileflowRuntimeManifestSchema(primitives, operations2, identities
2490
2626
  maps: operations2.record(tileflowPortableIdSchema2, manifestMapSchema),
2491
2627
  version: operations2.union(
2492
2628
  operations2.literal(tileflowRuntimeManifestVersion),
2493
- operations2.literal(2)
2629
+ operations2.union(operations2.literal(2), operations2.literal(3))
2494
2630
  )
2495
2631
  }),
2496
2632
  (manifest, context) => {
2497
2633
  const mapCount = Object.keys(manifest.maps).length;
2634
+ if (manifest.version !== 3)
2635
+ for (const [name, map] of Object.entries(manifest.maps)) {
2636
+ if (map.familyUrl !== void 0)
2637
+ context.addIssue({
2638
+ code: "custom",
2639
+ message: "familyUrl requires manifest version 3",
2640
+ path: ["maps", name, "familyUrl"]
2641
+ });
2642
+ }
2498
2643
  if (manifest.version === 1) {
2499
2644
  for (const [name, map] of Object.entries(manifest.maps)) {
2500
2645
  if (map.timeOfDay !== void 0)
@@ -2627,6 +2772,7 @@ var manifest_exports = {};
2627
2772
  __export(manifest_exports, {
2628
2773
  parseTileflowRuntimeManifest: () => parseTileflowRuntimeManifest,
2629
2774
  safeParseTileflowRuntimeManifest: () => safeParseTileflowRuntimeManifest,
2775
+ tileflowFamilyManifestVersion: () => tileflowFamilyManifestVersion,
2630
2776
  tileflowRuntimeManifestLimits: () => tileflowRuntimeManifestLimits,
2631
2777
  tileflowRuntimeManifestSchema: () => tileflowRuntimeManifestSchema,
2632
2778
  tileflowRuntimeManifestVersion: () => tileflowRuntimeManifestVersion,
@@ -2935,20 +3081,20 @@ var hexColour = /^#[0-9a-f]{6}$/iu;
2935
3081
  var unit = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
2936
3082
  function readColours(value) {
2937
3083
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
2938
- const record4 = value;
2939
- if (Object.keys(record4).length !== 4) return void 0;
2940
- const { color: color3, horizonColor, horizonBlend, glowColor } = record4;
3084
+ const record5 = value;
3085
+ if (Object.keys(record5).length !== 4) return void 0;
3086
+ const { color: color3, horizonColor, horizonBlend, glowColor } = record5;
2941
3087
  if (typeof color3 !== "string" || !hexColour.test(color3) || typeof horizonColor !== "string" || !hexColour.test(horizonColor) || typeof glowColor !== "string" || !hexColour.test(glowColor) || !unit(horizonBlend))
2942
3088
  return void 0;
2943
3089
  return { color: color3, horizonColor, horizonBlend, glowColor };
2944
3090
  }
2945
3091
  function readTileflowSky(value) {
2946
3092
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
2947
- const record4 = value;
2948
- const keys = Object.keys(record4);
2949
- if (record4.version !== 1 || keys.length !== (record4.timesOfDay === void 0 ? 3 : 4))
3093
+ const record5 = value;
3094
+ const keys = Object.keys(record5);
3095
+ if (record5.version !== 1 || keys.length !== (record5.timesOfDay === void 0 ? 3 : 4))
2950
3096
  return void 0;
2951
- const { starIntensity, glowColor, timesOfDay } = record4;
3097
+ const { starIntensity, glowColor, timesOfDay } = record5;
2952
3098
  if (!unit(starIntensity) || typeof glowColor !== "string" || !hexColour.test(glowColor))
2953
3099
  return void 0;
2954
3100
  if (timesOfDay === void 0) return { version: 1, starIntensity, glowColor };
@@ -3178,7 +3324,7 @@ function tileflowShotStateAt(shot, at) {
3178
3324
  const from = keyframes[segment];
3179
3325
  const to = keyframes[segment + 1];
3180
3326
  const linear = Math.min(1, Math.max(0, (time - from.at) / (to.at - from.at)));
3181
- const t = shot.easing === "linear" ? linear : smoothstep(linear);
3327
+ const t = shot.easing === "linear" ? linear : smoothstep2(linear);
3182
3328
  return {
3183
3329
  at: time,
3184
3330
  camera: interpolateCamera(cameraOf(from), cameraOf(to), t),
@@ -3240,7 +3386,7 @@ function wrapDegrees(value) {
3240
3386
  if (value >= -180 && value <= 180) return value;
3241
3387
  return ((value + 180) % 360 + 360) % 360 - 180;
3242
3388
  }
3243
- function smoothstep(t) {
3389
+ function smoothstep2(t) {
3244
3390
  return t * t * (3 - 2 * t);
3245
3391
  }
3246
3392
 
@@ -3415,31 +3561,31 @@ function createTileflowCompilationFailure(input) {
3415
3561
  };
3416
3562
  }
3417
3563
  function diagnosticsFromError(error, fallbackPhase) {
3418
- const record4 = asRecord(error);
3419
- const inheritedPhase = isTileflowCompilationPhase(record4.phase) ? record4.phase : fallbackPhase;
3420
- const nested = Array.isArray(record4.diagnostics) ? record4.diagnostics : void 0;
3564
+ const record5 = asRecord(error);
3565
+ const inheritedPhase = isTileflowCompilationPhase(record5.phase) ? record5.phase : fallbackPhase;
3566
+ const nested = Array.isArray(record5.diagnostics) ? record5.diagnostics : void 0;
3421
3567
  if (nested?.length) {
3422
3568
  return nested.map(
3423
- (item) => diagnosticFromRecord(asRecord(item), error, inheritedPhase, record4)
3569
+ (item) => diagnosticFromRecord(asRecord(item), error, inheritedPhase, record5)
3424
3570
  );
3425
3571
  }
3426
- const messages = Array.isArray(record4.messages) ? record4.messages : void 0;
3572
+ const messages = Array.isArray(record5.messages) ? record5.messages : void 0;
3427
3573
  if (messages?.length) {
3428
3574
  return messages.map(
3429
- (item) => diagnosticFromRecord(asRecord(item), error, inheritedPhase, record4)
3575
+ (item) => diagnosticFromRecord(asRecord(item), error, inheritedPhase, record5)
3430
3576
  );
3431
3577
  }
3432
- return [diagnosticFromRecord(record4, error, inheritedPhase)];
3433
- }
3434
- function diagnosticFromRecord(record4, fallback, fallbackPhase, inherited = {}) {
3435
- const code = inheritedString(record4, inherited, "code");
3436
- const domain = inheritedString(record4, inherited, "domain") ?? inheritedString(record4, inherited, "owner");
3437
- const message = inheritedString(record4, inherited, "message");
3438
- const path = inheritedString(record4, inherited, "path");
3439
- const phase = record4.phase ?? inherited.phase;
3440
- const severity = record4.severity ?? record4.level ?? inherited.severity ?? inherited.level;
3441
- const suggestion = inheritedString(record4, inherited, "suggestion");
3442
- const target = inheritedString(record4, inherited, "target");
3578
+ return [diagnosticFromRecord(record5, error, inheritedPhase)];
3579
+ }
3580
+ function diagnosticFromRecord(record5, fallback, fallbackPhase, inherited = {}) {
3581
+ const code = inheritedString(record5, inherited, "code");
3582
+ const domain = inheritedString(record5, inherited, "domain") ?? inheritedString(record5, inherited, "owner");
3583
+ const message = inheritedString(record5, inherited, "message");
3584
+ const path = inheritedString(record5, inherited, "path");
3585
+ const phase = record5.phase ?? inherited.phase;
3586
+ const severity = record5.severity ?? record5.level ?? inherited.severity ?? inherited.level;
3587
+ const suggestion = inheritedString(record5, inherited, "suggestion");
3588
+ const target = inheritedString(record5, inherited, "target");
3443
3589
  return {
3444
3590
  code: code ?? "TILEFLOW_COMPILATION_FAILED",
3445
3591
  ...domain ? { domain } : {},
@@ -3451,8 +3597,8 @@ function diagnosticFromRecord(record4, fallback, fallbackPhase, inherited = {})
3451
3597
  ...target ? { target } : {}
3452
3598
  };
3453
3599
  }
3454
- function inheritedString(record4, inherited, key) {
3455
- const value = record4[key] ?? inherited[key];
3600
+ function inheritedString(record5, inherited, key) {
3601
+ const value = record5[key] ?? inherited[key];
3456
3602
  return typeof value === "string" ? value : void 0;
3457
3603
  }
3458
3604
  function isTileflowCompilationPhase(value) {
@@ -3632,9 +3778,9 @@ var tileflowCompilerMetadataKeys = Object.freeze({
3632
3778
 
3633
3779
  // src/modules/addresses/index.ts
3634
3780
  function addresses(options = {}) {
3635
- return { type: "addresses", ...cloneJson(options) };
3781
+ return { type: "addresses", ...cloneJson3(options) };
3636
3782
  }
3637
- function cloneJson(value) {
3783
+ function cloneJson3(value) {
3638
3784
  return JSON.parse(JSON.stringify(value));
3639
3785
  }
3640
3786
 
@@ -4301,34 +4447,34 @@ function asRecord2(value) {
4301
4447
  // src/cartography/merge.ts
4302
4448
  init_values();
4303
4449
  function mergeTileflowDesign(base, ...overlays) {
4304
- let resolved = cloneJson3(base);
4450
+ let resolved = cloneJson4(base);
4305
4451
  for (const overlay of overlays) {
4306
4452
  resolved = mergeValue(resolved, overlay);
4307
4453
  }
4308
4454
  return resolved;
4309
4455
  }
4310
4456
  function mergeValue(base, overlay) {
4311
- if (overlay === void 0) return cloneJson3(base);
4312
- if (Array.isArray(overlay) || isTileflowExpression(overlay) || isTileflowThemeValueNode(overlay) || isTileflowZoomValue(overlay) || !isRecord2(overlay)) {
4313
- return cloneJson3(overlay);
4457
+ if (overlay === void 0) return cloneJson4(base);
4458
+ if (Array.isArray(overlay) || isTileflowExpression(overlay) || isTileflowThemeValueNode(overlay) || isTileflowZoomValue(overlay) || !isRecord4(overlay)) {
4459
+ return cloneJson4(overlay);
4314
4460
  }
4315
- const baseRecord = isRecord2(base) ? base : {};
4461
+ const baseRecord = isRecord4(base) ? base : {};
4316
4462
  const result = {};
4317
4463
  for (const key of /* @__PURE__ */ new Set([...Object.keys(baseRecord), ...Object.keys(overlay)])) {
4318
4464
  const overlayValue = overlay[key];
4319
4465
  if (overlayValue === void 0) {
4320
- if (key in baseRecord) result[key] = cloneJson3(baseRecord[key]);
4466
+ if (key in baseRecord) result[key] = cloneJson4(baseRecord[key]);
4321
4467
  continue;
4322
4468
  }
4323
4469
  result[key] = mergeValue(baseRecord[key], overlayValue);
4324
4470
  }
4325
4471
  return result;
4326
4472
  }
4327
- function cloneJson3(value) {
4473
+ function cloneJson4(value) {
4328
4474
  if (value === void 0) return value;
4329
4475
  return JSON.parse(JSON.stringify(value));
4330
4476
  }
4331
- function isRecord2(value) {
4477
+ function isRecord4(value) {
4332
4478
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
4333
4479
  }
4334
4480
 
@@ -4385,9 +4531,9 @@ function compileAddresses(request, context) {
4385
4531
 
4386
4532
  // src/modules/aeroways/index.ts
4387
4533
  function aeroways(options = {}) {
4388
- return { type: "aeroways", ...cloneJson4(options) };
4534
+ return { type: "aeroways", ...cloneJson5(options) };
4389
4535
  }
4390
- function cloneJson4(value) {
4536
+ function cloneJson5(value) {
4391
4537
  return JSON.parse(JSON.stringify(value));
4392
4538
  }
4393
4539
 
@@ -4546,9 +4692,9 @@ function compileAeroways(request, context) {
4546
4692
 
4547
4693
  // src/modules/boundaries/index.ts
4548
4694
  function boundaries(options = {}) {
4549
- return { type: "boundaries", ...cloneJson5(options) };
4695
+ return { type: "boundaries", ...cloneJson6(options) };
4550
4696
  }
4551
- function cloneJson5(value) {
4697
+ function cloneJson6(value) {
4552
4698
  return JSON.parse(JSON.stringify(value));
4553
4699
  }
4554
4700
 
@@ -4663,12 +4809,15 @@ function compileBoundaries(request, context) {
4663
4809
 
4664
4810
  // src/modules/buildings/index.ts
4665
4811
  function buildings(options = {}) {
4666
- return { type: "buildings", ...cloneJson6(options) };
4812
+ return { type: "buildings", ...cloneJson7(options) };
4667
4813
  }
4668
- function cloneJson6(value) {
4814
+ function cloneJson7(value) {
4669
4815
  return JSON.parse(JSON.stringify(value));
4670
4816
  }
4671
4817
 
4818
+ // src/building-layer-ids.ts
4819
+ var tileflowNativeBuildingsLayerId = "tileflow-buildings-native-extrusion";
4820
+
4672
4821
  // src/modules/buildings/compiler.ts
4673
4822
  init_values();
4674
4823
  function compileBuildings(request, context) {
@@ -4749,7 +4898,7 @@ function compileBuildings(request, context) {
4749
4898
  kind: "layer",
4750
4899
  layer: applyExtrusionStyle(
4751
4900
  {
4752
- id: "tileflow-buildings-3d",
4901
+ id: tileflowNativeBuildingsLayerId,
4753
4902
  type: "fill-extrusion",
4754
4903
  source,
4755
4904
  "source-layer": sourceLayer,
@@ -4998,7 +5147,7 @@ function visibleRoadClasses(options) {
4998
5147
  ].filter((roadClass) => !disabled.has(roadClass));
4999
5148
  }
5000
5149
  function roads(options = {}) {
5001
- return { type: "roads", ...cloneJson7(options) };
5150
+ return { type: "roads", ...cloneJson8(options) };
5002
5151
  }
5003
5152
  function resolveRoads(request) {
5004
5153
  return {
@@ -5047,7 +5196,7 @@ function roadStyleMetrics(options) {
5047
5196
  weightScale: { thin: 0.78, regular: 1, bold: 1.24 }[options.weight]
5048
5197
  };
5049
5198
  }
5050
- function cloneJson7(value) {
5199
+ function cloneJson8(value) {
5051
5200
  return JSON.parse(JSON.stringify(value));
5052
5201
  }
5053
5202
 
@@ -5066,7 +5215,7 @@ var defaults3 = {
5066
5215
  water: "major"
5067
5216
  };
5068
5217
  function labels(options = {}) {
5069
- return { type: "labels", ...cloneJson8(options) };
5218
+ return { type: "labels", ...cloneJson9(options) };
5070
5219
  }
5071
5220
  function resolveLabels(request) {
5072
5221
  return {
@@ -5090,7 +5239,7 @@ function visibleRoadLabelClasses(detail, roads2, explicitClasses) {
5090
5239
  const eligible = explicit ? tileflowRoadClasses.filter((roadClass) => explicit.has(roadClass)) : roadClassesWithPaths(detail, detail === "all" && roads2.extras.paths);
5091
5240
  return eligible.filter((roadClass) => visibleGeometry.has(roadClass));
5092
5241
  }
5093
- function cloneJson8(value) {
5242
+ function cloneJson9(value) {
5094
5243
  return JSON.parse(JSON.stringify(value));
5095
5244
  }
5096
5245
 
@@ -5858,9 +6007,9 @@ function symbolContribution(target, localOrder, layer, slot = "symbols", family)
5858
6007
 
5859
6008
  // src/modules/land/index.ts
5860
6009
  function land(options = {}) {
5861
- return { type: "land", ...cloneJson9(options) };
6010
+ return { type: "land", ...cloneJson10(options) };
5862
6011
  }
5863
- function cloneJson9(value) {
6012
+ function cloneJson10(value) {
5864
6013
  return JSON.parse(JSON.stringify(value));
5865
6014
  }
5866
6015
 
@@ -6221,9 +6370,9 @@ var tileflowLandformClasses = [
6221
6370
  "arete"
6222
6371
  ];
6223
6372
  function landforms(options = {}) {
6224
- return { type: "landforms", ...cloneJson10(options) };
6373
+ return { type: "landforms", ...cloneJson11(options) };
6225
6374
  }
6226
- function cloneJson10(value) {
6375
+ function cloneJson11(value) {
6227
6376
  return JSON.parse(JSON.stringify(value));
6228
6377
  }
6229
6378
 
@@ -6405,9 +6554,9 @@ var tileflowNauticalNotice = Object.freeze({
6405
6554
  text: "Not for navigation"
6406
6555
  });
6407
6556
  function nautical(options = {}) {
6408
- return { type: "nautical", ...cloneJson11(options) };
6557
+ return { type: "nautical", ...cloneJson12(options) };
6409
6558
  }
6410
- function cloneJson11(value) {
6559
+ function cloneJson12(value) {
6411
6560
  return JSON.parse(JSON.stringify(value));
6412
6561
  }
6413
6562
 
@@ -7290,7 +7439,7 @@ var tileflowNauticalV1Schema = Object.freeze({
7290
7439
  });
7291
7440
  var tileflowBathymetryNameLayerLimits = Object.freeze({ maxZoomRanges: 128 });
7292
7441
  function bathymetry(options = {}) {
7293
- return { type: "bathymetry", ...cloneJson12(options) };
7442
+ return { type: "bathymetry", ...cloneJson13(options) };
7294
7443
  }
7295
7444
  function resolveMarine(marine, apiBaseUrl = "https://api.tileflow.dev") {
7296
7445
  if (marine === void 0) return void 0;
@@ -7331,7 +7480,7 @@ function resolveBathymetry(input, apiBaseUrl) {
7331
7480
  ...advanced?.contours === void 0 ? {} : { contours: advanced.contours },
7332
7481
  display,
7333
7482
  ...advanced?.labels === void 0 ? {} : { labels: advanced.labels },
7334
- ...advanced?.vectorLayers === void 0 ? {} : { vectorLayers: cloneJson12(advanced.vectorLayers) },
7483
+ ...advanced?.vectorLayers === void 0 ? {} : { vectorLayers: cloneJson13(advanced.vectorLayers) },
7335
7484
  ...reliefVisible ? { relief: resolveBathymetryRelief(reliefConfig ?? {}, apiBaseUrl) } : {},
7336
7485
  ...usesVector ? {
7337
7486
  vector: resolveMarineSource({
@@ -7370,7 +7519,7 @@ function resolveBathymetryRelief(style, apiBaseUrl) {
7370
7519
  url
7371
7520
  },
7372
7521
  sourceId,
7373
- style: cloneJson12(style)
7522
+ style: cloneJson13(style)
7374
7523
  };
7375
7524
  }
7376
7525
  var tileflowNauticalPromoteId = Object.freeze({
@@ -7443,7 +7592,7 @@ function normalizeApiBaseUrl2(value) {
7443
7592
  }
7444
7593
  return url.toString();
7445
7594
  }
7446
- function cloneJson12(value) {
7595
+ function cloneJson13(value) {
7447
7596
  return JSON.parse(JSON.stringify(value));
7448
7597
  }
7449
7598
 
@@ -7460,9 +7609,9 @@ var tileflowNauticalLightsLimits = Object.freeze({ maxLayers: 16 });
7460
7609
  var identifierPattern = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/u;
7461
7610
  var layerIdPattern = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/u;
7462
7611
  function readTileflowNauticalLights(metadata) {
7463
- if (!isRecord3(metadata)) return void 0;
7612
+ if (!isRecord5(metadata)) return void 0;
7464
7613
  const value = metadata[tileflowNauticalLightsMetadataKey];
7465
- if (!isRecord3(value) || value.version !== 1) return void 0;
7614
+ if (!isRecord5(value) || value.version !== 1) return void 0;
7466
7615
  const { layers: layers2, source, sourceLayer } = value;
7467
7616
  if (typeof source !== "string" || !identifierPattern.test(source)) return void 0;
7468
7617
  if (typeof sourceLayer !== "string" || !identifierPattern.test(sourceLayer)) return void 0;
@@ -7485,13 +7634,13 @@ function declareTileflowNauticalLights(layers2, sourceId, sourceLayer) {
7485
7634
  return { layers: ids, source: sourceId, sourceLayer, version: 1 };
7486
7635
  }
7487
7636
  function steadyTileflowNauticalLights(style) {
7488
- const declaration = isRecord3(style.metadata) ? readTileflowNauticalLights(style.metadata) : void 0;
7489
- const hasKey = isRecord3(style.metadata) && tileflowNauticalLightsMetadataKey in style.metadata;
7637
+ const declaration = isRecord5(style.metadata) ? readTileflowNauticalLights(style.metadata) : void 0;
7638
+ const hasKey = isRecord5(style.metadata) && tileflowNauticalLightsMetadataKey in style.metadata;
7490
7639
  if (!declaration && !hasKey) return style;
7491
7640
  const metadata = { ...style.metadata };
7492
7641
  delete metadata[tileflowNauticalLightsMetadataKey];
7493
7642
  const layers2 = Array.isArray(style.layers) ? style.layers.map(
7494
- (layer) => isRecord3(layer) && isRecord3(layer.paint) && readsLightState(layer.paint) ? { ...layer, paint: steady(layer.paint) } : layer
7643
+ (layer) => isRecord5(layer) && isRecord5(layer.paint) && readsLightState(layer.paint) ? { ...layer, paint: steady(layer.paint) } : layer
7495
7644
  ) : style.layers;
7496
7645
  return { ...style, layers: layers2, metadata };
7497
7646
  }
@@ -7501,7 +7650,7 @@ function tileflowNauticalLightStateRead(state, steadyValue) {
7501
7650
  var stateKeys = new Set(Object.values(tileflowNauticalLightStates));
7502
7651
  function readsLightState(value) {
7503
7652
  if (!Array.isArray(value)) {
7504
- return isRecord3(value) ? Object.values(value).some(readsLightState) : false;
7653
+ return isRecord5(value) ? Object.values(value).some(readsLightState) : false;
7505
7654
  }
7506
7655
  if (value[0] === "literal") return false;
7507
7656
  if (value[0] === "feature-state" && typeof value[1] === "string" && stateKeys.has(value[1])) {
@@ -7517,12 +7666,12 @@ function steady(value) {
7517
7666
  }
7518
7667
  return value.map(steady);
7519
7668
  }
7520
- if (isRecord3(value)) {
7669
+ if (isRecord5(value)) {
7521
7670
  return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, steady(entry)]));
7522
7671
  }
7523
7672
  return value;
7524
7673
  }
7525
- function isRecord3(value) {
7674
+ function isRecord5(value) {
7526
7675
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
7527
7676
  }
7528
7677
 
@@ -8692,7 +8841,7 @@ function appendNauticalLayer(contributions, options) {
8692
8841
  if (point.visible === false) return;
8693
8842
  const isLabel = options.target.startsWith("nautical.labels.");
8694
8843
  if (!isLabel && point.marker && point.marker.visible !== false) {
8695
- const marker = applyCircleStyle(
8844
+ const marker2 = applyCircleStyle(
8696
8845
  base(
8697
8846
  `${options.id}-marker`,
8698
8847
  "circle",
@@ -8710,7 +8859,7 @@ function appendNauticalLayer(contributions, options) {
8710
8859
  nauticalContribution(
8711
8860
  `${options.target}.marker`,
8712
8861
  options.localOrder,
8713
- options.flash ? flashing(marker, ["circle-opacity", "circle-stroke-opacity"]) : marker,
8862
+ options.flash ? flashing(marker2, ["circle-opacity", "circle-stroke-opacity"]) : marker2,
8714
8863
  "symbols"
8715
8864
  )
8716
8865
  );
@@ -9032,7 +9181,7 @@ var tileflowPoiIconOnlyTypes = Object.freeze([
9032
9181
  "public_restroom"
9033
9182
  ]);
9034
9183
  function poi(options = {}) {
9035
- return { type: "poi", ...cloneJson13(options) };
9184
+ return { type: "poi", ...cloneJson14(options) };
9036
9185
  }
9037
9186
  function resolvePoi(request) {
9038
9187
  return {
@@ -9055,7 +9204,7 @@ function resolvePoi(request) {
9055
9204
  }
9056
9205
  };
9057
9206
  }
9058
- function cloneJson13(value) {
9207
+ function cloneJson14(value) {
9059
9208
  return JSON.parse(JSON.stringify(value));
9060
9209
  }
9061
9210
 
@@ -9160,12 +9309,12 @@ function mergePoiCategoryLayers(entries, options) {
9160
9309
  };
9161
9310
  for (const part of ["layout", "paint"]) {
9162
9311
  const records = entries.map(({ layer }) => asRecord3(layer[part]));
9163
- const properties = [...new Set(records.flatMap((record4) => Object.keys(record4)))].sort();
9312
+ const properties = [...new Set(records.flatMap((record5) => Object.keys(record5)))].sort();
9164
9313
  const merged = {};
9165
9314
  for (const property of properties) {
9166
9315
  const fallback = Object.hasOwn(varying, property) ? varying[property] : tileflowPoiLayerPropertyDefaults[property];
9167
9316
  const values = records.map(
9168
- (record4) => Object.hasOwn(record4, property) ? record4[property] : fallback
9317
+ (record5) => Object.hasOwn(record5, property) ? record5[property] : fallback
9169
9318
  );
9170
9319
  if (values.every((value) => sameJson(value, values[0]))) {
9171
9320
  if (values[0] !== void 0) merged[property] = values[0];
@@ -9687,6 +9836,7 @@ function withoutVisibility(value) {
9687
9836
  init_values();
9688
9837
 
9689
9838
  // src/themes/index.ts
9839
+ init_color_program();
9690
9840
  init_model();
9691
9841
  init_model();
9692
9842
  init_visual_semantics();
@@ -9895,8 +10045,18 @@ var baseThemeColorRoles = {
9895
10045
  };
9896
10046
  var tileflowRequiredThemeColorRoles = Object.freeze(Object.values(baseThemeColorRoles));
9897
10047
  var baseThemeColorRoleSet = new Set(tileflowRequiredThemeColorRoles);
9898
- function resolveThemeColors(theme, onDecision) {
9899
- const tokens = resolveTileflowTheme(theme).tokens.color;
10048
+ function resolveThemeColors(theme, onDecision, preservePrograms = false) {
10049
+ if (onDecision && preservePrograms) {
10050
+ const report = onDecision;
10051
+ onDecision = (decision) => report({ ...decision, value: colorProgramValue(decision.value) });
10052
+ }
10053
+ const concrete = resolveTileflowTheme(theme).tokens.color;
10054
+ const tokens = Object.fromEntries(
10055
+ Object.entries(concrete).map(([name, value]) => [
10056
+ name,
10057
+ preservePrograms ? colorProgramMarker(value, { kind: "theme-token", category: "color", token: name }) : value
10058
+ ])
10059
+ );
9900
10060
  const requireColor = (name) => {
9901
10061
  const value = tokens[name];
9902
10062
  if (value !== void 0) {
@@ -10134,10 +10294,22 @@ function relativeLuminance(color3) {
10134
10294
  return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);
10135
10295
  }
10136
10296
  function alpha(color3, opacity) {
10297
+ if (isColorProgramMarker(color3))
10298
+ return colorProgramMarker(alpha(colorProgramValue(color3), opacity), {
10299
+ kind: "theme-color",
10300
+ operation: "alpha",
10301
+ color: colorProgramOf(color3),
10302
+ opacity
10303
+ });
10137
10304
  const { a, b, g, r } = hexToRgba(color3);
10138
10305
  return `rgba(${r}, ${g}, ${b}, ${roundAlpha(a * opacity)})`;
10139
10306
  }
10140
10307
  function mix(hexA, hexB, amount) {
10308
+ if (isColorProgramMarker(hexA) || isColorProgramMarker(hexB))
10309
+ return colorProgramMarker(
10310
+ mix(colorProgramValue(hexA), colorProgramValue(hexB), amount),
10311
+ ["srgb", colorProgramOf(hexA), colorProgramOf(hexB), amount]
10312
+ );
10141
10313
  const a = hexToRgb(hexA);
10142
10314
  const b = hexToRgb(hexB);
10143
10315
  const r = Math.round(a.r + (b.r - a.r) * amount);
@@ -10150,6 +10322,7 @@ function hexToRgb(hex) {
10150
10322
  return { b, g, r };
10151
10323
  }
10152
10324
  function hexToRgba(hex) {
10325
+ hex = colorProgramValue(hex);
10153
10326
  const rgbMatch = hex.match(
10154
10327
  /^rgba?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)(?:\s*,\s*(\d+(?:\.\d+)?))?\s*\)$/
10155
10328
  );
@@ -10171,6 +10344,11 @@ function hexToRgba(hex) {
10171
10344
  };
10172
10345
  }
10173
10346
  function cssColor(hex) {
10347
+ if (isColorProgramMarker(hex))
10348
+ return colorProgramMarker(
10349
+ cssColor(colorProgramValue(hex)),
10350
+ colorProgramOf(hex)
10351
+ );
10174
10352
  const value = hex.replace("#", "");
10175
10353
  if (value.length !== 4 && value.length !== 8) {
10176
10354
  return hex;
@@ -10349,10 +10527,10 @@ function bindRepeated(curve) {
10349
10527
  for (let index = 0; index < 2; index += 1) {
10350
10528
  const json = /* @__PURE__ */ new Map();
10351
10529
  const found = /* @__PURE__ */ new Map();
10352
- const visit = (value) => {
10530
+ const visit2 = (value) => {
10353
10531
  if (!Array.isArray(value) || value[0] === "literal")
10354
10532
  return { text: JSON.stringify(value), free: true };
10355
- const parts = value.map(visit);
10533
+ const parts = value.map(visit2);
10356
10534
  const text = `[${parts.map((part) => part.text).join(",")}]`;
10357
10535
  json.set(value, text);
10358
10536
  const free = value[0] !== "zoom" && value[0] !== "var" && parts.every((part) => part.free);
@@ -10360,7 +10538,7 @@ function bindRepeated(curve) {
10360
10538
  found.set(text, (found.get(text) ?? 0) + 1);
10361
10539
  return { text, free };
10362
10540
  };
10363
- visit(tree);
10541
+ visit2(tree);
10364
10542
  let best;
10365
10543
  let saving = 256;
10366
10544
  for (const [text, count] of found) {
@@ -11216,8 +11394,8 @@ function withinClasses(value, drawn) {
11216
11394
  if ((operator === "==" || operator === "!=") && operands.length === 2) {
11217
11395
  const label = isClass(operands[0]) ? operands[1] : isClass(operands[1]) ? operands[0] : null;
11218
11396
  if (typeof label !== "string") return void 0;
11219
- const equal = among([label]);
11220
- return equal === void 0 ? void 0 : operator === "==" ? equal : !equal;
11397
+ const equal2 = among([label]);
11398
+ return equal2 === void 0 ? void 0 : operator === "==" ? equal2 : !equal2;
11221
11399
  }
11222
11400
  if (operator === "match" && operands.length === 4 && isClass(operands[0]) && typeof operands[2] === "boolean" && typeof operands[3] === "boolean") {
11223
11401
  const labels2 = Array.isArray(operands[1]) ? operands[1] : [operands[1]];
@@ -11715,6 +11893,7 @@ function cloneJson16(value) {
11715
11893
  }
11716
11894
 
11717
11895
  // src/modules/vegetation/compiler.ts
11896
+ init_color_program();
11718
11897
  init_values();
11719
11898
 
11720
11899
  // src/trees-3d.ts
@@ -11980,7 +12159,10 @@ function tileflowVegetationDescription(request, context) {
11980
12159
  return void 0;
11981
12160
  const tree = context.colors.vegetation.tree;
11982
12161
  const style = request?.threeDimensional ?? {};
11983
- const colours = (value, fallback) => Array.isArray(value) && value.length > 0 && value.every((entry) => typeof entry === "string") ? value : [...fallback];
12162
+ const colours = (value, fallback) => {
12163
+ const values = Array.isArray(value) ? value.map((entry) => toMapLibreStyleValue(entry)) : [];
12164
+ return values.length && values.every((entry) => typeof entry === "string" || isColorProgramMarker(entry)) ? values : [...fallback];
12165
+ };
11984
12166
  const number = (value) => {
11985
12167
  const resolved = value === void 0 ? void 0 : toMapLibreStyleValue(value);
11986
12168
  return typeof resolved === "number" && resolved > 0 ? resolved : 1;
@@ -12007,7 +12189,10 @@ function tileflowVegetationDescription(request, context) {
12007
12189
  hedge: field2("hedge", tileflowWorldTreeFields.hedge)
12008
12190
  },
12009
12191
  colors: {
12010
- bark: typeof style.barkColor === "string" ? style.barkColor : tree.bark,
12192
+ bark: (() => {
12193
+ const value = toMapLibreStyleValue(style.barkColor ?? tree.bark);
12194
+ return typeof value === "string" || isColorProgramMarker(value) ? value : tree.bark;
12195
+ })(),
12011
12196
  broadleaf: colours(style.broadleafColors, tree.broadleaf),
12012
12197
  conifer: colours(style.coniferColors, tree.conifer),
12013
12198
  palm: colours(style.palmColors, tree.palm),
@@ -12049,7 +12234,7 @@ function tileflowVegetationDescription(request, context) {
12049
12234
  })(),
12050
12235
  ...(() => {
12051
12236
  const edgeColor = style.edgeColor === void 0 ? void 0 : toMapLibreStyleValue(style.edgeColor);
12052
- return typeof edgeColor === "string" ? { edgeColor } : {};
12237
+ return typeof edgeColor === "string" || isColorProgramMarker(edgeColor) ? { edgeColor } : {};
12053
12238
  })(),
12054
12239
  unknownForms: style.unknownForms ?? tileflowDefaultUnknownTreeForms,
12055
12240
  regions: style.regions ?? [],
@@ -12893,8 +13078,8 @@ function assertProvidedServices(domain, services) {
12893
13078
  }
12894
13079
  }
12895
13080
  function checkedModuleRecord(entries) {
12896
- const record4 = Object.fromEntries(entries);
12897
- const actual = Object.keys(record4).sort(compareCodeUnits2);
13081
+ const record5 = Object.fromEntries(entries);
13082
+ const actual = Object.keys(record5).sort(compareCodeUnits2);
12898
13083
  if (actual.length !== tileflowSemanticDomainRegistry.length || actual.some(
12899
13084
  (name, index) => name !== [...tileflowSemanticDomainRegistry.map(({ name: name2 }) => name2)].sort(compareCodeUnits2)[index]
12900
13085
  )) {
@@ -12902,7 +13087,7 @@ function checkedModuleRecord(entries) {
12902
13087
  "Tileflow semantic domain registry did not produce one value for every module."
12903
13088
  );
12904
13089
  }
12905
- return record4;
13090
+ return record5;
12906
13091
  }
12907
13092
  function assertRegistryIsValid() {
12908
13093
  const names = /* @__PURE__ */ new Set();
@@ -13759,7 +13944,7 @@ var defaultBuildingFloodLight = Object.freeze({
13759
13944
  });
13760
13945
  var defaultModelEmissionGradient = [0, 1, 1, 1, 0];
13761
13946
  function readBuildingFloodLight(value) {
13762
- if (!record(value)) return void 0;
13947
+ if (!record2(value)) return void 0;
13763
13948
  const defaults4 = defaultBuildingFloodLight;
13764
13949
  const radius = value.groundRadius;
13765
13950
  return {
@@ -13773,11 +13958,11 @@ function readBuildingFloodLight(value) {
13773
13958
  };
13774
13959
  }
13775
13960
  function readModelRoleLighting(value) {
13776
- if (!record(value)) return void 0;
13961
+ if (!record2(value)) return void 0;
13777
13962
  const result = {};
13778
13963
  for (const role of Object.keys(tileflowModelLightingRoles)) {
13779
13964
  const entry = value[role];
13780
- if (!record(entry)) continue;
13965
+ if (!record2(entry)) continue;
13781
13966
  const gradient = entry.emissiveHeight;
13782
13967
  result[role] = {
13783
13968
  emissiveStrength: bounded(entry.emissiveStrength, 0, 5, 0),
@@ -13800,7 +13985,7 @@ function readModelRoleLighting(value) {
13800
13985
  return Object.keys(result).length ? result : void 0;
13801
13986
  }
13802
13987
  function readRoleColorVariation(value) {
13803
- if (!record(value)) return {};
13988
+ if (!record2(value)) return {};
13804
13989
  for (const [key, max] of [
13805
13990
  ["hue", 360],
13806
13991
  ["saturation", 100],
@@ -13816,16 +14001,16 @@ function readRoleColorVariation(value) {
13816
14001
  function bounded(value, min, max, fallback) {
13817
14002
  return typeof value === "number" && Number.isFinite(value) && value >= min && value <= max ? value : fallback;
13818
14003
  }
13819
- function record(value) {
14004
+ function record2(value) {
13820
14005
  return typeof value === "object" && value !== null && !Array.isArray(value);
13821
14006
  }
13822
14007
 
13823
14008
  // src/buildings-3d-surfaces.ts
13824
14009
  function readTileflowBuildingSurfaces(value) {
13825
- if (!record2(value)) return void 0;
14010
+ if (!record3(value)) return void 0;
13826
14011
  const number = (value2, min, max, fallback) => typeof value2 === "number" && Number.isFinite(value2) && value2 >= min && value2 <= max ? value2 : fallback;
13827
14012
  const result = {};
13828
- if (record2(value.walls) && value.walls.kind === "brick") {
14013
+ if (record3(value.walls) && value.walls.kind === "brick") {
13829
14014
  const size = Array.isArray(value.walls.size) ? value.walls.size : [];
13830
14015
  const width = number(size[0], 0.5, 100, 6);
13831
14016
  const height = number(size[1], 0.5, 100, 3);
@@ -13839,7 +14024,7 @@ function readTileflowBuildingSurfaces(value) {
13839
14024
  contrast: number(value.walls.contrast, 0, 1, 0.2)
13840
14025
  };
13841
14026
  }
13842
- if (record2(value.roof) && value.roof.kind === "tiles") {
14027
+ if (record3(value.roof) && value.roof.kind === "tiles") {
13843
14028
  const size = number(value.roof.size, 0.5, 100, 8);
13844
14029
  result.roof = {
13845
14030
  kind: "tiles",
@@ -13848,14 +14033,14 @@ function readTileflowBuildingSurfaces(value) {
13848
14033
  contrast: number(value.roof.contrast, 0, 1, 0.1)
13849
14034
  };
13850
14035
  }
13851
- if (record2(value.cornice))
14036
+ if (record3(value.cornice))
13852
14037
  result.cornice = {
13853
14038
  height: number(value.cornice.height, 0.05, 5, 0.6),
13854
14039
  contrast: number(value.cornice.contrast, 0, 1, 0.2)
13855
14040
  };
13856
14041
  return Object.keys(result).length ? result : void 0;
13857
14042
  }
13858
- function record2(value) {
14043
+ function record3(value) {
13859
14044
  return value !== null && typeof value === "object" && !Array.isArray(value);
13860
14045
  }
13861
14046
 
@@ -13927,8 +14112,9 @@ function readTileflowBuildings3D(metadata) {
13927
14112
  };
13928
14113
  }
13929
14114
  function readTileflowBuildingsEntrance(value) {
14115
+ if (value === false) return { entrance: false };
13930
14116
  if (!isRecord7(value)) return {};
13931
- const band = (entry) => Array.isArray(entry) && entry.length === 2 && entry.every((zoom3) => typeof zoom3 === "number" && zoom3 >= 0 && zoom3 <= 24) && entry[0] < entry[1] ? [entry[0], entry[1]] : void 0;
14117
+ const band = (entry) => Array.isArray(entry) && entry.length === 2 && entry.every((zoom3) => typeof zoom3 === "number" && zoom3 >= 0 && zoom3 <= 24) && entry[0] <= entry[1] ? [entry[0], entry[1]] : void 0;
13932
14118
  const buildings2 = band(value.buildings);
13933
14119
  const models = band(value.models);
13934
14120
  return buildings2 || models ? { entrance: { ...buildings2 ? { buildings: buildings2 } : {}, ...models ? { models } : {} } } : {};
@@ -13981,6 +14167,9 @@ function isRecord7(value) {
13981
14167
  // src/camera.ts
13982
14168
  var tileflowCameraMetadataKey = "tileflow:camera";
13983
14169
 
14170
+ // src/cartography/streets.ts
14171
+ init_color_program();
14172
+
13984
14173
  // src/data/requirements.ts
13985
14174
  import { convertFilter } from "@maplibre/maplibre-gl-style-spec";
13986
14175
  function inferTileflowDataRequirements(style, options = {}) {
@@ -14120,11 +14309,11 @@ function collectFieldAccesses(value, fields2, dynamicAccesses, path) {
14120
14309
  return;
14121
14310
  }
14122
14311
  if (!value || typeof value !== "object") return;
14123
- const record4 = value;
14124
- if (typeof record4.property === "string" && Array.isArray(record4.stops)) {
14125
- fields2.set(record4.property, fields2.get(record4.property));
14312
+ const record5 = value;
14313
+ if (typeof record5.property === "string" && Array.isArray(record5.stops)) {
14314
+ fields2.set(record5.property, fields2.get(record5.property));
14126
14315
  }
14127
- for (const [key, child] of Object.entries(record4)) {
14316
+ for (const [key, child] of Object.entries(record5)) {
14128
14317
  collectFieldAccesses(child, fields2, dynamicAccesses, `${path}.${key}`);
14129
14318
  }
14130
14319
  }
@@ -14530,7 +14719,7 @@ function visitExpression(value, path, variables, issues) {
14530
14719
  const branchLimit = tileflowDataExpressionLimits.maxBranches;
14531
14720
  const operandLimit = tileflowDataExpressionLimits.maxOperands;
14532
14721
  const stopLimit = tileflowDataExpressionLimits.maxStops;
14533
- const visit = (entry, index, scope = variables) => visitExpressionNode(entry, [...path, index], scope, issues);
14722
+ const visit2 = (entry, index, scope = variables) => visitExpressionNode(entry, [...path, index], scope, issues);
14534
14723
  const exact = (length) => {
14535
14724
  if (value.length === length) return true;
14536
14725
  addExpressionIssue(
@@ -14592,18 +14781,18 @@ function visitExpression(value, path, variables, issues) {
14592
14781
  if (!exact(4)) return;
14593
14782
  if (!isNonEmptyString(value[1])) {
14594
14783
  addExpressionIssue(issues, [...path, 1], "Variable names must be non-empty strings.");
14595
- visit(value[2], 2);
14596
- visit(value[3], 3);
14784
+ visit2(value[2], 2);
14785
+ visit2(value[3], 3);
14597
14786
  return;
14598
14787
  }
14599
- visit(value[2], 2);
14600
- visit(value[3], 3, /* @__PURE__ */ new Set([...variables, value[1]]));
14788
+ visit2(value[2], 2);
14789
+ visit2(value[3], 3, /* @__PURE__ */ new Set([...variables, value[1]]));
14601
14790
  return;
14602
14791
  }
14603
14792
  case "abs":
14604
14793
  case "!":
14605
14794
  case "to-string": {
14606
- if (exact(2)) visit(value[1], 1);
14795
+ if (exact(2)) visit2(value[1], 1);
14607
14796
  return;
14608
14797
  }
14609
14798
  case "boolean":
@@ -14612,7 +14801,7 @@ function visitExpression(value, path, variables, issues) {
14612
14801
  addExpressionIssue(issues, path, `Expression ${operator} expects one or two arguments.`);
14613
14802
  return;
14614
14803
  }
14615
- for (let index = 1; index < value.length; index += 1) visit(value[index], index);
14804
+ for (let index = 1; index < value.length; index += 1) visit2(value[index], index);
14616
14805
  return;
14617
14806
  }
14618
14807
  case "-":
@@ -14625,8 +14814,8 @@ function visitExpression(value, path, variables, issues) {
14625
14814
  case ">":
14626
14815
  case ">=": {
14627
14816
  if (!exact(3)) return;
14628
- visit(value[1], 1);
14629
- visit(value[2], 2);
14817
+ visit2(value[1], 1);
14818
+ visit2(value[2], 2);
14630
14819
  return;
14631
14820
  }
14632
14821
  case "+":
@@ -14644,7 +14833,7 @@ function visitExpression(value, path, variables, issues) {
14644
14833
  );
14645
14834
  return;
14646
14835
  }
14647
- for (let index = 1; index < value.length; index += 1) visit(value[index], index);
14836
+ for (let index = 1; index < value.length; index += 1) visit2(value[index], index);
14648
14837
  return;
14649
14838
  }
14650
14839
  case "all":
@@ -14658,7 +14847,7 @@ function visitExpression(value, path, variables, issues) {
14658
14847
  );
14659
14848
  return;
14660
14849
  }
14661
- for (let index = 1; index < value.length; index += 1) visit(value[index], index);
14850
+ for (let index = 1; index < value.length; index += 1) visit2(value[index], index);
14662
14851
  return;
14663
14852
  }
14664
14853
  case "case": {
@@ -14678,7 +14867,7 @@ function visitExpression(value, path, variables, issues) {
14678
14867
  );
14679
14868
  return;
14680
14869
  }
14681
- for (let index = 1; index < value.length; index += 1) visit(value[index], index);
14870
+ for (let index = 1; index < value.length; index += 1) visit2(value[index], index);
14682
14871
  return;
14683
14872
  }
14684
14873
  case "match": {
@@ -14698,10 +14887,10 @@ function visitExpression(value, path, variables, issues) {
14698
14887
  );
14699
14888
  return;
14700
14889
  }
14701
- visit(value[1], 1);
14890
+ visit2(value[1], 1);
14702
14891
  validateSerializedMatchLabels(value, path, issues);
14703
- for (let index = 3; index < value.length; index += 2) visit(value[index], index);
14704
- visit(value.at(-1), value.length - 1);
14892
+ for (let index = 3; index < value.length; index += 2) visit2(value[index], index);
14893
+ visit2(value.at(-1), value.length - 1);
14705
14894
  return;
14706
14895
  }
14707
14896
  case "step": {
@@ -14717,10 +14906,10 @@ function visitExpression(value, path, variables, issues) {
14717
14906
  addExpressionIssue(issues, path, `Expression step exceeds ${stopLimit} stops.`);
14718
14907
  return;
14719
14908
  }
14720
- visit(value[1], 1);
14721
- visit(value[2], 2);
14909
+ visit2(value[1], 1);
14910
+ visit2(value[2], 2);
14722
14911
  validateSerializedStops(value, 3, path, issues);
14723
- for (let index = 4; index < value.length; index += 2) visit(value[index], index);
14912
+ for (let index = 4; index < value.length; index += 2) visit2(value[index], index);
14724
14913
  return;
14725
14914
  }
14726
14915
  case "interpolate": {
@@ -14737,9 +14926,9 @@ function visitExpression(value, path, variables, issues) {
14737
14926
  return;
14738
14927
  }
14739
14928
  validateInterpolationArray(value[1], [...path, 1], issues);
14740
- visit(value[2], 2);
14929
+ visit2(value[2], 2);
14741
14930
  validateSerializedStops(value, 3, path, issues);
14742
- for (let index = 4; index < value.length; index += 2) visit(value[index], index);
14931
+ for (let index = 4; index < value.length; index += 2) visit2(value[index], index);
14743
14932
  return;
14744
14933
  }
14745
14934
  default:
@@ -14874,6 +15063,262 @@ var expr = Object.freeze({
14874
15063
  zoom: zoom2
14875
15064
  });
14876
15065
 
15066
+ // src/cartography/expression-compaction.ts
15067
+ init_expression_operators();
15068
+ init_color_program();
15069
+ function compactTileflowExpression(value, scope = /* @__PURE__ */ new Map()) {
15070
+ if (isColorProgramMarker(value)) return value;
15071
+ if (!isExpression(value) || value[0] === "literal" || value[0] === "var") return value;
15072
+ if (value[0] === "let") return compactLet(value, scope);
15073
+ const node2 = value.map((entry, index) => {
15074
+ if (value[0] === "match" && index >= 2 && index < value.length - 1 && index % 2 === 0)
15075
+ return entry;
15076
+ if (index === 0 || value[0] === "interpolate" && index === 1) return entry;
15077
+ return compactTileflowExpression(entry, scope);
15078
+ });
15079
+ if (node2[0] === "all" || node2[0] === "any") {
15080
+ const operands = node2.slice(1).flatMap(
15081
+ (entry) => Array.isArray(entry) && entry[0] === node2[0] ? entry.slice(1) : [entry]
15082
+ );
15083
+ return [node2[0], ...operands];
15084
+ }
15085
+ if (node2[0] === "case") {
15086
+ while (node2.length >= 4 && equal(node2.at(-2), node2.at(-1)) && totalBoolean(node2.at(-3), scope))
15087
+ node2.splice(node2.length - 3, 2);
15088
+ return node2.length === 2 ? node2[1] : compactClassDispatch(node2, scope);
15089
+ }
15090
+ if (node2[0] === "match" && totalValue(node2[1], scope)) {
15091
+ for (let index = node2.length - 3; index >= 2; index -= 2)
15092
+ if (equal(node2[index + 1], node2.at(-1))) node2.splice(index, 2);
15093
+ return node2.length === 3 ? node2[2] : node2;
15094
+ }
15095
+ if (node2[0] === "*" && node2.slice(1).every((entry) => numeric(entry, scope))) {
15096
+ const operands = node2.slice(1).filter((entry) => entry !== 1);
15097
+ return operands.length === 0 ? 1 : operands.length === 1 ? operands[0] : ["*", ...operands];
15098
+ }
15099
+ if (node2[0] === "interpolate" && numeric(node2[2], scope)) {
15100
+ const outputs2 = node2.filter((_, index) => index >= 4 && index % 2 === 0);
15101
+ if (outputs2.length > 0 && outputs2.every((entry) => equal(entry, outputs2[0]))) return outputs2[0];
15102
+ if (typeof node2[2] === "number" && outputs2.every((output) => typeof output === "number" && Number.isFinite(output))) {
15103
+ for (let index = 3; index < node2.length - 1; index += 2)
15104
+ if (node2[index] === node2[2]) return node2[index + 1];
15105
+ }
15106
+ }
15107
+ return node2;
15108
+ }
15109
+ function compactClassDispatch(node2, scope) {
15110
+ if (node2.length < 6) return node2;
15111
+ const branches = [];
15112
+ const labels2 = /* @__PURE__ */ new Set();
15113
+ let labelType;
15114
+ for (let index = 1; index < node2.length - 1; index += 2) {
15115
+ const condition = node2[index];
15116
+ const parts = Array.isArray(condition) && condition[0] === "all" ? condition.slice(1) : [condition];
15117
+ const at = parts.findIndex(
15118
+ (part) => Array.isArray(part) && part[0] === "match" && part.length === 5 && part[3] === true && part[4] === false
15119
+ );
15120
+ if (at < 0 || !parts.every((part) => totalBoolean(part, scope))) return node2;
15121
+ const match4 = parts[at];
15122
+ const ownLabels = Array.isArray(match4[2]) ? match4[2] : [match4[2]];
15123
+ if (ownLabels.length === 0) return node2;
15124
+ for (const label of ownLabels) {
15125
+ labelType ??= typeof label;
15126
+ if (!["number", "string"].includes(labelType) || typeof label !== labelType) return node2;
15127
+ const key = JSON.stringify(label);
15128
+ if (labels2.has(key)) return node2;
15129
+ labels2.add(key);
15130
+ }
15131
+ branches.push({
15132
+ input: match4[1],
15133
+ labels: ownLabels,
15134
+ guards: parts.filter((_, i) => i !== at),
15135
+ output: node2[index + 1]
15136
+ });
15137
+ }
15138
+ const first = branches[0];
15139
+ if (!branches.every(
15140
+ (branch) => equal(branch.input, first.input) && equal(branch.guards, first.guards)
15141
+ ))
15142
+ return node2;
15143
+ const fallback = node2.at(-1);
15144
+ const match3 = [
15145
+ "match",
15146
+ first.input,
15147
+ ...branches.flatMap((branch) => [branch.labels, branch.output]),
15148
+ fallback
15149
+ ];
15150
+ const result = first.guards.length ? [
15151
+ "case",
15152
+ first.guards.length === 1 ? first.guards[0] : ["all", ...first.guards],
15153
+ match3,
15154
+ fallback
15155
+ ] : match3;
15156
+ return JSON.stringify(result).length < JSON.stringify(node2).length ? result : node2;
15157
+ }
15158
+ function compactLet(node2, scope) {
15159
+ const inner = new Map(scope);
15160
+ const bindings = [];
15161
+ for (let index = 1; index < node2.length - 1; index += 2) {
15162
+ const name = node2[index];
15163
+ if (typeof name !== "string") return node2;
15164
+ const value = compactTileflowExpression(node2[index + 1], scope);
15165
+ bindings.push(name, value);
15166
+ inner.set(name, { value, scope });
15167
+ }
15168
+ const result = compactTileflowExpression(node2.at(-1), inner);
15169
+ if (Array.isArray(result) && result[0] === "var" && bindings.includes(result[1])) {
15170
+ for (let index = bindings.length - 2; index >= 0; index -= 2)
15171
+ if (bindings[index] === result[1]) return bindings[index + 1];
15172
+ }
15173
+ const retained = [];
15174
+ for (let index = 0; index < bindings.length; index += 2)
15175
+ if (usesVariable(result, bindings[index]))
15176
+ retained.push(bindings[index], bindings[index + 1]);
15177
+ return retained.length ? ["let", ...retained, result] : result;
15178
+ }
15179
+ function usesVariable(value, name) {
15180
+ if (!Array.isArray(value) || value[0] === "literal") return false;
15181
+ if (value[0] === "var") return value[1] === name;
15182
+ return value.some((entry) => usesVariable(entry, name));
15183
+ }
15184
+ function numeric(value, scope) {
15185
+ if (typeof value === "number") return Number.isFinite(value);
15186
+ if (!Array.isArray(value)) return false;
15187
+ if (value[0] === "zoom") return value.length === 1;
15188
+ if (value[0] === "var" && typeof value[1] === "string") {
15189
+ const binding = scope.get(value[1]);
15190
+ return binding !== void 0 && numeric(binding.value, binding.scope);
15191
+ }
15192
+ if (value[0] === "*" || value[0] === "+" || value[0] === "-" || value[0] === "max" || value[0] === "min")
15193
+ return value.length > 1 && value.slice(1).every((entry) => numeric(entry, scope));
15194
+ if (value[0] === "case")
15195
+ return value.slice(1, -1).every(
15196
+ (entry, index) => index % 2 === 0 ? totalBoolean(entry, scope) : numeric(entry, scope)
15197
+ ) && numeric(value.at(-1), scope);
15198
+ if (value[0] === "interpolate")
15199
+ return numeric(value[2], scope) && value.filter((_, index) => index >= 4 && index % 2 === 0).every((entry) => numeric(entry, scope));
15200
+ return false;
15201
+ }
15202
+ function totalBoolean(value, scope) {
15203
+ if (typeof value === "boolean") return true;
15204
+ if (!Array.isArray(value)) return false;
15205
+ if (value[0] === "has") return value.length === 2 && typeof value[1] === "string";
15206
+ if (value[0] === "!" && value.length === 2) return totalBoolean(value[1], scope);
15207
+ if (value[0] === "all" || value[0] === "any")
15208
+ return value.slice(1).every((entry) => totalBoolean(entry, scope));
15209
+ if ((value[0] === "==" || value[0] === "!=") && value.length === 3)
15210
+ return totalValue(value[1], scope) && totalValue(value[2], scope);
15211
+ if (value[0] === "match")
15212
+ return totalValue(value[1], scope) && value.filter((_, index) => index >= 3 && index % 2 === 1).every((entry) => totalBoolean(entry, scope)) && totalBoolean(value.at(-1), scope);
15213
+ return false;
15214
+ }
15215
+ function totalValue(value, scope) {
15216
+ if (value === null || ["string", "boolean", "number"].includes(typeof value)) return true;
15217
+ if (!Array.isArray(value)) return false;
15218
+ if (value[0] === "literal") return true;
15219
+ if (value[0] === "get" || value[0] === "has")
15220
+ return value.length === 2 && typeof value[1] === "string";
15221
+ if (value[0] === "geometry-type" || value[0] === "id") return value.length === 1;
15222
+ if (value[0] === "to-string") return value.length === 2 && totalValue(value[1], scope);
15223
+ return numeric(value, scope) || totalBoolean(value, scope);
15224
+ }
15225
+ function isExpression(value) {
15226
+ return Array.isArray(value) && isMapLibreExpressionOperator(value[0]);
15227
+ }
15228
+ function equal(left, right) {
15229
+ return JSON.stringify(left) === JSON.stringify(right);
15230
+ }
15231
+ function shareExpressions(value) {
15232
+ if (!isExpression(value)) return value;
15233
+ let result = value;
15234
+ const bindings = [];
15235
+ const serialized = /* @__PURE__ */ new WeakMap();
15236
+ const textOf = (node2) => {
15237
+ let text = serialized.get(node2);
15238
+ if (text === void 0) {
15239
+ text = JSON.stringify(node2);
15240
+ serialized.set(node2, text);
15241
+ }
15242
+ return text;
15243
+ };
15244
+ const originalText = textOf(value);
15245
+ for (let index = 0; index < 16; index += 1) {
15246
+ let name = `__tf${index}`;
15247
+ while (originalText.includes(JSON.stringify(name))) name += "_";
15248
+ const candidates = /* @__PURE__ */ new Map();
15249
+ visit(result, (node2) => {
15250
+ if (textOf(node2).length < 48 || !selfContained(node2) || !(totalBoolean(node2, /* @__PURE__ */ new Map()) || numeric(node2, /* @__PURE__ */ new Map()) || stringOutput(node2)))
15251
+ return node2;
15252
+ const key = textOf(node2);
15253
+ const entry = candidates.get(key);
15254
+ if (entry) entry.count += 1;
15255
+ else candidates.set(key, { node: node2, count: 1 });
15256
+ return node2;
15257
+ });
15258
+ const reference = ["var", name];
15259
+ const referenceLength = JSON.stringify(reference).length;
15260
+ const best = [...candidates.entries()].map(([key, entry]) => ({
15261
+ key,
15262
+ ...entry,
15263
+ saving: (entry.count - 1) * key.length - entry.count * referenceLength - name.length - 8
15264
+ })).filter(({ count, saving }) => count > 1 && saving > 0).sort((a, b) => b.saving - a.saving || (a.key < b.key ? -1 : a.key > b.key ? 1 : 0))[0];
15265
+ if (!best) break;
15266
+ bindings.push(name, best.node);
15267
+ result = visit(result, (node2) => textOf(node2) === best.key ? reference : node2);
15268
+ }
15269
+ if (!bindings.length) return value;
15270
+ const shared = ["let", ...bindings, result];
15271
+ return JSON.stringify(shared).length < originalText.length ? shared : value;
15272
+ }
15273
+ function stringOutput(value) {
15274
+ if (typeof value === "string") return true;
15275
+ if (!Array.isArray(value)) return false;
15276
+ if (value[0] === "case")
15277
+ return value.slice(1, -1).every(
15278
+ (entry, index) => index % 2 === 0 ? totalBoolean(entry, /* @__PURE__ */ new Map()) : stringOutput(entry)
15279
+ ) && stringOutput(value.at(-1));
15280
+ if (value[0] === "match")
15281
+ return totalValue(value[1], /* @__PURE__ */ new Map()) && value.filter((_, index) => index >= 3 && index % 2 === 1).every(stringOutput) && stringOutput(value.at(-1));
15282
+ return false;
15283
+ }
15284
+ function selfContained(value) {
15285
+ if (!isExpression(value) || value[0] === "literal") return true;
15286
+ if (value[0] === "var" || value[0] === "let" || value[0] === "zoom") return false;
15287
+ return value.slice(1).every(selfContained);
15288
+ }
15289
+ function visit(value, transform) {
15290
+ if (!isExpression(value) || value[0] === "literal") return value;
15291
+ const replaced = transform(value);
15292
+ if (replaced !== value) return replaced;
15293
+ let changed = false;
15294
+ const children = value.map((entry, index) => {
15295
+ if (value[0] === "match" && index >= 2 && index < value.length - 1 && index % 2 === 0)
15296
+ return entry;
15297
+ const child = isExpression(entry) ? visit(entry, transform) : entry;
15298
+ changed ||= child !== entry;
15299
+ return child;
15300
+ });
15301
+ return changed ? children : value;
15302
+ }
15303
+ function compactTileflowLayer(layer) {
15304
+ const result = { ...layer };
15305
+ for (const group of ["paint", "layout"]) {
15306
+ const properties = layer[group];
15307
+ if (properties && typeof properties === "object" && !Array.isArray(properties))
15308
+ Object.assign(result, {
15309
+ [group]: Object.fromEntries(
15310
+ Object.entries(properties).map(([key, value]) => [
15311
+ key,
15312
+ // Asset discovery enumerates these outputs before resource preparation. Keep
15313
+ // their branches visible instead of hiding an image/font choice in a binding.
15314
+ /(?:image|pattern|font)$/u.test(key) ? compactTileflowExpression(value) : shareExpressions(compactTileflowExpression(value))
15315
+ ])
15316
+ )
15317
+ });
15318
+ }
15319
+ return result;
15320
+ }
15321
+
14877
15322
  // src/cartography/semantic-bindings.ts
14878
15323
  var completeSemanticDataContract = tileflowWorldV1Schema();
14879
15324
  var tileflowSemanticFieldNames = Object.freeze(
@@ -15076,7 +15521,7 @@ function materializeTileflowLayerFamilyIR(template, semantics) {
15076
15521
  }
15077
15522
  function lowerTileflowDomainIR(families, data, resolvePhysicalId = physicalLayerIdForSemanticKey) {
15078
15523
  const layers2 = families.map(
15079
- (family) => bindSemanticReferences(emitLayer(family, resolvePhysicalId), data)
15524
+ (family) => compactTileflowLayer(bindSemanticReferences(emitLayer(family, resolvePhysicalId), data))
15080
15525
  );
15081
15526
  assertNoSemanticReferences(layers2);
15082
15527
  const ids = /* @__PURE__ */ new Set();
@@ -15126,6 +15571,7 @@ function emitLayer(family, resolvePhysicalId) {
15126
15571
  function physicalLayerIdForSemanticKey(semanticKey) {
15127
15572
  const exact = exactPhysicalLayerIds[semanticKey];
15128
15573
  if (exact) return exact;
15574
+ if (semanticKey === "buildings.render.extrusion") return tileflowNativeBuildingsLayerId;
15129
15575
  let match3 = /^([A-Za-z0-9_-]+)\.render\.([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)$/u.exec(
15130
15576
  semanticKey
15131
15577
  );
@@ -15224,7 +15670,7 @@ function physicalLayerIdForSemanticKey(semanticKey) {
15224
15670
  var exactPhysicalLayerIds = Object.freeze({
15225
15671
  "addresses.labels": "tileflow-addresses-labels",
15226
15672
  "aeroways.runwayRef": "tileflow-aeroway-runway-ref",
15227
- "buildings.extrusion": "tileflow-buildings-3d",
15673
+ "buildings.extrusion": tileflowNativeBuildingsLayerId,
15228
15674
  "labels.aerodrome": "tileflow-label-aerodrome",
15229
15675
  "labels.junctions": "tileflow-label-road-junction",
15230
15676
  "land.background": "tileflow-background",
@@ -17476,7 +17922,7 @@ function boundedThemeNumberSchema(min, max) {
17476
17922
  }).strict()
17477
17923
  ]);
17478
17924
  }
17479
- var entranceBandSchema = z7.tuple([z7.number().finite().min(0).max(24), z7.number().finite().min(0).max(24)]).refine(([from, to]) => from < to, "A band starts at a lower zoom than it ends.");
17925
+ var entranceBandSchema = z7.tuple([z7.number().finite().min(0).max(24), z7.number().finite().min(0).max(24)]).refine(([from, to]) => from <= to, "A band cannot end before it starts.");
17480
17926
  var modelRoleLightingSchema = z7.object({
17481
17927
  emissiveStrength: boundedThemeNumberSchema(0, 5).optional(),
17482
17928
  emissiveVariation: boundedThemeNumberSchema(0, 1).optional(),
@@ -17536,7 +17982,7 @@ var buildingsModuleSchema = z7.object({
17536
17982
  lamp: modelRoleLightingSchema.optional(),
17537
17983
  logo: modelRoleLightingSchema.optional()
17538
17984
  }).strict().optional(),
17539
- entrance: z7.object({ buildings: entranceBandSchema.optional(), models: entranceBandSchema.optional() }).strict().optional(),
17985
+ entrance: z7.object({ buildings: entranceBandSchema.optional(), models: entranceBandSchema.optional() }).strict().or(z7.literal(false)).optional(),
17540
17986
  flatFootprints: z7.boolean().optional(),
17541
17987
  occlusion: z7.boolean().optional(),
17542
17988
  groundOcclusion: boundedThemeNumberSchema(0, 1).optional(),
@@ -18575,7 +19021,13 @@ var systemThemesSchema = z7.object({ dark: tileflowThemeNameSchema, light: tilef
18575
19021
  var resolvedTileflowMapSchema = z7.object({
18576
19022
  atmosphere: atmosphereSchema.optional(),
18577
19023
  boundaryAreas: boundaryAreasSchema.optional(),
18578
- camera: z7.object({ adaptivePerspective: z7.boolean().optional() }).strict().optional(),
19024
+ camera: z7.object({
19025
+ adaptivePerspective: z7.boolean().optional(),
19026
+ pitchLimit: z7.object({
19027
+ maxZoom: z7.number().finite().min(0).max(24),
19028
+ maxPitch: z7.number().finite().min(0).max(tileflowMaxPitch)
19029
+ }).strict().optional()
19030
+ }).strict().optional(),
18579
19031
  data: dataSchema.optional(),
18580
19032
  defaultTheme: tileflowThemeNameSchema,
18581
19033
  fonts: fontDirectoriesSchema.optional(),
@@ -18750,8 +19202,8 @@ function isSafeGlyphUrl(value) {
18750
19202
  function validateZoomRanges(value, context, path = []) {
18751
19203
  if (!value || typeof value !== "object") return;
18752
19204
  if (!Array.isArray(value)) {
18753
- const record4 = value;
18754
- if (typeof record4.minZoom === "number" && typeof record4.maxZoom === "number" && record4.minZoom > record4.maxZoom) {
19205
+ const record5 = value;
19206
+ if (typeof record5.minZoom === "number" && typeof record5.maxZoom === "number" && record5.minZoom > record5.maxZoom) {
18755
19207
  context.addIssue({
18756
19208
  code: "custom",
18757
19209
  path: [...path, "maxZoom"],
@@ -19451,8 +19903,8 @@ function validateTileflowCompiledIconCapabilities(style, icons) {
19451
19903
  const checked = structuredClone(style);
19452
19904
  applyTileflowIconCapabilities(checked, icons);
19453
19905
  for (const [index, layer] of checked.layers.entries()) {
19454
- const originalPaint = record3(style.layers[index]?.paint);
19455
- const checkedPaint = record3(layer.paint);
19906
+ const originalPaint = record4(style.layers[index]?.paint);
19907
+ const checkedPaint = record4(layer.paint);
19456
19908
  for (const property of ["icon-color", "icon-halo-color", "icon-halo-width", "icon-halo-blur"]) {
19457
19909
  if (originalPaint[property] === void 0 && checkedPaint[property] !== void 0) {
19458
19910
  throw Object.assign(
@@ -19483,7 +19935,7 @@ function applyTileflowIconCapabilities(style, icons) {
19483
19935
  "line-pattern",
19484
19936
  "background-pattern"
19485
19937
  ]) {
19486
- const pattern = record3(layer.paint)[property];
19938
+ const pattern = record4(layer.paint)[property];
19487
19939
  if (pattern !== void 0 && [...outputs(pattern, ids)].some((id2) => appearances[id2]?.representation === "sdf")) {
19488
19940
  throw Object.assign(new Error(`Layer "${layer.id}" cannot use an SDF icon as a pattern.`), {
19489
19941
  code: "TF_ICON_PATTERN_SDF",
@@ -19492,7 +19944,7 @@ function applyTileflowIconCapabilities(style, icons) {
19492
19944
  });
19493
19945
  }
19494
19946
  }
19495
- const image = record3(layer.layout)["icon-image"];
19947
+ const image = record4(layer.layout)["icon-image"];
19496
19948
  if (image === void 0) continue;
19497
19949
  if (typeof image === "string" && /\{[^{}]+\}/u.test(image) || typeof image !== "string" && !Array.isArray(image)) {
19498
19950
  throw Object.assign(new Error(`Layer "${layer.id}" has an unproven legacy icon selection.`), {
@@ -19514,7 +19966,7 @@ function applyTileflowIconCapabilities(style, icons) {
19514
19966
  }
19515
19967
  );
19516
19968
  }
19517
- const paint = { ...record3(layer.paint) };
19969
+ const paint = { ...record4(layer.paint) };
19518
19970
  for (const [property, limit] of [
19519
19971
  ["icon-halo-width", 2],
19520
19972
  ["icon-halo-blur", 1]
@@ -19544,7 +19996,7 @@ function applyTileflowIconCapabilities(style, icons) {
19544
19996
  layer.paint = paint;
19545
19997
  }
19546
19998
  }
19547
- function record3(value) {
19999
+ function record4(value) {
19548
20000
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
19549
20001
  }
19550
20002
  function outputs(value, ids) {
@@ -20402,6 +20854,15 @@ function combineConditionalValues(entries, fallback, minimumRelevantZoom) {
20402
20854
  if (allEqual(entries.map(({ value }) => value))) {
20403
20855
  return { ok: true, value: entries[0]?.value };
20404
20856
  }
20857
+ const atMinimumZoom = (value) => {
20858
+ if (!isZoomStep(value)) return value;
20859
+ const step2 = value;
20860
+ const lastStop = step2[step2.length - 2];
20861
+ return typeof lastStop === "number" && lastStop <= minimumRelevantZoom ? evaluateZoomStep(value, minimumRelevantZoom) : value;
20862
+ };
20863
+ entries = entries.map(({ condition, value }) => ({ condition, value: atMinimumZoom(value) }));
20864
+ fallback = atMinimumZoom(fallback);
20865
+ if (allEqual(entries.map(({ value }) => value))) return { ok: true, value: entries[0].value };
20405
20866
  const letValues = entries.map(
20406
20867
  ({ value }) => Array.isArray(value) && value.length === 4 && value[0] === "let" ? value : void 0
20407
20868
  );
@@ -20567,7 +21028,8 @@ function classMatchLabelsAreDisjoint(labels2) {
20567
21028
  return true;
20568
21029
  }
20569
21030
  function splitClassMatch(condition) {
20570
- const parts = condition[0] === "all" ? condition.slice(1) : [condition];
21031
+ const conjuncts = (value) => Array.isArray(value) && value[0] === "all" ? value.slice(1).flatMap(conjuncts) : [value];
21032
+ const parts = conjuncts(condition);
20571
21033
  const matchIndex = parts.findIndex(
20572
21034
  (part) => Array.isArray(part) && part.length === 5 && part[0] === "match" && Array.isArray(part[1]) && part[1][0] === "get" && part[1][1] !== void 0 && part.at(-2) === true && part.at(-1) === false
20573
21035
  );
@@ -20705,10 +21167,10 @@ function stableSerialize(value) {
20705
21167
  }
20706
21168
 
20707
21169
  // src/cartography/scene-3d-metadata.ts
21170
+ init_color_program();
20708
21171
  init_values();
20709
21172
  var colourSources = [
20710
- ["tileflow-buildings-render-extrusion", "fill-extrusion-color"],
20711
- ["tileflow-buildings-3d", "fill-extrusion-color"],
21173
+ [tileflowNativeBuildingsLayerId, "fill-extrusion-color"],
20712
21174
  ["tileflow-buildings-fill", "fill-color"]
20713
21175
  ];
20714
21176
  function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, tileflowLandmarks) {
@@ -20731,7 +21193,7 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
20731
21193
  return typeof resolved === "number" ? resolved : fallback;
20732
21194
  };
20733
21195
  const edgeColor = style.edgeColor === void 0 ? void 0 : toMapLibreStyleValue(style.edgeColor);
20734
- const surfaces = isRecord13(style.surfaces) ? readTileflowBuildingSurfaces(
21196
+ const surfaces = isRecord13(style.surfaces) ? mapColorProgramsThrough(
20735
21197
  Object.fromEntries(
20736
21198
  Object.entries(style.surfaces).map(([key, entry]) => [
20737
21199
  key,
@@ -20742,7 +21204,8 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
20742
21204
  ])
20743
21205
  ) : entry
20744
21206
  ])
20745
- )
21207
+ ),
21208
+ readTileflowBuildingSurfaces
20746
21209
  ) : void 0;
20747
21210
  return {
20748
21211
  source: context.data.sourceId,
@@ -20756,18 +21219,21 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
20756
21219
  verticalGradient,
20757
21220
  edges: number(style.edges, 0),
20758
21221
  edgeRadius: number(style.edgeRadius, defaults4.edgeRadius),
20759
- ...typeof edgeColor === "string" ? { edgeColor } : {},
21222
+ ...typeof edgeColor === "string" || isColorProgramMarker(edgeColor) ? { edgeColor } : {},
20760
21223
  nightLight: number(style.nightLight, 0),
20761
21224
  ...isRecord13(style.floodLight) ? {
20762
- floodLight: readBuildingFloodLight({
20763
- ...style.floodLight,
20764
- intensity: number(style.floodLight.intensity, 0),
20765
- brightnessScale: number(style.floodLight.brightnessScale, 1),
20766
- color: style.floodLight.color === void 0 ? void 0 : toMapLibreStyleValue(style.floodLight.color)
20767
- })
21225
+ floodLight: mapColorProgramsThrough(
21226
+ {
21227
+ ...style.floodLight,
21228
+ intensity: number(style.floodLight.intensity, 0),
21229
+ brightnessScale: number(style.floodLight.brightnessScale, 1),
21230
+ color: style.floodLight.color === void 0 ? void 0 : toMapLibreStyleValue(style.floodLight.color)
21231
+ },
21232
+ readBuildingFloodLight
21233
+ )
20768
21234
  } : {},
20769
21235
  ...isRecord13(style.modelLighting) ? {
20770
- modelLighting: readModelRoleLighting(
21236
+ modelLighting: mapColorProgramsThrough(
20771
21237
  Object.fromEntries(
20772
21238
  Object.entries(style.modelLighting).filter(([, entry]) => isRecord13(entry)).map(([role, entry]) => {
20773
21239
  const own = entry;
@@ -20798,7 +21264,8 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
20798
21264
  }
20799
21265
  ];
20800
21266
  })
20801
- )
21267
+ ),
21268
+ readModelRoleLighting
20802
21269
  )
20803
21270
  } : {},
20804
21271
  windows: style.windows === true,
@@ -20852,7 +21319,8 @@ function modelColours(value) {
20852
21319
  const colours = {};
20853
21320
  for (const [part, colour] of Object.entries(value)) {
20854
21321
  const resolved = colour === void 0 ? void 0 : toMapLibreStyleValue(colour);
20855
- if (typeof resolved === "string") colours[part] = resolved;
21322
+ if (typeof resolved === "string" || isColorProgramMarker(resolved))
21323
+ colours[part] = resolved;
20856
21324
  }
20857
21325
  return Object.keys(colours).length > 0 ? { modelColors: colours } : {};
20858
21326
  }
@@ -20873,7 +21341,7 @@ function tileflowStreetLightsDesignOf(module, context) {
20873
21341
  ...layers2.sidewalk ? { sidewalk: layers2.sidewalk } : {}
20874
21342
  },
20875
21343
  estimate: estimate === "streets" || estimate === "none" || estimate === "lit" ? estimate : "lit",
20876
- color: typeof color3 === "string" ? color3 : tileflowDefaultStreetLightsDesign.color,
21344
+ color: typeof color3 === "string" || isColorProgramMarker(color3) ? color3 : tileflowDefaultStreetLightsDesign.color,
20877
21345
  intensity: typeof intensity === "number" ? intensity : 1
20878
21346
  };
20879
21347
  }
@@ -20940,7 +21408,7 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
20940
21408
  "theme",
20941
21409
  () => resolveThemeValues(themeableConfig, selected.theme, `map.${input.id}`)
20942
21410
  );
20943
- const config = runCompilationPhase(
21411
+ let config = runCompilationPhase(
20944
21412
  "config-validation",
20945
21413
  () => parseResolvedTileflowMap({
20946
21414
  ...resolvedThemeConfig,
@@ -20948,6 +21416,11 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
20948
21416
  themes: input.themes
20949
21417
  })
20950
21418
  );
21419
+ if (options.runtimeColors)
21420
+ config = {
21421
+ ...config,
21422
+ modules: preserveThemeColorPrograms(input.modules, config.modules, selected.theme)
21423
+ };
20951
21424
  const apiBaseUrl = normalizeBaseUrl(options.apiBaseUrl);
20952
21425
  const data = runCompilationPhase("data", () => resolveTileflowData(config.data, { apiBaseUrl }));
20953
21426
  const marine = runCompilationPhase("data", () => resolveMarine(config.marine, apiBaseUrl));
@@ -21040,7 +21513,8 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
21040
21513
  "theme",
21041
21514
  () => resolveThemeColors(
21042
21515
  selected.theme,
21043
- onReport ? (decision) => colorDecisions.push(decision) : void 0
21516
+ onReport ? (decision) => colorDecisions.push(decision) : void 0,
21517
+ options.runtimeColors
21044
21518
  )
21045
21519
  );
21046
21520
  const images = runCompilationPhase("theme", () => resolveThemeImages(selected.theme));
@@ -21342,7 +21816,7 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
21342
21816
  marine?.nautical?.sourceId,
21343
21817
  tileflowNauticalV1Schema.layers.lights
21344
21818
  );
21345
- const style = {
21819
+ let style = {
21346
21820
  ...sky ? { sky: compileTileflowStreetSky(sky, Boolean(atmosphere)) } : atmosphere ? { sky: compileTileflowSky(atmosphere) } : {},
21347
21821
  version: 8,
21348
21822
  name: config.name ?? "Streets",
@@ -21398,16 +21872,73 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
21398
21872
  [tileflowOverlayPlacementManifestMetadataKey]: overlayPlacementManifest,
21399
21873
  "tileflow:modules": Object.entries(modules).filter(([, module]) => !isRecord14(module) || module.enabled !== false).map(([name]) => name).sort(),
21400
21874
  ...config.view ? { "tileflow:view": config.view } : {},
21401
- ...config.camera?.adaptivePerspective !== void 0 ? {
21875
+ ...config.camera?.adaptivePerspective !== void 0 || config.camera?.pitchLimit !== void 0 ? {
21402
21876
  [tileflowCameraMetadataKey]: {
21403
21877
  version: 1,
21404
- adaptivePerspective: config.camera.adaptivePerspective
21878
+ ...config.camera
21405
21879
  }
21406
21880
  } : {},
21407
21881
  ...notices.length > 0 ? { [tileflowNoticesMetadataKey]: notices } : {},
21408
21882
  ...nauticalLights ? { [tileflowNauticalLightsMetadataKey]: nauticalLights } : {}
21409
21883
  }
21410
21884
  };
21885
+ if (options.runtimeColors) {
21886
+ const theme = selected.theme;
21887
+ if (style.light)
21888
+ style.light = preserveThemeColorPrograms(
21889
+ { color: theme.lighting.sun?.color ?? theme.lighting.color },
21890
+ style.light,
21891
+ theme,
21892
+ true
21893
+ );
21894
+ const authoredSky = typeof input.sky === "object" ? input.sky : void 0;
21895
+ if (style.sky && authoredSky)
21896
+ style.sky = preserveThemeColorPrograms(
21897
+ {
21898
+ "sky-color": authoredSky.color,
21899
+ "horizon-color": authoredSky.horizonColor,
21900
+ "fog-color": authoredSky.horizonColor
21901
+ },
21902
+ style.sky,
21903
+ theme,
21904
+ true
21905
+ );
21906
+ if (style.metadata) {
21907
+ style.metadata["tileflow:sun"] = preserveThemeColorPrograms(
21908
+ { color: theme.lighting.sun?.color, ambient: { color: theme.lighting.ambient?.color } },
21909
+ style.metadata["tileflow:sun"],
21910
+ theme,
21911
+ true
21912
+ );
21913
+ if (authoredSky)
21914
+ style.metadata["tileflow:sky"] = preserveThemeColorPrograms(
21915
+ {
21916
+ glowColor: authoredSky.glowColor,
21917
+ timesOfDay: Object.fromEntries(
21918
+ ["day", "night", "dawn", "dusk"].map((moment) => [moment, authoredSky])
21919
+ )
21920
+ },
21921
+ style.metadata["tileflow:sky"],
21922
+ theme,
21923
+ true
21924
+ );
21925
+ const route = Object.fromEntries(
21926
+ ["line", "casing", "core", "glow", "alternative"].filter((key) => Object.hasOwn(theme.tokens.color, `route.${key}`)).map((key) => [key, { kind: "theme-token", category: "color", token: `route.${key}` }])
21927
+ );
21928
+ style.metadata["tileflow:route"] = preserveThemeColorPrograms(
21929
+ route,
21930
+ style.metadata["tileflow:route"],
21931
+ theme,
21932
+ true
21933
+ );
21934
+ for (const key of Object.keys(style.metadata))
21935
+ if (style.metadata[key] === void 0) delete style.metadata[key];
21936
+ }
21937
+ style = extractTileflowColorPrograms(style, {
21938
+ color: theme.tokens.color,
21939
+ number: theme.tokens.number
21940
+ });
21941
+ }
21411
21942
  const report = {
21412
21943
  ...partialReport,
21413
21944
  ...onReport ? {
@@ -21689,8 +22220,8 @@ function runCompilationPhase(phase, operation) {
21689
22220
  }
21690
22221
  }
21691
22222
  function withCompilationPhase(error, phase) {
21692
- const record4 = isRecord14(error) ? error : {};
21693
- if (typeof record4.phase === "string") return error;
22223
+ const record5 = isRecord14(error) ? error : {};
22224
+ if (typeof record5.phase === "string") return error;
21694
22225
  if (error instanceof Error && Object.isExtensible(error)) {
21695
22226
  Object.defineProperty(error, "phase", { configurable: true, enumerable: true, value: phase });
21696
22227
  return error;
@@ -21706,7 +22237,7 @@ function withCompilationPhase(error, phase) {
21706
22237
  "severity",
21707
22238
  "target"
21708
22239
  ]) {
21709
- if (record4[key] !== void 0) Object.assign(wrapped, { [key]: record4[key] });
22240
+ if (record5[key] !== void 0) Object.assign(wrapped, { [key]: record5[key] });
21710
22241
  }
21711
22242
  Object.assign(wrapped, { phase });
21712
22243
  return wrapped;
@@ -23183,16 +23714,16 @@ function parseBoundaryLookupCsv(text) {
23183
23714
  throw new Error("Boundaries lookup CSV must start with a header that has id and class.");
23184
23715
  }
23185
23716
  const rows = [];
23186
- records.forEach((record4, index) => {
23187
- if (record4.length === 1 && record4[0] === "") return;
23188
- if (record4.length !== header.length) {
23717
+ records.forEach((record5, index) => {
23718
+ if (record5.length === 1 && record5[0] === "") return;
23719
+ if (record5.length !== header.length) {
23189
23720
  throw new Error(
23190
- `Boundaries lookup CSV row ${index + 2} has ${record4.length} fields; expected ${header.length}.`
23721
+ `Boundaries lookup CSV row ${index + 2} has ${record5.length} fields; expected ${header.length}.`
23191
23722
  );
23192
23723
  }
23193
23724
  const row = {};
23194
23725
  header.forEach((column2, position) => {
23195
- const cell = record4[position];
23726
+ const cell = record5[position];
23196
23727
  if (cell === "" || !lookupColumns.has(column2)) return;
23197
23728
  if (numericLookupColumns.has(column2)) {
23198
23729
  const number = Number(cell);
@@ -23213,7 +23744,7 @@ function parseBoundaryLookupCsv(text) {
23213
23744
  }
23214
23745
  function parseCsv(text) {
23215
23746
  const records = [];
23216
- let record4 = [];
23747
+ let record5 = [];
23217
23748
  let field2 = "";
23218
23749
  let quoted = false;
23219
23750
  for (let index = 0; index < text.length; index += 1) {
@@ -23232,22 +23763,22 @@ function parseCsv(text) {
23232
23763
  } else if (char === '"' && field2 === "") {
23233
23764
  quoted = true;
23234
23765
  } else if (char === ",") {
23235
- record4.push(field2);
23766
+ record5.push(field2);
23236
23767
  field2 = "";
23237
23768
  } else if (char === "\n" || char === "\r") {
23238
23769
  if (char === "\r" && text[index + 1] === "\n") index += 1;
23239
- record4.push(field2);
23240
- records.push(record4);
23241
- record4 = [];
23770
+ record5.push(field2);
23771
+ records.push(record5);
23772
+ record5 = [];
23242
23773
  field2 = "";
23243
23774
  } else {
23244
23775
  field2 += char;
23245
23776
  }
23246
23777
  }
23247
23778
  if (quoted) throw new Error("Boundaries lookup CSV ends inside a quoted field.");
23248
- if (field2 !== "" || record4.length > 0) {
23249
- record4.push(field2);
23250
- records.push(record4);
23779
+ if (field2 !== "" || record5.length > 0) {
23780
+ record5.push(field2);
23781
+ records.push(record5);
23251
23782
  }
23252
23783
  return records;
23253
23784
  }
@@ -24151,6 +24682,7 @@ function resolveTileflowRuntimeStyle(options) {
24151
24682
  styleId: theme.styleId
24152
24683
  },
24153
24684
  colorScheme: theme.colorScheme,
24685
+ ...options.manifestMap.familyUrl ? { familyUrl: options.manifestMap.familyUrl } : {},
24154
24686
  ...theme.fontFaces === void 0 ? {} : { fontFaces: [...theme.fontFaces] },
24155
24687
  revision: theme.revision,
24156
24688
  style: theme.styleUrl,
@@ -24667,6 +25199,7 @@ function resolveLoadedTileflowManifestUrls(manifest, manifestUrl) {
24667
25199
  mapName,
24668
25200
  {
24669
25201
  ...entry,
25202
+ ...entry.familyUrl ? { familyUrl: resolveTileflowLoadedResourceUrl(entry.familyUrl, manifestUrl) } : {},
24670
25203
  themes: Object.fromEntries(
24671
25204
  Object.entries(entry.themes).map(([themeName, theme]) => {
24672
25205
  const styleUrl = resolveTileflowLoadedResourceUrl(theme.styleUrl, manifestUrl);
@@ -24716,6 +25249,7 @@ function resolveTileflowManifestMap(manifest, mapName) {
24716
25249
  mapId: entry.mapId ?? analytics?.mapId,
24717
25250
  name: mapName,
24718
25251
  defaultTheme: entry.defaultTheme,
25252
+ ...entry.familyUrl ? { familyUrl: entry.familyUrl } : {},
24719
25253
  ...entry.environment ? { environment: entry.environment } : {},
24720
25254
  ...entry.systemThemes ? { systemThemes: { ...entry.systemThemes } } : {},
24721
25255
  ...entry.timeOfDay ? { timeOfDay: entry.timeOfDay } : {},
@@ -25539,14 +26073,14 @@ function tileflowModelImageSize(bytes, mimeType) {
25539
26073
  for (let at = 2; at + 4 <= bytes.length; ) {
25540
26074
  if (bytes[at++] !== 255) return void 0;
25541
26075
  while (bytes[at] === 255) at++;
25542
- const marker = bytes[at++];
25543
- if (marker === 217 || marker === 218) return void 0;
25544
- if (marker === 1 || marker >= 208 && marker <= 215) continue;
26076
+ const marker2 = bytes[at++];
26077
+ if (marker2 === 217 || marker2 === 218) return void 0;
26078
+ if (marker2 === 1 || marker2 >= 208 && marker2 <= 215) continue;
25545
26079
  if (at + 2 > bytes.length) return void 0;
25546
26080
  const length = view.getUint16(at);
25547
26081
  if (length < 2 || at + length > bytes.length) return void 0;
25548
26082
  if ([192, 193, 194, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207].includes(
25549
- marker
26083
+ marker2
25550
26084
  )) {
25551
26085
  if (length < 8) return void 0;
25552
26086
  const height = view.getUint16(at + 3), width = view.getUint16(at + 5);