card-gate-frame 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2536 @@
1
+ //#region src/core/geometry-utils.ts
2
+ const COORDINATE_SCALE$1 = 1e4;
3
+ const canonicalNumber = (value) => {
4
+ const rounded = Math.round(value * COORDINATE_SCALE$1) / COORDINATE_SCALE$1;
5
+ return Object.is(rounded, -0) ? 0 : rounded;
6
+ };
7
+ const canonicalizeCommand = (command) => {
8
+ switch (command.kind) {
9
+ case "M":
10
+ case "L": return {
11
+ kind: command.kind,
12
+ x: canonicalNumber(command.x),
13
+ y: canonicalNumber(command.y)
14
+ };
15
+ case "C": return {
16
+ kind: "C",
17
+ x1: canonicalNumber(command.x1),
18
+ y1: canonicalNumber(command.y1),
19
+ x2: canonicalNumber(command.x2),
20
+ y2: canonicalNumber(command.y2),
21
+ x: canonicalNumber(command.x),
22
+ y: canonicalNumber(command.y)
23
+ };
24
+ case "Q": return {
25
+ kind: "Q",
26
+ x1: canonicalNumber(command.x1),
27
+ y1: canonicalNumber(command.y1),
28
+ x: canonicalNumber(command.x),
29
+ y: canonicalNumber(command.y)
30
+ };
31
+ case "Z": return { kind: "Z" };
32
+ }
33
+ };
34
+ const canonicalizePath = (path) => path.map(canonicalizeCommand);
35
+ const mirrorCommand = (command, width) => {
36
+ switch (command.kind) {
37
+ case "M":
38
+ case "L": return {
39
+ kind: command.kind,
40
+ x: canonicalNumber(width - command.x),
41
+ y: command.y
42
+ };
43
+ case "C": return {
44
+ kind: "C",
45
+ x1: canonicalNumber(width - command.x1),
46
+ y1: command.y1,
47
+ x2: canonicalNumber(width - command.x2),
48
+ y2: command.y2,
49
+ x: canonicalNumber(width - command.x),
50
+ y: command.y
51
+ };
52
+ case "Q": return {
53
+ kind: "Q",
54
+ x1: canonicalNumber(width - command.x1),
55
+ y1: command.y1,
56
+ x: canonicalNumber(width - command.x),
57
+ y: command.y
58
+ };
59
+ case "Z": return { kind: "Z" };
60
+ }
61
+ };
62
+ const mirrorPath = (path, width) => path.map((command) => mirrorCommand(command, width));
63
+ const deepFreeze = (value) => {
64
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
65
+ for (const nested of Object.values(value)) deepFreeze(nested);
66
+ Object.freeze(value);
67
+ }
68
+ return value;
69
+ };
70
+
71
+ //#endregion
72
+ //#region src/core/utf8.ts
73
+ const Encoder = globalThis.TextEncoder;
74
+ const encoder = new Encoder();
75
+ const encodeUtf8 = (value) => encoder.encode(value);
76
+
77
+ //#endregion
78
+ //#region src/core/courtyard.ts
79
+ const DEFAULT_INSET = 8;
80
+ const DEFAULT_STROKE_WIDTH = 2;
81
+ const DEFAULT_DENSITY = .55;
82
+ const DEFAULT_CURVATURE = .65;
83
+ const normalizeDimensions$1 = (dimensions) => {
84
+ const { width, height } = dimensions;
85
+ if (!Number.isFinite(width) || !Number.isFinite(height)) throw new TypeError("Gate dimensions must be finite numbers");
86
+ if (width < 160 || width > 1600 || height < 96 || height > 1200) throw new RangeError("Gate dimensions are outside the supported range");
87
+ const aspectRatio = width / height;
88
+ if (aspectRatio < .5 || aspectRatio > 6) throw new RangeError("Gate dimensions have an unsupported aspect ratio");
89
+ return {
90
+ width: canonicalNumber(width),
91
+ height: canonicalNumber(height)
92
+ };
93
+ };
94
+ const normalizeSeed$1 = (seed) => {
95
+ if (typeof seed === "number") {
96
+ if (!Number.isFinite(seed)) throw new TypeError("Gate seed numbers must be finite");
97
+ return Object.is(seed, -0) ? 0 : seed;
98
+ }
99
+ if (typeof seed !== "string") throw new TypeError("Gate seeds must be strings or finite numbers");
100
+ if (seed.length === 0) throw new TypeError("Gate seed strings must not be empty");
101
+ return seed;
102
+ };
103
+ const validateGenerationOptions = (options) => {
104
+ if (options.family !== "courtyard") throw new TypeError("GF-01 requires family: \"courtyard\"");
105
+ if (options.generationVersion !== void 0 && options.generationVersion !== 1) throw new TypeError("GF-01 supports only generationVersion 1");
106
+ };
107
+ const normalizeShapeControl = (value, fallback, name) => {
108
+ const normalized = value === void 0 ? fallback : value;
109
+ if (!Number.isFinite(normalized)) throw new TypeError(`${name} must be a finite number`);
110
+ if (normalized < 0 || normalized > 1) throw new RangeError(`${name} must be between 0 and 1 inclusive`);
111
+ return canonicalNumber(normalized);
112
+ };
113
+ const hashByte = (hash, byte) => Math.imul(hash ^ byte, 16777619) >>> 0;
114
+ const hashUtf8 = (value) => {
115
+ let hash = 2166136261;
116
+ for (const byte of encodeUtf8(value)) hash = hashByte(hash, byte);
117
+ return hash;
118
+ };
119
+ const seedState = (seed) => hashUtf8(`${typeof seed === "number" ? "number" : "string"}:${String(seed)}`);
120
+ const nextUint32 = (state) => {
121
+ let next = state || 1831565813;
122
+ next ^= next << 13;
123
+ next ^= next >>> 17;
124
+ next ^= next << 5;
125
+ return next >>> 0;
126
+ };
127
+ function generateGate$1(dimensions, options) {
128
+ validateGenerationOptions(options);
129
+ const { width, height } = normalizeDimensions$1(dimensions);
130
+ const seed = normalizeSeed$1(options.seed);
131
+ const density = normalizeShapeControl(options.density, DEFAULT_DENSITY, "density");
132
+ const curvature = normalizeShapeControl(options.curvature, DEFAULT_CURVATURE, "curvature");
133
+ const edge = canonicalNumber(9);
134
+ const railY = canonicalNumber(Math.max(edge + 24, height * .275));
135
+ const bottomY = canonicalNumber(height - edge);
136
+ const frameBand = canonicalNumber(Math.max(12, Math.min(24, Math.min(width, height) * .06)));
137
+ const safeLeft = canonicalNumber(edge + frameBand);
138
+ const safeRight = canonicalNumber(width - safeLeft);
139
+ const safeTop = canonicalNumber(railY + frameBand);
140
+ const safeBottom = canonicalNumber(bottomY - frameBand);
141
+ const centerX = canonicalNumber(width / 2);
142
+ const surfacePath = [
143
+ {
144
+ kind: "M",
145
+ x: edge,
146
+ y: railY
147
+ },
148
+ {
149
+ kind: "L",
150
+ x: width - edge,
151
+ y: railY
152
+ },
153
+ {
154
+ kind: "L",
155
+ x: width - edge,
156
+ y: bottomY
157
+ },
158
+ {
159
+ kind: "L",
160
+ x: edge,
161
+ y: bottomY
162
+ },
163
+ { kind: "Z" },
164
+ {
165
+ kind: "M",
166
+ x: safeLeft,
167
+ y: safeTop
168
+ },
169
+ {
170
+ kind: "L",
171
+ x: safeLeft,
172
+ y: safeBottom
173
+ },
174
+ {
175
+ kind: "L",
176
+ x: safeRight,
177
+ y: safeBottom
178
+ },
179
+ {
180
+ kind: "L",
181
+ x: safeRight,
182
+ y: safeTop
183
+ },
184
+ { kind: "Z" }
185
+ ];
186
+ const structuralPath = [
187
+ {
188
+ kind: "M",
189
+ x: edge,
190
+ y: bottomY
191
+ },
192
+ {
193
+ kind: "L",
194
+ x: edge,
195
+ y: railY
196
+ },
197
+ {
198
+ kind: "L",
199
+ x: width - edge,
200
+ y: railY
201
+ },
202
+ {
203
+ kind: "L",
204
+ x: width - edge,
205
+ y: bottomY
206
+ },
207
+ { kind: "Z" }
208
+ ];
209
+ const seedVariation = nextUint32(seedState(seed)) / 4294967295;
210
+ const availableScrollLift = canonicalNumber(railY - edge - 4);
211
+ const scrollLift = canonicalNumber(Math.min(availableScrollLift, 27 + seedVariation * 6));
212
+ const scrollStart = safeLeft;
213
+ const availableScrollReach = canonicalNumber(centerX - 24 - scrollStart);
214
+ const baseScrollReach = Math.min(width * .22, 66 + seedVariation * 8, availableScrollReach);
215
+ const scrollReach = canonicalNumber(baseScrollReach * (.86 + density * .14));
216
+ const scrollEnd = canonicalNumber(scrollStart + scrollReach);
217
+ const curvatureScale = .8 + curvature * .3;
218
+ const leftScroll = [
219
+ {
220
+ kind: "M",
221
+ x: scrollStart,
222
+ y: railY
223
+ },
224
+ {
225
+ kind: "C",
226
+ x1: scrollStart + 5 * curvatureScale,
227
+ y1: railY - 20 * curvatureScale,
228
+ x2: scrollEnd - 18 * curvatureScale,
229
+ y2: railY - scrollLift,
230
+ x: scrollEnd,
231
+ y: railY - scrollLift
232
+ },
233
+ {
234
+ kind: "C",
235
+ x1: scrollEnd + 18 * curvatureScale,
236
+ y1: railY - scrollLift,
237
+ x2: scrollEnd + 22 * curvatureScale,
238
+ y2: railY - 9 * curvatureScale,
239
+ x: scrollEnd + 10,
240
+ y: railY - 6
241
+ },
242
+ {
243
+ kind: "C",
244
+ x1: scrollEnd,
245
+ y1: railY - 3,
246
+ x2: scrollEnd - 8 * curvatureScale,
247
+ y2: railY - 10 * curvatureScale,
248
+ x: scrollEnd - 3,
249
+ y: railY - 16
250
+ }
251
+ ];
252
+ const spear = [
253
+ {
254
+ kind: "M",
255
+ x: centerX,
256
+ y: railY
257
+ },
258
+ {
259
+ kind: "L",
260
+ x: centerX,
261
+ y: edge + 18
262
+ },
263
+ {
264
+ kind: "M",
265
+ x: centerX,
266
+ y: edge
267
+ },
268
+ {
269
+ kind: "L",
270
+ x: centerX + 7,
271
+ y: edge + 12
272
+ },
273
+ {
274
+ kind: "L",
275
+ x: centerX,
276
+ y: edge + 19
277
+ },
278
+ {
279
+ kind: "L",
280
+ x: centerX - 7,
281
+ y: edge + 12
282
+ },
283
+ { kind: "Z" }
284
+ ];
285
+ const geometry = {
286
+ schemaVersion: 1,
287
+ generationVersion: 1,
288
+ seed,
289
+ dimensions: {
290
+ width,
291
+ height
292
+ },
293
+ viewBox: {
294
+ minX: 0,
295
+ minY: 0,
296
+ width,
297
+ height
298
+ },
299
+ options: {
300
+ generationVersion: 1,
301
+ family: "courtyard",
302
+ density,
303
+ curvature,
304
+ symmetry: 1,
305
+ crest: "spear",
306
+ inset: DEFAULT_INSET,
307
+ strokeWidth: DEFAULT_STROKE_WIDTH
308
+ },
309
+ bounds: {
310
+ left: edge,
311
+ right: canonicalNumber(width - edge),
312
+ railY,
313
+ bottomY
314
+ },
315
+ contentInsets: {
316
+ top: safeTop,
317
+ right: canonicalNumber(width - safeRight),
318
+ bottom: canonicalNumber(height - safeBottom),
319
+ left: safeLeft
320
+ },
321
+ surfacePath: canonicalizePath(surfacePath),
322
+ structuralPaths: [canonicalizePath(structuralPath)],
323
+ decorativePaths: [
324
+ canonicalizePath(leftScroll),
325
+ mirrorPath(canonicalizePath(leftScroll), width),
326
+ canonicalizePath(spear)
327
+ ]
328
+ };
329
+ return deepFreeze(geometry);
330
+ }
331
+
332
+ //#endregion
333
+ //#region src/core/labelled-sampler.ts
334
+ function createLabelledSampler(version) {
335
+ const UINT32_RANGE = 4294967296;
336
+ const SAMPLE_DOMAIN = `gate-frame/v${version}/sample\0`;
337
+ const LABEL_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*(?:\/[a-z][a-z0-9]*(?:-[a-z0-9]+)*)*$/;
338
+ const pushUint32Le = (bytes, value) => {
339
+ bytes.push(value & 255, value >>> 8 & 255, value >>> 16 & 255, value >>> 24);
340
+ };
341
+ function sampleKeyBytes(seed, label, index) {
342
+ if (typeof seed === "number" && !Number.isFinite(seed)) throw new TypeError("Sample seed numbers must be finite");
343
+ if (typeof seed !== "string" && typeof seed !== "number") throw new TypeError("Sample seeds must be strings or finite numbers");
344
+ if (!LABEL_PATTERN.test(label)) throw new TypeError(`Sample labels must use the generation-v${version} labelled domain`);
345
+ if (index !== void 0 && (!Number.isInteger(index) || index < 0 || index > 4294967295)) throw new RangeError("Sample indices must be unsigned 32-bit integers");
346
+ const normalizedSeed = typeof seed === "number" && Object.is(seed, -0) ? 0 : seed;
347
+ const seedBytes = encodeUtf8(String(normalizedSeed));
348
+ const labelBytes = encodeUtf8(label);
349
+ const bytes = Array.from(encodeUtf8(SAMPLE_DOMAIN));
350
+ bytes.push(typeof normalizedSeed === "number" ? 110 : 115);
351
+ pushUint32Le(bytes, seedBytes.length);
352
+ bytes.push(...seedBytes);
353
+ pushUint32Le(bytes, labelBytes.length);
354
+ bytes.push(...labelBytes);
355
+ bytes.push(index === void 0 ? 0 : 1);
356
+ if (index !== void 0) pushUint32Le(bytes, index);
357
+ return Uint8Array.from(bytes);
358
+ }
359
+ function sampleUint32(seed, label, index) {
360
+ let hash = 2166136261;
361
+ for (const byte of sampleKeyBytes(seed, label, index)) hash = Math.imul(hash ^ byte, 16777619) >>> 0;
362
+ hash ^= hash >>> 16;
363
+ hash = Math.imul(hash, 2246822507) >>> 0;
364
+ hash ^= hash >>> 13;
365
+ hash = Math.imul(hash, 3266489909) >>> 0;
366
+ hash ^= hash >>> 16;
367
+ return hash >>> 0;
368
+ }
369
+ function sampleScalar(seed, label, index) {
370
+ return sampleUint32(seed, label, index) / UINT32_RANGE;
371
+ }
372
+ function sampleIndex(seed, label, count, index) {
373
+ if (!Number.isInteger(count) || count < 1 || count > 65536) throw new RangeError("Sample counts must be integers from 1 through 65536");
374
+ return Math.floor(sampleUint32(seed, label, index) * count / UINT32_RANGE);
375
+ }
376
+ return {
377
+ sampleKeyBytes,
378
+ sampleUint32,
379
+ sampleScalar,
380
+ sampleIndex
381
+ };
382
+ }
383
+
384
+ //#endregion
385
+ //#region src/core/v2/sampler.ts
386
+ const sampler$1 = createLabelledSampler(2);
387
+ const sampleKeyBytes$1 = sampler$1.sampleKeyBytes;
388
+ const sampleUint32$1 = sampler$1.sampleUint32;
389
+ const sampleScalar$1 = sampler$1.sampleScalar;
390
+ const sampleIndex$1 = sampler$1.sampleIndex;
391
+
392
+ //#endregion
393
+ //#region src/core/v3/sampler.ts
394
+ const sampler = createLabelledSampler(3);
395
+ const sampleKeyBytes = sampler.sampleKeyBytes;
396
+ const sampleUint32 = sampler.sampleUint32;
397
+ const sampleScalar = sampler.sampleScalar;
398
+ const sampleIndex = sampler.sampleIndex;
399
+
400
+ //#endregion
401
+ //#region src/core/family-options.ts
402
+ const INSET = 8;
403
+ const STROKE_WIDTH = 2;
404
+ const FAMILY_ORDER = [
405
+ "courtyard",
406
+ "fleuron",
407
+ "arcade",
408
+ "vine",
409
+ "fan",
410
+ "volute"
411
+ ];
412
+ const SPEAR_DIAMOND_CRESTS = [
413
+ "spear",
414
+ "diamond",
415
+ "none"
416
+ ];
417
+ const FLEUR_SPEAR_CRESTS = [
418
+ "fleur",
419
+ "spear",
420
+ "none"
421
+ ];
422
+ const DIAMOND_SPEAR_CRESTS = [
423
+ "diamond",
424
+ "spear",
425
+ "none"
426
+ ];
427
+ const FLEUR_DIAMOND_CRESTS = [
428
+ "fleur",
429
+ "diamond",
430
+ "none"
431
+ ];
432
+ const isGateFamily = (value) => typeof value === "string" && FAMILY_ORDER.includes(value);
433
+ const compatibleCrestsForFamily = (family) => family === "courtyard" ? SPEAR_DIAMOND_CRESTS : family === "vine" ? FLEUR_DIAMOND_CRESTS : family === "arcade" || family === "fan" ? DIAMOND_SPEAR_CRESTS : FLEUR_SPEAR_CRESTS;
434
+ const isCompatibleCrest = (family, crest) => isGateFamily(family) && compatibleCrestsForFamily(family).includes(crest);
435
+ const FAMILY_OPTION_KEYS = /* @__PURE__ */ new Set([
436
+ "seed",
437
+ "generationVersion",
438
+ "family",
439
+ "density",
440
+ "curvature",
441
+ "symmetry",
442
+ "peakHeight",
443
+ "sideComplexity",
444
+ "crest"
445
+ ]);
446
+ const requirePlainObject$1 = (value, version) => {
447
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new TypeError(`generation-v${version} options must be a plain object`);
448
+ return value;
449
+ };
450
+ const ownValue = (input, key) => Object.hasOwn(input, key) ? input[key] : void 0;
451
+ const normalizeDimensions = (dimensions) => {
452
+ if (dimensions === null || typeof dimensions !== "object") throw new TypeError("Gate dimensions must be a plain object");
453
+ const { width, height } = dimensions;
454
+ if (!Number.isFinite(width) || !Number.isFinite(height)) throw new TypeError("Gate dimensions must be finite numbers");
455
+ if (width < 160 || width > 1600 || height < 96 || height > 1200) throw new RangeError("Gate dimensions are outside the supported range");
456
+ const aspectRatio = width / height;
457
+ if (aspectRatio < .5 || aspectRatio > 6) throw new RangeError("Gate dimensions have an unsupported aspect ratio");
458
+ return {
459
+ width: canonicalNumber(width),
460
+ height: canonicalNumber(height)
461
+ };
462
+ };
463
+ const normalizeSeed = (value) => {
464
+ if (typeof value === "number") {
465
+ if (!Number.isFinite(value)) throw new TypeError("Gate seed numbers must be finite");
466
+ return Object.is(value, -0) ? 0 : value;
467
+ }
468
+ if (typeof value !== "string" || value.length === 0) throw new TypeError("Gate seeds must be non-empty strings or finite numbers");
469
+ return value;
470
+ };
471
+ const normalizeControl = (value, fallback, name) => {
472
+ const candidate = value === void 0 ? fallback : value;
473
+ if (typeof candidate !== "number" || !Number.isFinite(candidate)) throw new TypeError(`${name} must be a finite number`);
474
+ if (candidate < 0 || candidate > 1) throw new RangeError(`${name} must be between 0 and 1 inclusive`);
475
+ return canonicalNumber(candidate);
476
+ };
477
+ function normalizeFamilyOptions(value, version) {
478
+ const sampleIndex$2 = version === 2 ? sampleIndex$1 : sampleIndex;
479
+ const input = requirePlainObject$1(value, version);
480
+ const unsupportedKeys = Reflect.ownKeys(input).filter((key) => typeof key !== "string" || !FAMILY_OPTION_KEYS.has(key)).map(String).sort();
481
+ if (unsupportedKeys.length > 0) throw new TypeError(`Unsupported generation-v${version} option keys: ${unsupportedKeys.join(", ")}`);
482
+ if (ownValue(input, "generationVersion") !== version) throw new TypeError(`generation-v${version} options require generationVersion ${version}`);
483
+ const seed = normalizeSeed(ownValue(input, "seed"));
484
+ const familyInput = ownValue(input, "family");
485
+ const requestedFamily = familyInput === void 0 ? "auto" : familyInput;
486
+ if (requestedFamily !== "auto" && !isGateFamily(requestedFamily)) throw new TypeError(`generation-v${version} family is invalid`);
487
+ const family = requestedFamily === "auto" ? FAMILY_ORDER[sampleIndex$2(seed, "family", FAMILY_ORDER.length)] : requestedFamily;
488
+ const density = normalizeControl(ownValue(input, "density"), .55, "density");
489
+ const curvature = normalizeControl(ownValue(input, "curvature"), .65, "curvature");
490
+ const symmetry = normalizeControl(ownValue(input, "symmetry"), 1, "symmetry");
491
+ const peakHeight = normalizeControl(ownValue(input, "peakHeight"), .35, "peakHeight");
492
+ const sideComplexity = normalizeControl(ownValue(input, "sideComplexity"), .5, "sideComplexity");
493
+ const requestedCrest = ownValue(input, "crest");
494
+ const compatibleCrests = compatibleCrestsForFamily(family);
495
+ if (requestedCrest !== void 0 && (typeof requestedCrest !== "string" || !compatibleCrests.includes(requestedCrest))) throw new TypeError(`crest is incompatible with generation-v${version} family ${family}`);
496
+ return {
497
+ seed,
498
+ options: {
499
+ generationVersion: version,
500
+ family,
501
+ density,
502
+ curvature,
503
+ symmetry,
504
+ peakHeight,
505
+ sideComplexity,
506
+ crest: requestedCrest === void 0 ? compatibleCrests[version === 3 ? 0 : sampleIndex$2(seed, `${family}/crest`, compatibleCrests.length)] : requestedCrest,
507
+ inset: INSET,
508
+ strokeWidth: STROKE_WIDTH
509
+ }
510
+ };
511
+ }
512
+
513
+ //#endregion
514
+ //#region src/core/v2/generate.ts
515
+ const COORDINATE_SCALE = 1e4;
516
+ const centerPairForWidth = (width) => {
517
+ const widthLatticeUnits = Math.round(width * COORDINATE_SCALE);
518
+ const leftLatticeUnits = Math.floor(widthLatticeUnits / 2);
519
+ return {
520
+ left: leftLatticeUnits / COORDINATE_SCALE,
521
+ right: (widthLatticeUnits - leftLatticeUnits) / COORDINATE_SCALE
522
+ };
523
+ };
524
+ const pairedCenteredPaths = (path, width) => {
525
+ const centerPair = centerPairForWidth(width);
526
+ return centerPair.left === centerPair.right ? [path] : [path, mirrorPath(path, width)];
527
+ };
528
+ const displaceInteriorY = (path, displacement) => canonicalizePath(path.map((command, index) => {
529
+ if (command.kind === "C") return {
530
+ ...command,
531
+ y1: command.y1 + displacement,
532
+ y2: command.y2 + displacement
533
+ };
534
+ if (command.kind === "Q") return {
535
+ ...command,
536
+ y1: command.y1 + displacement
537
+ };
538
+ if (command.kind === "L" && index < path.length - 1) return {
539
+ ...command,
540
+ y: command.y + displacement
541
+ };
542
+ return command;
543
+ }));
544
+ const quantizedSymmetryDisplacement = (symmetry, signedSample, maximumSpan) => {
545
+ if (symmetry === 1) return 0;
546
+ const maximumLatticeUnits = Math.max(1, Math.floor(maximumSpan * COORDINATE_SCALE / 2));
547
+ const sampledLatticeUnits = Math.round((1 - symmetry) * signedSample * maximumSpan * COORDINATE_SCALE);
548
+ return (signedSample < 0 ? -1 : 1) * Math.min(maximumLatticeUnits, Math.max(1, Math.abs(sampledLatticeUnits))) / COORDINATE_SCALE;
549
+ };
550
+ const createCrestPath = (crest, centerX, edge, railY, peakHeight) => {
551
+ if (crest === "none") return;
552
+ const upperHeight = railY - edge;
553
+ const tipY = edge;
554
+ const shoulderY = canonicalNumber(edge + Math.min(18, upperHeight * (.28 + peakHeight * .16)));
555
+ const halfWidth = canonicalNumber(Math.min(8, upperHeight * .18));
556
+ if (crest === "spear") return canonicalizePath([
557
+ {
558
+ kind: "M",
559
+ x: centerX,
560
+ y: railY
561
+ },
562
+ {
563
+ kind: "L",
564
+ x: centerX,
565
+ y: tipY
566
+ },
567
+ {
568
+ kind: "M",
569
+ x: centerX,
570
+ y: tipY
571
+ },
572
+ {
573
+ kind: "L",
574
+ x: centerX + halfWidth,
575
+ y: shoulderY
576
+ },
577
+ {
578
+ kind: "L",
579
+ x: centerX,
580
+ y: shoulderY + halfWidth
581
+ },
582
+ {
583
+ kind: "L",
584
+ x: centerX - halfWidth,
585
+ y: shoulderY
586
+ },
587
+ { kind: "Z" }
588
+ ]);
589
+ if (crest === "fleur") {
590
+ const crestHeight = canonicalNumber(upperHeight * (.44 + peakHeight * .04));
591
+ const crestTopY = canonicalNumber(railY - crestHeight);
592
+ const sidePetalHalfWidth = canonicalNumber(crestHeight * .39);
593
+ const collarHalfWidth = canonicalNumber(crestHeight * .26);
594
+ const centralPetalHalfWidth = canonicalNumber(crestHeight * .18);
595
+ const lowerSepalHalfWidth = canonicalNumber(crestHeight * .37);
596
+ const sidePetalY = canonicalNumber(crestTopY + crestHeight * .38);
597
+ const collarY = canonicalNumber(crestTopY + crestHeight * .7);
598
+ const centralPetal = canonicalizePath([
599
+ {
600
+ kind: "M",
601
+ x: centerX,
602
+ y: collarY
603
+ },
604
+ {
605
+ kind: "C",
606
+ x1: centerX - centralPetalHalfWidth,
607
+ y1: crestTopY + crestHeight * .52,
608
+ x2: centerX - centralPetalHalfWidth,
609
+ y2: crestTopY + crestHeight * .16,
610
+ x: centerX,
611
+ y: crestTopY
612
+ },
613
+ {
614
+ kind: "C",
615
+ x1: centerX + centralPetalHalfWidth,
616
+ y1: crestTopY + crestHeight * .16,
617
+ x2: centerX + centralPetalHalfWidth,
618
+ y2: crestTopY + crestHeight * .52,
619
+ x: centerX,
620
+ y: collarY
621
+ },
622
+ { kind: "Z" }
623
+ ]);
624
+ const leftSidePetal = canonicalizePath([
625
+ {
626
+ kind: "M",
627
+ x: centerX,
628
+ y: collarY
629
+ },
630
+ {
631
+ kind: "C",
632
+ x1: centerX - collarHalfWidth * .35,
633
+ y1: crestTopY + crestHeight * .56,
634
+ x2: centerX - sidePetalHalfWidth * .72,
635
+ y2: crestTopY + crestHeight * .23,
636
+ x: centerX - sidePetalHalfWidth,
637
+ y: sidePetalY
638
+ },
639
+ {
640
+ kind: "C",
641
+ x1: centerX - sidePetalHalfWidth * 1.02,
642
+ y1: crestTopY + crestHeight * .52,
643
+ x2: centerX - collarHalfWidth * 1.25,
644
+ y2: crestTopY + crestHeight * .66,
645
+ x: centerX - collarHalfWidth,
646
+ y: collarY
647
+ }
648
+ ]);
649
+ const leftSepal = canonicalizePath([{
650
+ kind: "M",
651
+ x: centerX - collarHalfWidth,
652
+ y: collarY
653
+ }, {
654
+ kind: "C",
655
+ x1: centerX - lowerSepalHalfWidth,
656
+ y1: crestTopY + crestHeight * .79,
657
+ x2: centerX - lowerSepalHalfWidth,
658
+ y2: crestTopY + crestHeight * .91,
659
+ x: centerX,
660
+ y: railY
661
+ }]);
662
+ return canonicalizePath([
663
+ {
664
+ kind: "M",
665
+ x: centerX,
666
+ y: railY
667
+ },
668
+ {
669
+ kind: "L",
670
+ x: centerX,
671
+ y: collarY
672
+ },
673
+ ...leftSidePetal,
674
+ ...mirrorPath(leftSidePetal, centerX * 2),
675
+ {
676
+ kind: "M",
677
+ x: centerX - collarHalfWidth,
678
+ y: collarY
679
+ },
680
+ {
681
+ kind: "L",
682
+ x: centerX + collarHalfWidth,
683
+ y: collarY
684
+ },
685
+ ...leftSepal,
686
+ ...mirrorPath(leftSepal, centerX * 2),
687
+ ...centralPetal
688
+ ]);
689
+ }
690
+ return canonicalizePath([
691
+ {
692
+ kind: "M",
693
+ x: centerX,
694
+ y: railY
695
+ },
696
+ {
697
+ kind: "L",
698
+ x: centerX,
699
+ y: tipY
700
+ },
701
+ {
702
+ kind: "M",
703
+ x: centerX,
704
+ y: tipY
705
+ },
706
+ {
707
+ kind: "L",
708
+ x: centerX + halfWidth,
709
+ y: shoulderY
710
+ },
711
+ {
712
+ kind: "L",
713
+ x: centerX,
714
+ y: shoulderY + halfWidth * 2
715
+ },
716
+ {
717
+ kind: "L",
718
+ x: centerX - halfWidth,
719
+ y: shoulderY
720
+ },
721
+ { kind: "Z" }
722
+ ]);
723
+ };
724
+ const composeCourtyard = (seed, options, layout) => {
725
+ const { width, edge, railY, bottomY, frameBand, safeLeft } = layout;
726
+ const composition = sampleIndex$1(seed, "courtyard/composition", 3);
727
+ const motifLevel = Math.min(2, Math.floor(3 * options.density + sampleScalar$1(seed, "courtyard/motif-count")));
728
+ const sideLevel = Math.min(2, Math.floor(3 * options.sideComplexity + sampleScalar$1(seed, "courtyard/sides")));
729
+ const scrollPairs = [
730
+ 1,
731
+ 2,
732
+ 3
733
+ ][motifLevel];
734
+ const upperHeight = railY - edge;
735
+ const halfSpan = width / 2 - edge;
736
+ const scrollReach = canonicalNumber(halfSpan * (.42 + .26 * sampleScalar$1(seed, "courtyard/anchor/scroll-reach")));
737
+ const lift = canonicalNumber(upperHeight * (.42 + options.curvature * .18 + options.peakHeight * .12));
738
+ const structural = [];
739
+ if (composition === 1) {
740
+ const bayWidth = (width - edge * 2) / 3;
741
+ for (let index = 0; index < 3; index += 1) {
742
+ const left = canonicalNumber(edge + bayWidth * index);
743
+ const right = canonicalNumber(edge + bayWidth * (index + 1));
744
+ const bayLift = canonicalNumber(lift * (index === 1 ? 1 : .72));
745
+ const bayPath = canonicalizePath([{
746
+ kind: "M",
747
+ x: left,
748
+ y: railY
749
+ }, {
750
+ kind: "Q",
751
+ x1: (left + right) / 2,
752
+ y1: railY - bayLift,
753
+ x: right,
754
+ y: railY
755
+ }]);
756
+ structural.push(...index === 1 ? pairedCenteredPaths(bayPath, width) : [bayPath]);
757
+ }
758
+ const centerPair = centerPairForWidth(width);
759
+ if (centerPair.left !== centerPair.right) {
760
+ const leftBay = structural[0];
761
+ structural[structural.length - 1] = mirrorPath(leftBay, width);
762
+ }
763
+ } else if (composition === 2) {
764
+ const shoulder = canonicalNumber(width / 2 - Math.min(scrollReach * .38, halfSpan * .34));
765
+ const crownY = canonicalNumber(edge + upperHeight * (.28 - options.peakHeight * .08));
766
+ const bridgePath = canonicalizePath([
767
+ {
768
+ kind: "M",
769
+ x: edge,
770
+ y: railY
771
+ },
772
+ {
773
+ kind: "L",
774
+ x: shoulder,
775
+ y: railY - lift * .48
776
+ },
777
+ {
778
+ kind: "Q",
779
+ x1: width / 2,
780
+ y1: crownY,
781
+ x: width - shoulder,
782
+ y: railY - lift * .48
783
+ },
784
+ {
785
+ kind: "L",
786
+ x: width - edge,
787
+ y: railY
788
+ }
789
+ ]);
790
+ structural.push(...pairedCenteredPaths(bridgePath, width));
791
+ const centerStem = canonicalizePath([{
792
+ kind: "M",
793
+ x: width / 2,
794
+ y: railY
795
+ }, {
796
+ kind: "L",
797
+ x: width / 2,
798
+ y: crownY
799
+ }]);
800
+ structural.push(...pairedCenteredPaths(centerStem, width));
801
+ }
802
+ for (let index = 0; index < scrollPairs; index += 1) {
803
+ const nesting = 1 - index * .17;
804
+ const startX = canonicalNumber(edge + index * Math.min(5, frameBand * .22));
805
+ const endX = canonicalNumber(edge + scrollReach * nesting);
806
+ const scrollLift = canonicalNumber(lift * nesting);
807
+ const leftScroll = canonicalizePath([
808
+ {
809
+ kind: "M",
810
+ x: startX,
811
+ y: railY
812
+ },
813
+ {
814
+ kind: "C",
815
+ x1: startX + (endX - startX) * .18,
816
+ y1: railY - scrollLift * .52,
817
+ x2: startX + (endX - startX) * .54,
818
+ y2: railY - scrollLift,
819
+ x: endX,
820
+ y: railY - scrollLift * .58
821
+ },
822
+ {
823
+ kind: "C",
824
+ x1: endX + (endX - startX) * .08,
825
+ y1: railY - scrollLift * .26,
826
+ x2: endX - (endX - startX) * .12,
827
+ y2: railY - scrollLift * .08,
828
+ x: endX - (endX - startX) * .28,
829
+ y: railY
830
+ }
831
+ ]);
832
+ const rightDisplacement = quantizedSymmetryDisplacement(options.symmetry, sampleScalar$1(seed, "courtyard/detail", index) - .5, lift * .08);
833
+ structural.push(leftScroll, displaceInteriorY(mirrorPath(leftScroll, width), rightDisplacement));
834
+ }
835
+ const sideMiddle = canonicalNumber((railY + bottomY) / 2);
836
+ const sideReach = canonicalNumber(safeLeft - edge - 1.0001);
837
+ const leftSide = sideLevel === 0 ? canonicalizePath([
838
+ {
839
+ kind: "M",
840
+ x: edge,
841
+ y: railY
842
+ },
843
+ {
844
+ kind: "L",
845
+ x: edge + sideReach * .52,
846
+ y: sideMiddle
847
+ },
848
+ {
849
+ kind: "L",
850
+ x: edge,
851
+ y: bottomY
852
+ }
853
+ ]) : canonicalizePath([
854
+ {
855
+ kind: "M",
856
+ x: edge,
857
+ y: railY
858
+ },
859
+ {
860
+ kind: "C",
861
+ x1: edge + sideReach,
862
+ y1: railY + (sideMiddle - railY) * .42,
863
+ x2: edge + sideReach,
864
+ y2: sideMiddle - frameBand * .35,
865
+ x: edge + sideReach * .5,
866
+ y: sideMiddle
867
+ },
868
+ {
869
+ kind: "C",
870
+ x1: edge,
871
+ y1: sideMiddle + frameBand * .35,
872
+ x2: edge,
873
+ y2: bottomY - (bottomY - sideMiddle) * .42,
874
+ x: edge,
875
+ y: bottomY
876
+ }
877
+ ]);
878
+ return {
879
+ structural,
880
+ decorative: [leftSide, mirrorPath(leftSide, width)]
881
+ };
882
+ };
883
+ const composeFleuron = (seed, options, layout) => {
884
+ const { width, edge, railY, bottomY, frameBand, safeLeft } = layout;
885
+ const composition = sampleIndex$1(seed, "fleuron/composition", 3);
886
+ const motifLevel = Math.min(2, Math.floor(3 * options.density + sampleScalar$1(seed, "fleuron/motif-count")));
887
+ const sideLevel = Math.min(2, Math.floor(3 * options.sideComplexity + sampleScalar$1(seed, "fleuron/sides")));
888
+ const clusterCount = [
889
+ 1,
890
+ 2,
891
+ 3
892
+ ][motifLevel];
893
+ const upperHeight = railY - edge;
894
+ const upperRight = canonicalNumber(width - edge);
895
+ const upperWidth = canonicalNumber(upperRight - edge);
896
+ const petalSpan = canonicalNumber(upperWidth * (.12 + .1 * sampleScalar$1(seed, "fleuron/proportion/petal-span")));
897
+ const lift = canonicalNumber(upperHeight * (.5 + options.curvature * .16 + options.peakHeight * .16));
898
+ const centerX = width / 2;
899
+ const structural = [];
900
+ const clusterTier = (index) => Math.min(index, clusterCount - 1 - index) % 2;
901
+ if (composition === 0) {
902
+ const leftBrace = canonicalizePath([
903
+ {
904
+ kind: "M",
905
+ x: edge,
906
+ y: railY
907
+ },
908
+ {
909
+ kind: "C",
910
+ x1: edge + petalSpan * .3,
911
+ y1: railY - lift * .5,
912
+ x2: centerX - petalSpan,
913
+ y2: railY - lift,
914
+ x: centerX,
915
+ y: railY - lift * .58
916
+ },
917
+ {
918
+ kind: "C",
919
+ x1: centerX - petalSpan * .3,
920
+ y1: railY - lift * .34,
921
+ x2: centerX - petalSpan * .18,
922
+ y2: railY - lift * .12,
923
+ x: centerX,
924
+ y: railY
925
+ }
926
+ ]);
927
+ structural.push(leftBrace, mirrorPath(leftBrace, width));
928
+ const centerStem = canonicalizePath([{
929
+ kind: "M",
930
+ x: centerX,
931
+ y: railY
932
+ }, {
933
+ kind: "L",
934
+ x: centerX,
935
+ y: edge + upperHeight * .2
936
+ }]);
937
+ structural.push(...pairedCenteredPaths(centerStem, width));
938
+ } else if (composition === 1) {
939
+ const heartPath = canonicalizePath([
940
+ {
941
+ kind: "M",
942
+ x: edge,
943
+ y: railY
944
+ },
945
+ {
946
+ kind: "C",
947
+ x1: edge + petalSpan * .55,
948
+ y1: railY - lift * .58,
949
+ x2: centerX - petalSpan * .72,
950
+ y2: railY - lift,
951
+ x: centerX,
952
+ y: railY - lift * .42
953
+ },
954
+ {
955
+ kind: "C",
956
+ x1: centerX + petalSpan * .72,
957
+ y1: railY - lift,
958
+ x2: width - edge - petalSpan * .55,
959
+ y2: railY - lift * .58,
960
+ x: width - edge,
961
+ y: railY
962
+ }
963
+ ]);
964
+ structural.push(...pairedCenteredPaths(heartPath, width));
965
+ }
966
+ const decorative = [];
967
+ const stemPairSources = /* @__PURE__ */ new Map();
968
+ const petalPairSources = /* @__PURE__ */ new Map();
969
+ for (let index = 0; index < clusterCount; index += 1) {
970
+ const mirrorIndex = clusterCount - 1 - index;
971
+ if (index > mirrorIndex) {
972
+ const stemSource = stemPairSources.get(mirrorIndex);
973
+ const source = petalPairSources.get(mirrorIndex);
974
+ structural.push(mirrorPath(stemSource, width));
975
+ decorative.push(mirrorPath(source, width));
976
+ continue;
977
+ }
978
+ const offset = (index - (clusterCount - 1) / 2) * petalSpan * .72;
979
+ const rootX = canonicalNumber(centerX + offset * 1.45);
980
+ const flowerX = canonicalNumber(centerX + offset);
981
+ const flowerY = canonicalNumber(railY - lift * (.58 + (clusterTier(index) === 0 ? .16 : 0)));
982
+ const halfPetal = canonicalNumber(Math.min(petalSpan * .22, upperHeight * .2));
983
+ const stemPath = canonicalizePath([{
984
+ kind: "M",
985
+ x: rootX,
986
+ y: railY
987
+ }, {
988
+ kind: "Q",
989
+ x1: rootX + (flowerX - rootX) * .35,
990
+ y1: railY - lift * .36,
991
+ x: flowerX,
992
+ y: flowerY + halfPetal
993
+ }]);
994
+ const petalPath = canonicalizePath([
995
+ {
996
+ kind: "M",
997
+ x: flowerX,
998
+ y: flowerY + halfPetal
999
+ },
1000
+ {
1001
+ kind: "Q",
1002
+ x1: flowerX - halfPetal,
1003
+ y1: flowerY,
1004
+ x: flowerX,
1005
+ y: flowerY - halfPetal
1006
+ },
1007
+ {
1008
+ kind: "Q",
1009
+ x1: flowerX + halfPetal,
1010
+ y1: flowerY,
1011
+ x: flowerX,
1012
+ y: flowerY + halfPetal
1013
+ },
1014
+ { kind: "Z" }
1015
+ ]);
1016
+ if (index === mirrorIndex) {
1017
+ structural.push(...pairedCenteredPaths(stemPath, width));
1018
+ decorative.push(...pairedCenteredPaths(petalPath, width));
1019
+ } else {
1020
+ stemPairSources.set(index, stemPath);
1021
+ petalPairSources.set(index, petalPath);
1022
+ structural.push(stemPath);
1023
+ decorative.push(petalPath);
1024
+ }
1025
+ }
1026
+ const sideMiddle = canonicalNumber((railY + bottomY) / 2);
1027
+ const sideReach = canonicalNumber(safeLeft - edge - 1.0001);
1028
+ const leftSide = sideLevel === 0 ? canonicalizePath([
1029
+ {
1030
+ kind: "M",
1031
+ x: edge,
1032
+ y: bottomY
1033
+ },
1034
+ {
1035
+ kind: "L",
1036
+ x: edge + sideReach * .72,
1037
+ y: sideMiddle
1038
+ },
1039
+ {
1040
+ kind: "L",
1041
+ x: edge,
1042
+ y: railY
1043
+ }
1044
+ ]) : sideLevel === 1 ? canonicalizePath([
1045
+ {
1046
+ kind: "M",
1047
+ x: edge,
1048
+ y: sideMiddle + frameBand * .42
1049
+ },
1050
+ {
1051
+ kind: "Q",
1052
+ x1: edge + sideReach,
1053
+ y1: sideMiddle,
1054
+ x: edge,
1055
+ y: sideMiddle - frameBand * .42
1056
+ },
1057
+ {
1058
+ kind: "Q",
1059
+ x1: edge + sideReach * .45,
1060
+ y1: sideMiddle,
1061
+ x: edge,
1062
+ y: sideMiddle + frameBand * .42
1063
+ },
1064
+ { kind: "Z" }
1065
+ ]) : canonicalizePath([
1066
+ {
1067
+ kind: "M",
1068
+ x: edge,
1069
+ y: railY
1070
+ },
1071
+ {
1072
+ kind: "C",
1073
+ x1: edge + sideReach,
1074
+ y1: railY + (sideMiddle - railY) * .34,
1075
+ x2: edge + sideReach,
1076
+ y2: sideMiddle - frameBand * .28,
1077
+ x: edge + sideReach * .35,
1078
+ y: sideMiddle
1079
+ },
1080
+ {
1081
+ kind: "C",
1082
+ x1: edge + sideReach,
1083
+ y1: sideMiddle + frameBand * .28,
1084
+ x2: edge + sideReach,
1085
+ y2: bottomY - (bottomY - sideMiddle) * .34,
1086
+ x: edge,
1087
+ y: bottomY
1088
+ }
1089
+ ]);
1090
+ const rightSideDisplacement = quantizedSymmetryDisplacement(options.symmetry, sampleScalar$1(seed, "fleuron/detail", clusterCount) - .5, frameBand * .12);
1091
+ decorative.push(leftSide, displaceInteriorY(mirrorPath(leftSide, width), rightSideDisplacement));
1092
+ return {
1093
+ structural,
1094
+ decorative
1095
+ };
1096
+ };
1097
+ const composeVine = (seed, options, layout) => {
1098
+ const { width, edge, railY, bottomY, frameBand, safeLeft } = layout;
1099
+ const composition = sampleIndex$1(seed, "vine/composition", 3);
1100
+ const motifLevel = Math.min(2, Math.floor(3 * options.density + sampleScalar$1(seed, "vine/motif-count")));
1101
+ const sideLevel = Math.min(2, Math.floor(3 * options.sideComplexity + sampleScalar$1(seed, "vine/sides")));
1102
+ const leafPairs = [
1103
+ 2,
1104
+ 4,
1105
+ 6
1106
+ ][motifLevel];
1107
+ const curlPairCount = [
1108
+ 1,
1109
+ 1,
1110
+ 2
1111
+ ][motifLevel];
1112
+ const upperHeight = railY - edge;
1113
+ const halfSpan = width / 2 - edge;
1114
+ const leafNode = .22 + .56 * sampleScalar$1(seed, "vine/anchor/leaf-node");
1115
+ const lift = canonicalNumber(upperHeight * (.52 + options.curvature * .16 + options.peakHeight * .14));
1116
+ const structural = [];
1117
+ if (composition === 0) {
1118
+ const leftStem = canonicalizePath([
1119
+ {
1120
+ kind: "M",
1121
+ x: edge,
1122
+ y: railY
1123
+ },
1124
+ {
1125
+ kind: "C",
1126
+ x1: edge + halfSpan * .12,
1127
+ y1: railY - lift * .28,
1128
+ x2: edge + halfSpan * .28,
1129
+ y2: railY - lift * .78,
1130
+ x: edge + halfSpan * .62,
1131
+ y: railY - lift
1132
+ },
1133
+ {
1134
+ kind: "C",
1135
+ x1: edge + halfSpan * .78,
1136
+ y1: railY - lift * .96,
1137
+ x2: width / 2 - halfSpan * .08,
1138
+ y2: railY - lift * .5,
1139
+ x: width / 2,
1140
+ y: railY
1141
+ }
1142
+ ]);
1143
+ structural.push(leftStem, mirrorPath(leftStem, width));
1144
+ } else if (composition === 1) {
1145
+ const leftCanopy = canonicalizePath([
1146
+ {
1147
+ kind: "M",
1148
+ x: edge,
1149
+ y: railY
1150
+ },
1151
+ {
1152
+ kind: "C",
1153
+ x1: edge + halfSpan * .18,
1154
+ y1: railY - lift * .72,
1155
+ x2: edge + halfSpan * .52,
1156
+ y2: railY - lift,
1157
+ x: width / 2,
1158
+ y: railY - lift * .62
1159
+ },
1160
+ {
1161
+ kind: "C",
1162
+ x1: width / 2 + halfSpan * .18,
1163
+ y1: railY - lift * .38,
1164
+ x2: width / 2 + halfSpan * .3,
1165
+ y2: railY - lift * .82,
1166
+ x: width / 2 + halfSpan * .48,
1167
+ y: railY - lift * .54
1168
+ },
1169
+ {
1170
+ kind: "C",
1171
+ x1: width / 2 + halfSpan * .62,
1172
+ y1: railY - lift * .3,
1173
+ x2: width - edge - halfSpan * .08,
1174
+ y2: railY - lift * .16,
1175
+ x: width - edge,
1176
+ y: railY
1177
+ }
1178
+ ]);
1179
+ structural.push(leftCanopy, mirrorPath(leftCanopy, width));
1180
+ } else {
1181
+ const leftBranch = canonicalizePath([
1182
+ {
1183
+ kind: "M",
1184
+ x: edge,
1185
+ y: railY
1186
+ },
1187
+ {
1188
+ kind: "C",
1189
+ x1: edge + halfSpan * .08,
1190
+ y1: railY - lift * .22,
1191
+ x2: edge + halfSpan * .2,
1192
+ y2: railY - lift * .86,
1193
+ x: edge + halfSpan * .48,
1194
+ y: railY - lift * .82
1195
+ },
1196
+ {
1197
+ kind: "C",
1198
+ x1: edge + halfSpan * .7,
1199
+ y1: railY - lift * .78,
1200
+ x2: width / 2 - halfSpan * .12,
1201
+ y2: railY - lift * .34,
1202
+ x: width / 2,
1203
+ y: railY - lift * .56
1204
+ },
1205
+ {
1206
+ kind: "C",
1207
+ x1: width / 2 - halfSpan * .04,
1208
+ y1: railY - lift * .28,
1209
+ x2: width / 2 - halfSpan * .02,
1210
+ y2: railY - lift * .12,
1211
+ x: width / 2,
1212
+ y: railY
1213
+ }
1214
+ ]);
1215
+ structural.push(leftBranch, mirrorPath(leftBranch, width));
1216
+ const centerStem = canonicalizePath([{
1217
+ kind: "M",
1218
+ x: width / 2,
1219
+ y: railY
1220
+ }, {
1221
+ kind: "L",
1222
+ x: width / 2,
1223
+ y: railY - lift * .56
1224
+ }]);
1225
+ structural.push(...pairedCenteredPaths(centerStem, width));
1226
+ }
1227
+ for (let index = 0; index < curlPairCount; index += 1) {
1228
+ const rootX = canonicalNumber(edge + halfSpan * (.16 + index * .11));
1229
+ const reach = canonicalNumber(halfSpan * (.28 - index * .06));
1230
+ const curlLift = canonicalNumber(lift * (.68 - index * .12));
1231
+ const leftCurl = canonicalizePath([
1232
+ {
1233
+ kind: "M",
1234
+ x: rootX,
1235
+ y: railY
1236
+ },
1237
+ {
1238
+ kind: "C",
1239
+ x1: rootX + reach * .12,
1240
+ y1: railY - curlLift * .48,
1241
+ x2: rootX + reach * .54,
1242
+ y2: railY - curlLift,
1243
+ x: rootX + reach * .82,
1244
+ y: railY - curlLift * .72
1245
+ },
1246
+ {
1247
+ kind: "C",
1248
+ x1: rootX + reach * 1.08,
1249
+ y1: railY - curlLift * .52,
1250
+ x2: rootX + reach,
1251
+ y2: railY - curlLift * .18,
1252
+ x: rootX + reach * .76,
1253
+ y: railY - curlLift * .28
1254
+ },
1255
+ {
1256
+ kind: "C",
1257
+ x1: rootX + reach * .56,
1258
+ y1: railY - curlLift * .38,
1259
+ x2: rootX + reach * .6,
1260
+ y2: railY - curlLift * .58,
1261
+ x: rootX + reach * .73,
1262
+ y: railY - curlLift * .5
1263
+ }
1264
+ ]);
1265
+ const rightDisplacement = quantizedSymmetryDisplacement(options.symmetry, sampleScalar$1(seed, "vine/detail", leafPairs * 2 + index) - .5, curlLift * .06);
1266
+ structural.push(leftCurl, displaceInteriorY(mirrorPath(leftCurl, width), rightDisplacement));
1267
+ }
1268
+ const decorative = [];
1269
+ const leafHalfWidth = canonicalNumber(Math.min(halfSpan * .035, upperHeight * .12));
1270
+ const leafHalfHeight = canonicalNumber(Math.min(upperHeight * .075, 7));
1271
+ for (let index = 0; index < leafPairs; index += 1) {
1272
+ const detail = sampleScalar$1(seed, "vine/detail", index);
1273
+ const along = .18 + .64 * (index + leafNode) / (leafPairs + 1);
1274
+ const leafX = canonicalNumber(edge + halfSpan * along);
1275
+ const leftLeafDisplacement = quantizedSymmetryDisplacement(options.symmetry, .5 - detail, lift * .08);
1276
+ const leafY = canonicalNumber(railY - lift * (.28 + along * .56) + leftLeafDisplacement);
1277
+ const leftLeaf = canonicalizePath([
1278
+ {
1279
+ kind: "M",
1280
+ x: leafX,
1281
+ y: leafY + leafHalfHeight
1282
+ },
1283
+ {
1284
+ kind: "Q",
1285
+ x1: leafX - leafHalfWidth,
1286
+ y1: leafY,
1287
+ x: leafX,
1288
+ y: leafY - leafHalfHeight
1289
+ },
1290
+ {
1291
+ kind: "Q",
1292
+ x1: leafX + leafHalfWidth,
1293
+ y1: leafY,
1294
+ x: leafX,
1295
+ y: leafY + leafHalfHeight
1296
+ },
1297
+ { kind: "Z" }
1298
+ ]);
1299
+ const leftTwig = canonicalizePath([{
1300
+ kind: "M",
1301
+ x: leafX,
1302
+ y: railY
1303
+ }, {
1304
+ kind: "Q",
1305
+ x1: leafX + (index % 2 === 0 ? -leafHalfWidth : leafHalfWidth),
1306
+ y1: (railY + leafY) / 2,
1307
+ x: leafX,
1308
+ y: leafY + leafHalfHeight
1309
+ }]);
1310
+ const rightLeafDisplacement = quantizedSymmetryDisplacement(options.symmetry, sampleScalar$1(seed, "vine/detail", index + leafPairs) - .5, leafHalfHeight * .24);
1311
+ structural.push(leftTwig, mirrorPath(leftTwig, width));
1312
+ decorative.push(leftLeaf, displaceInteriorY(mirrorPath(leftLeaf, width), rightLeafDisplacement));
1313
+ }
1314
+ const sideMiddle = canonicalNumber((railY + bottomY) / 2);
1315
+ const sideReach = canonicalNumber(safeLeft - edge - 1.0001);
1316
+ const leftSide = sideLevel === 0 ? canonicalizePath([{
1317
+ kind: "M",
1318
+ x: edge,
1319
+ y: bottomY
1320
+ }, {
1321
+ kind: "L",
1322
+ x: edge + sideReach * .62,
1323
+ y: railY
1324
+ }]) : sideLevel === 1 ? canonicalizePath([
1325
+ {
1326
+ kind: "M",
1327
+ x: edge,
1328
+ y: bottomY
1329
+ },
1330
+ {
1331
+ kind: "Q",
1332
+ x1: edge + sideReach,
1333
+ y1: sideMiddle,
1334
+ x: edge,
1335
+ y: railY
1336
+ },
1337
+ {
1338
+ kind: "L",
1339
+ x: edge + sideReach * .72,
1340
+ y: sideMiddle
1341
+ }
1342
+ ]) : canonicalizePath([
1343
+ {
1344
+ kind: "M",
1345
+ x: edge,
1346
+ y: bottomY
1347
+ },
1348
+ {
1349
+ kind: "C",
1350
+ x1: edge + sideReach,
1351
+ y1: bottomY - (bottomY - sideMiddle) * .46,
1352
+ x2: edge,
1353
+ y2: sideMiddle + frameBand * .3,
1354
+ x: edge + sideReach * .58,
1355
+ y: sideMiddle
1356
+ },
1357
+ {
1358
+ kind: "C",
1359
+ x1: edge + sideReach,
1360
+ y1: sideMiddle - frameBand * .3,
1361
+ x2: edge,
1362
+ y2: railY + (sideMiddle - railY) * .46,
1363
+ x: edge,
1364
+ y: railY
1365
+ }
1366
+ ]);
1367
+ decorative.push(leftSide, mirrorPath(leftSide, width));
1368
+ return {
1369
+ structural,
1370
+ decorative
1371
+ };
1372
+ };
1373
+ const composeFan = (seed, options, layout) => {
1374
+ const { width, edge, railY, bottomY, frameBand, safeLeft } = layout;
1375
+ const composition = sampleIndex$1(seed, "fan/composition", 3);
1376
+ const motifLevel = Math.min(2, Math.floor(3 * options.density + sampleScalar$1(seed, "fan/motif-count")));
1377
+ const sideLevel = Math.min(2, Math.floor(3 * options.sideComplexity + sampleScalar$1(seed, "fan/sides")));
1378
+ const raysPerHalf = [
1379
+ 3,
1380
+ 5,
1381
+ 7
1382
+ ][motifLevel];
1383
+ const upperHeight = railY - edge;
1384
+ const halfSpan = width / 2 - edge;
1385
+ const originRatio = .58 + .24 * sampleScalar$1(seed, "fan/anchor/origin-height");
1386
+ const originY = canonicalNumber(railY + (bottomY - railY) * originRatio);
1387
+ const lift = canonicalNumber(upperHeight * (.48 + options.peakHeight * .22 + options.curvature * .1));
1388
+ const structural = [];
1389
+ for (let index = 0; index < raysPerHalf; index += 1) {
1390
+ const fraction = (index + 1) / (raysPerHalf + 1);
1391
+ const detail = sampleScalar$1(seed, "fan/detail", index);
1392
+ const targetX = canonicalNumber(edge + halfSpan * fraction);
1393
+ const leftTargetDisplacement = quantizedSymmetryDisplacement(options.symmetry, .5 - detail, lift * .06);
1394
+ const targetY = canonicalNumber(railY - lift * (.34 + fraction * .62) + leftTargetDisplacement);
1395
+ const leftRay = composition === 0 ? canonicalizePath([
1396
+ {
1397
+ kind: "M",
1398
+ x: edge,
1399
+ y: originY
1400
+ },
1401
+ {
1402
+ kind: "L",
1403
+ x: edge,
1404
+ y: railY
1405
+ },
1406
+ {
1407
+ kind: "L",
1408
+ x: targetX,
1409
+ y: targetY
1410
+ }
1411
+ ]) : composition === 1 ? canonicalizePath([
1412
+ {
1413
+ kind: "M",
1414
+ x: edge,
1415
+ y: originY
1416
+ },
1417
+ {
1418
+ kind: "L",
1419
+ x: edge,
1420
+ y: railY
1421
+ },
1422
+ {
1423
+ kind: "L",
1424
+ x: edge + halfSpan * fraction * .24,
1425
+ y: railY + frameBand * (.72 - fraction * .24)
1426
+ },
1427
+ {
1428
+ kind: "L",
1429
+ x: edge + halfSpan * fraction * .58,
1430
+ y: railY - lift * fraction * .34
1431
+ },
1432
+ {
1433
+ kind: "L",
1434
+ x: targetX,
1435
+ y: targetY
1436
+ }
1437
+ ]) : canonicalizePath([
1438
+ {
1439
+ kind: "M",
1440
+ x: edge,
1441
+ y: originY
1442
+ },
1443
+ {
1444
+ kind: "L",
1445
+ x: edge,
1446
+ y: railY
1447
+ },
1448
+ {
1449
+ kind: "Q",
1450
+ x1: edge + halfSpan * fraction * .38,
1451
+ y1: railY - lift * fraction * .18,
1452
+ x: targetX,
1453
+ y: targetY
1454
+ },
1455
+ {
1456
+ kind: "L",
1457
+ x: width / 2,
1458
+ y: railY
1459
+ }
1460
+ ]);
1461
+ const mirroredRightRay = mirrorPath(leftRay, width);
1462
+ const rightTargetIndex = composition === 2 ? mirroredRightRay.length - 2 : mirroredRightRay.length - 1;
1463
+ const rightTargetDisplacement = quantizedSymmetryDisplacement(options.symmetry, sampleScalar$1(seed, "fan/detail", index + raysPerHalf) - .5, lift * .05);
1464
+ const rightRay = canonicalizePath(mirroredRightRay.map((command, commandIndex) => commandIndex === rightTargetIndex && (command.kind === "L" || command.kind === "Q") ? {
1465
+ ...command,
1466
+ y: command.y + rightTargetDisplacement
1467
+ } : command));
1468
+ structural.push(leftRay, rightRay);
1469
+ }
1470
+ const decorative = [];
1471
+ const sideMiddle = canonicalNumber((railY + bottomY) / 2);
1472
+ const sideReach = canonicalNumber(safeLeft - edge - 1.0001);
1473
+ const leftSide = sideLevel === 0 ? canonicalizePath([{
1474
+ kind: "M",
1475
+ x: edge + sideReach * .32,
1476
+ y: railY
1477
+ }, {
1478
+ kind: "L",
1479
+ x: edge + sideReach * .32,
1480
+ y: bottomY
1481
+ }]) : sideLevel === 1 ? canonicalizePath([
1482
+ {
1483
+ kind: "M",
1484
+ x: edge,
1485
+ y: railY
1486
+ },
1487
+ {
1488
+ kind: "L",
1489
+ x: edge + sideReach * .42,
1490
+ y: sideMiddle - frameBand * .48
1491
+ },
1492
+ {
1493
+ kind: "L",
1494
+ x: edge + sideReach,
1495
+ y: sideMiddle
1496
+ },
1497
+ {
1498
+ kind: "L",
1499
+ x: edge,
1500
+ y: bottomY
1501
+ }
1502
+ ]) : canonicalizePath([
1503
+ {
1504
+ kind: "M",
1505
+ x: edge,
1506
+ y: railY
1507
+ },
1508
+ {
1509
+ kind: "L",
1510
+ x: edge + sideReach * .72,
1511
+ y: sideMiddle
1512
+ },
1513
+ {
1514
+ kind: "L",
1515
+ x: edge,
1516
+ y: bottomY
1517
+ }
1518
+ ]);
1519
+ decorative.push(leftSide, mirrorPath(leftSide, width));
1520
+ return {
1521
+ structural,
1522
+ decorative
1523
+ };
1524
+ };
1525
+ const composeVolute = (seed, options, layout) => {
1526
+ const { width, edge, railY, bottomY, frameBand, safeLeft } = layout;
1527
+ const composition = sampleIndex$1(seed, "volute/composition", 3);
1528
+ const motifLevel = Math.min(2, Math.floor(3 * options.density + sampleScalar$1(seed, "volute/motif-count")));
1529
+ const sideLevel = Math.min(2, Math.floor(3 * options.sideComplexity + sampleScalar$1(seed, "volute/sides")));
1530
+ const pairCount = [
1531
+ 1,
1532
+ 2,
1533
+ 3
1534
+ ][motifLevel];
1535
+ const upperHeight = railY - edge;
1536
+ const halfSpan = width / 2 - edge;
1537
+ const upperRight = canonicalNumber(width - edge);
1538
+ const upperWidth = canonicalNumber(upperRight - edge);
1539
+ const coilRadius = canonicalNumber(upperWidth * (.1 + .08 * sampleScalar$1(seed, "volute/proportion/coil-radius")));
1540
+ const lift = canonicalNumber(upperHeight * (.5 + options.curvature * .2 + options.peakHeight * .14));
1541
+ const structural = [];
1542
+ if (composition === 0) for (let index = 0; index < pairCount; index += 1) {
1543
+ const nesting = 1 - index * .18;
1544
+ const startX = canonicalNumber(edge + index * Math.min(5, frameBand * .2));
1545
+ const centerX = canonicalNumber(edge + Math.min(halfSpan * .78, coilRadius * (1.3 + index * .2)));
1546
+ const coilLift = canonicalNumber(lift * nesting);
1547
+ const leftVolute = canonicalizePath([
1548
+ {
1549
+ kind: "M",
1550
+ x: startX,
1551
+ y: railY
1552
+ },
1553
+ {
1554
+ kind: "C",
1555
+ x1: startX + coilRadius * .18,
1556
+ y1: railY - coilLift * .54,
1557
+ x2: centerX - coilRadius * .5,
1558
+ y2: railY - coilLift,
1559
+ x: centerX,
1560
+ y: railY - coilLift * .66
1561
+ },
1562
+ {
1563
+ kind: "C",
1564
+ x1: centerX + coilRadius * .32,
1565
+ y1: railY - coilLift * .4,
1566
+ x2: centerX + coilRadius * .16,
1567
+ y2: railY - coilLift * .18,
1568
+ x: centerX - coilRadius * .08,
1569
+ y: railY - coilLift * .28
1570
+ },
1571
+ {
1572
+ kind: "C",
1573
+ x1: centerX - coilRadius * .2,
1574
+ y1: railY - coilLift * .36,
1575
+ x2: centerX - coilRadius * .24,
1576
+ y2: railY - coilLift * .1,
1577
+ x: centerX - coilRadius * .42,
1578
+ y: railY
1579
+ }
1580
+ ]);
1581
+ structural.push(leftVolute, mirrorPath(leftVolute, width));
1582
+ }
1583
+ else if (composition === 1) {
1584
+ const crownPath = canonicalizePath([
1585
+ {
1586
+ kind: "M",
1587
+ x: edge,
1588
+ y: railY
1589
+ },
1590
+ {
1591
+ kind: "C",
1592
+ x1: edge + coilRadius * .4,
1593
+ y1: railY - lift * .66,
1594
+ x2: width / 2 - coilRadius * 1.15,
1595
+ y2: railY - lift,
1596
+ x: width / 2 - coilRadius * .42,
1597
+ y: railY - lift * .6
1598
+ },
1599
+ {
1600
+ kind: "C",
1601
+ x1: width / 2 - coilRadius * .08,
1602
+ y1: railY - lift * .36,
1603
+ x2: width / 2 - coilRadius * .28,
1604
+ y2: railY - lift * .14,
1605
+ x: width / 2,
1606
+ y: railY - lift * .72
1607
+ },
1608
+ {
1609
+ kind: "C",
1610
+ x1: width / 2 + coilRadius * .28,
1611
+ y1: railY - lift * .14,
1612
+ x2: width / 2 + coilRadius * .08,
1613
+ y2: railY - lift * .36,
1614
+ x: width / 2 + coilRadius * .42,
1615
+ y: railY - lift * .6
1616
+ },
1617
+ {
1618
+ kind: "C",
1619
+ x1: width / 2 + coilRadius * 1.15,
1620
+ y1: railY - lift,
1621
+ x2: width - edge - coilRadius * .4,
1622
+ y2: railY - lift * .66,
1623
+ x: width - edge,
1624
+ y: railY
1625
+ }
1626
+ ]);
1627
+ structural.push(...pairedCenteredPaths(crownPath, width));
1628
+ for (let index = 0; index < pairCount; index += 1) {
1629
+ const spread = canonicalNumber(coilRadius * (.45 + index * .32));
1630
+ const bracePath = canonicalizePath([{
1631
+ kind: "M",
1632
+ x: width / 2 - spread,
1633
+ y: railY
1634
+ }, {
1635
+ kind: "Q",
1636
+ x1: width / 2,
1637
+ y1: railY - lift * (.28 + index * .08),
1638
+ x: width / 2 + spread,
1639
+ y: railY
1640
+ }]);
1641
+ structural.push(...pairedCenteredPaths(bracePath, width));
1642
+ }
1643
+ } else {
1644
+ for (let index = 0; index < pairCount; index += 1) {
1645
+ const nesting = 1 - index * .16;
1646
+ const startX = canonicalNumber(edge + index * Math.min(6, frameBand * .22));
1647
+ const reach = canonicalNumber(Math.min(halfSpan * .86, coilRadius * (1.25 + index * .28)));
1648
+ const cascadeLift = canonicalNumber(lift * nesting);
1649
+ const leftCascade = canonicalizePath([
1650
+ {
1651
+ kind: "M",
1652
+ x: startX,
1653
+ y: railY
1654
+ },
1655
+ {
1656
+ kind: "C",
1657
+ x1: startX + reach * .22,
1658
+ y1: railY - cascadeLift * .84,
1659
+ x2: startX + reach * .68,
1660
+ y2: railY - cascadeLift,
1661
+ x: startX + reach,
1662
+ y: railY - cascadeLift * .54
1663
+ },
1664
+ {
1665
+ kind: "C",
1666
+ x1: startX + reach * .88,
1667
+ y1: railY - cascadeLift * .22,
1668
+ x2: startX + reach * .58,
1669
+ y2: railY - cascadeLift * .14,
1670
+ x: startX + reach * .42,
1671
+ y: railY
1672
+ }
1673
+ ]);
1674
+ structural.push(leftCascade, mirrorPath(leftCascade, width));
1675
+ }
1676
+ const centerStem = canonicalizePath([{
1677
+ kind: "M",
1678
+ x: width / 2,
1679
+ y: railY
1680
+ }, {
1681
+ kind: "L",
1682
+ x: width / 2,
1683
+ y: railY - lift * .64
1684
+ }]);
1685
+ structural.push(...pairedCenteredPaths(centerStem, width));
1686
+ }
1687
+ const decorative = [];
1688
+ const sideMiddle = canonicalNumber((railY + bottomY) / 2);
1689
+ const sideReach = canonicalNumber(safeLeft - edge - 1.0001);
1690
+ const leftSide = sideLevel === 0 ? canonicalizePath([{
1691
+ kind: "M",
1692
+ x: edge,
1693
+ y: railY
1694
+ }, {
1695
+ kind: "C",
1696
+ x1: edge + sideReach,
1697
+ y1: railY + (sideMiddle - railY) * .42,
1698
+ x2: edge + sideReach,
1699
+ y2: sideMiddle - frameBand * .24,
1700
+ x: edge,
1701
+ y: sideMiddle
1702
+ }]) : sideLevel === 1 ? canonicalizePath([
1703
+ {
1704
+ kind: "M",
1705
+ x: edge,
1706
+ y: railY
1707
+ },
1708
+ {
1709
+ kind: "C",
1710
+ x1: edge + sideReach,
1711
+ y1: railY + (sideMiddle - railY) * .38,
1712
+ x2: edge,
1713
+ y2: sideMiddle - frameBand * .28,
1714
+ x: edge + sideReach * .62,
1715
+ y: sideMiddle
1716
+ },
1717
+ {
1718
+ kind: "C",
1719
+ x1: edge + sideReach,
1720
+ y1: sideMiddle + frameBand * .28,
1721
+ x2: edge,
1722
+ y2: bottomY - (bottomY - sideMiddle) * .38,
1723
+ x: edge,
1724
+ y: bottomY
1725
+ }
1726
+ ]) : canonicalizePath([
1727
+ {
1728
+ kind: "M",
1729
+ x: edge,
1730
+ y: railY
1731
+ },
1732
+ {
1733
+ kind: "C",
1734
+ x1: edge + sideReach,
1735
+ y1: railY + (sideMiddle - railY) * .28,
1736
+ x2: edge + sideReach,
1737
+ y2: sideMiddle - frameBand * .4,
1738
+ x: edge + sideReach * .46,
1739
+ y: sideMiddle - frameBand * .18
1740
+ },
1741
+ {
1742
+ kind: "C",
1743
+ x1: edge,
1744
+ y1: sideMiddle,
1745
+ x2: edge + sideReach,
1746
+ y2: sideMiddle + frameBand * .12,
1747
+ x: edge + sideReach * .68,
1748
+ y: sideMiddle + frameBand * .3
1749
+ },
1750
+ {
1751
+ kind: "C",
1752
+ x1: edge + sideReach,
1753
+ y1: sideMiddle + frameBand * .5,
1754
+ x2: edge,
1755
+ y2: bottomY - (bottomY - sideMiddle) * .28,
1756
+ x: edge,
1757
+ y: bottomY
1758
+ }
1759
+ ]);
1760
+ const rightSideDisplacement = quantizedSymmetryDisplacement(options.symmetry, sampleScalar$1(seed, "volute/detail", pairCount) - .5, frameBand * .12);
1761
+ decorative.push(leftSide, displaceInteriorY(mirrorPath(leftSide, width), rightSideDisplacement));
1762
+ return {
1763
+ structural,
1764
+ decorative
1765
+ };
1766
+ };
1767
+ function generateGateV2(dimensionsInput, optionsInput) {
1768
+ const dimensions = normalizeDimensions(dimensionsInput);
1769
+ const { seed, options } = normalizeFamilyOptions(optionsInput, 2);
1770
+ const { width, height } = dimensions;
1771
+ const edge = canonicalNumber(9);
1772
+ const railY = canonicalNumber(Math.max(edge + 32, height * .3));
1773
+ const bottomY = canonicalNumber(height - edge);
1774
+ const frameBand = canonicalNumber(Math.max(12, Math.min(24, Math.min(width, height) * .06)));
1775
+ const safeLeft = canonicalNumber(edge + frameBand);
1776
+ const safeRight = canonicalNumber(width - safeLeft);
1777
+ const safeTop = canonicalNumber(railY + frameBand);
1778
+ const safeBottom = canonicalNumber(bottomY - frameBand);
1779
+ const centerX = canonicalNumber(width / 2);
1780
+ const surfacePath = canonicalizePath([
1781
+ {
1782
+ kind: "M",
1783
+ x: edge,
1784
+ y: railY
1785
+ },
1786
+ {
1787
+ kind: "L",
1788
+ x: width - edge,
1789
+ y: railY
1790
+ },
1791
+ {
1792
+ kind: "L",
1793
+ x: width - edge,
1794
+ y: bottomY
1795
+ },
1796
+ {
1797
+ kind: "L",
1798
+ x: edge,
1799
+ y: bottomY
1800
+ },
1801
+ { kind: "Z" },
1802
+ {
1803
+ kind: "M",
1804
+ x: safeLeft,
1805
+ y: safeTop
1806
+ },
1807
+ {
1808
+ kind: "L",
1809
+ x: safeLeft,
1810
+ y: safeBottom
1811
+ },
1812
+ {
1813
+ kind: "L",
1814
+ x: safeRight,
1815
+ y: safeBottom
1816
+ },
1817
+ {
1818
+ kind: "L",
1819
+ x: safeRight,
1820
+ y: safeTop
1821
+ },
1822
+ { kind: "Z" }
1823
+ ]);
1824
+ const framePath = canonicalizePath([
1825
+ {
1826
+ kind: "M",
1827
+ x: edge,
1828
+ y: bottomY
1829
+ },
1830
+ {
1831
+ kind: "L",
1832
+ x: edge,
1833
+ y: railY
1834
+ },
1835
+ {
1836
+ kind: "L",
1837
+ x: width - edge,
1838
+ y: railY
1839
+ },
1840
+ {
1841
+ kind: "L",
1842
+ x: width - edge,
1843
+ y: bottomY
1844
+ },
1845
+ { kind: "Z" }
1846
+ ]);
1847
+ if (options.family === "courtyard" || options.family === "fleuron" || options.family === "vine" || options.family === "fan" || options.family === "volute") {
1848
+ const familyLayout = {
1849
+ width,
1850
+ edge,
1851
+ railY,
1852
+ bottomY,
1853
+ frameBand,
1854
+ safeLeft
1855
+ };
1856
+ const composed = options.family === "courtyard" ? composeCourtyard(seed, options, familyLayout) : options.family === "fleuron" ? composeFleuron(seed, options, familyLayout) : options.family === "vine" ? composeVine(seed, options, familyLayout) : options.family === "fan" ? composeFan(seed, options, familyLayout) : composeVolute(seed, options, familyLayout);
1857
+ const decorativePaths = [...composed.decorative];
1858
+ const crestPath = createCrestPath(options.crest, centerX, edge, railY, options.peakHeight);
1859
+ if (crestPath !== void 0) decorativePaths.push(...pairedCenteredPaths(crestPath, width));
1860
+ return deepFreeze({
1861
+ schemaVersion: 1,
1862
+ generationVersion: 2,
1863
+ seed,
1864
+ dimensions,
1865
+ viewBox: {
1866
+ minX: 0,
1867
+ minY: 0,
1868
+ width,
1869
+ height
1870
+ },
1871
+ options,
1872
+ bounds: {
1873
+ left: edge,
1874
+ right: canonicalNumber(width - edge),
1875
+ railY,
1876
+ bottomY
1877
+ },
1878
+ contentInsets: {
1879
+ top: safeTop,
1880
+ right: canonicalNumber(width - safeRight),
1881
+ bottom: canonicalNumber(height - safeBottom),
1882
+ left: safeLeft
1883
+ },
1884
+ surfacePath,
1885
+ structuralPaths: [framePath, ...composed.structural],
1886
+ decorativePaths
1887
+ });
1888
+ }
1889
+ const composition = sampleIndex$1(seed, "arcade/composition", 3);
1890
+ const motifLevel = Math.min(2, Math.floor(3 * options.density + sampleScalar$1(seed, "arcade/motif-count")));
1891
+ const sideLevel = Math.min(2, Math.floor(3 * options.sideComplexity + sampleScalar$1(seed, "arcade/sides")));
1892
+ const bayCount = [
1893
+ 2,
1894
+ 3,
1895
+ 5
1896
+ ][motifLevel];
1897
+ const span = width - edge * 2;
1898
+ const upperHeight = railY - edge;
1899
+ const springRatio = .32 + .26 * sampleScalar$1(seed, "arcade/anchor/spring-height");
1900
+ const archLift = canonicalNumber(upperHeight * springRatio * (.72 + options.curvature * .28) * (.78 + options.peakHeight * .22));
1901
+ const boundaries = Array.from({ length: bayCount + 1 }, (_, index) => canonicalNumber(edge + span * index / bayCount));
1902
+ const arches = [];
1903
+ for (let index = 0; index < bayCount; index += 1) {
1904
+ const left = boundaries[index];
1905
+ const right = boundaries[index + 1];
1906
+ const detail = sampleScalar$1(seed, "arcade/detail", index);
1907
+ const displacement = quantizedSymmetryDisplacement(options.symmetry, detail - .5, (right - left) * .16);
1908
+ const roundBay = canonicalizePath([{
1909
+ kind: "M",
1910
+ x: left,
1911
+ y: railY
1912
+ }, {
1913
+ kind: "Q",
1914
+ x1: (left + right) / 2 + displacement,
1915
+ y1: railY - archLift,
1916
+ x: right,
1917
+ y: railY
1918
+ }]);
1919
+ const center = (left + right) / 2;
1920
+ const pointedBay = canonicalizePath([
1921
+ {
1922
+ kind: "M",
1923
+ x: left,
1924
+ y: railY
1925
+ },
1926
+ {
1927
+ kind: "C",
1928
+ x1: left + (right - left) * .2 + displacement,
1929
+ y1: railY - archLift * .42,
1930
+ x2: center - (right - left) * .12 + displacement,
1931
+ y2: railY - archLift,
1932
+ x: center + displacement,
1933
+ y: railY - archLift
1934
+ },
1935
+ {
1936
+ kind: "C",
1937
+ x1: center + (right - left) * .12 + displacement,
1938
+ y1: railY - archLift,
1939
+ x2: right - (right - left) * .2 + displacement,
1940
+ y2: railY - archLift * .42,
1941
+ x: right,
1942
+ y: railY
1943
+ }
1944
+ ]);
1945
+ const mirroredPairIndex = Math.min(index, bayCount - 1 - index);
1946
+ arches.push(composition === 0 || composition === 2 && mirroredPairIndex % 2 === 0 ? roundBay : pointedBay);
1947
+ }
1948
+ const centerPair = centerPairForWidth(width);
1949
+ if (centerPair.left !== centerPair.right) {
1950
+ if (options.symmetry === 1) for (let index = 0; index < Math.floor(bayCount / 2); index += 1) {
1951
+ const leftBay = arches[index];
1952
+ arches[bayCount - 1 - index] = mirrorPath(leftBay, width);
1953
+ }
1954
+ if (bayCount % 2 === 1) {
1955
+ const middleIndex = Math.floor(bayCount / 2);
1956
+ const middleBay = arches[middleIndex];
1957
+ arches.splice(middleIndex + 1, 0, mirrorPath(middleBay, width));
1958
+ }
1959
+ }
1960
+ const decorativePaths = [];
1961
+ const crestPath = createCrestPath(options.crest, centerX, edge, railY, options.peakHeight);
1962
+ if (crestPath !== void 0) decorativePaths.push(...pairedCenteredPaths(crestPath, width));
1963
+ const sideCenterY = canonicalNumber((railY + bottomY) / 2);
1964
+ const sideReach = canonicalNumber(safeLeft - edge - 2);
1965
+ const leftSide = sideLevel === 0 ? canonicalizePath([{
1966
+ kind: "M",
1967
+ x: edge + sideReach * .34,
1968
+ y: railY
1969
+ }, {
1970
+ kind: "L",
1971
+ x: edge + sideReach * .34,
1972
+ y: bottomY
1973
+ }]) : sideLevel === 1 ? canonicalizePath([{
1974
+ kind: "M",
1975
+ x: edge,
1976
+ y: railY
1977
+ }, {
1978
+ kind: "Q",
1979
+ x1: edge + sideReach * (.65 + options.curvature * .25),
1980
+ y1: sideCenterY,
1981
+ x: edge,
1982
+ y: bottomY
1983
+ }]) : canonicalizePath([
1984
+ {
1985
+ kind: "M",
1986
+ x: edge,
1987
+ y: railY
1988
+ },
1989
+ {
1990
+ kind: "L",
1991
+ x: edge + sideReach * .55,
1992
+ y: sideCenterY - frameBand * .5
1993
+ },
1994
+ {
1995
+ kind: "L",
1996
+ x: edge + sideReach,
1997
+ y: sideCenterY
1998
+ },
1999
+ {
2000
+ kind: "L",
2001
+ x: edge + sideReach * .55,
2002
+ y: sideCenterY + frameBand * .5
2003
+ },
2004
+ {
2005
+ kind: "L",
2006
+ x: edge,
2007
+ y: bottomY
2008
+ }
2009
+ ]);
2010
+ decorativePaths.push(leftSide, mirrorPath(leftSide, width));
2011
+ return deepFreeze({
2012
+ schemaVersion: 1,
2013
+ generationVersion: 2,
2014
+ seed,
2015
+ dimensions,
2016
+ viewBox: {
2017
+ minX: 0,
2018
+ minY: 0,
2019
+ width,
2020
+ height
2021
+ },
2022
+ options,
2023
+ bounds: {
2024
+ left: edge,
2025
+ right: canonicalNumber(width - edge),
2026
+ railY,
2027
+ bottomY
2028
+ },
2029
+ contentInsets: {
2030
+ top: safeTop,
2031
+ right: canonicalNumber(width - safeRight),
2032
+ bottom: canonicalNumber(height - safeBottom),
2033
+ left: safeLeft
2034
+ },
2035
+ surfacePath,
2036
+ structuralPaths: [framePath, ...arches],
2037
+ decorativePaths
2038
+ });
2039
+ }
2040
+
2041
+ //#endregion
2042
+ //#region src/core/v3/generate.ts
2043
+ function generateGateV3(dimensions, optionsInput) {
2044
+ const { seed, options } = normalizeFamilyOptions(optionsInput, 3);
2045
+ dimensions = normalizeDimensions(dimensions);
2046
+ const { width, height } = dimensions;
2047
+ const edge = 9;
2048
+ const railY = canonicalNumber(Math.max(41, height * .3));
2049
+ const bottomY = canonicalNumber(height - edge);
2050
+ const frameBand = canonicalNumber(Math.max(12, Math.min(24, Math.min(width, height) * .06)));
2051
+ const safeLeft = canonicalNumber(edge + frameBand);
2052
+ const safeRight = canonicalNumber(width - safeLeft);
2053
+ const safeTop = canonicalNumber(railY + frameBand);
2054
+ const safeBottom = canonicalNumber(bottomY - frameBand);
2055
+ const source = {
2056
+ seed,
2057
+ options,
2058
+ dimensions,
2059
+ bounds: {
2060
+ left: edge,
2061
+ right: canonicalNumber(width - edge),
2062
+ railY,
2063
+ bottomY
2064
+ },
2065
+ contentInsets: {
2066
+ left: safeLeft,
2067
+ right: canonicalNumber(width - safeRight),
2068
+ top: safeTop,
2069
+ bottom: canonicalNumber(height - safeBottom)
2070
+ },
2071
+ surfacePath: canonicalizePath([
2072
+ m(edge, railY),
2073
+ l(width - edge, railY),
2074
+ l(width - edge, bottomY),
2075
+ l(edge, bottomY),
2076
+ z,
2077
+ m(safeLeft, safeTop),
2078
+ l(safeLeft, safeBottom),
2079
+ l(safeRight, safeBottom),
2080
+ l(safeRight, safeTop),
2081
+ z
2082
+ ]),
2083
+ structuralPaths: [canonicalizePath([
2084
+ m(edge, bottomY),
2085
+ l(edge, railY),
2086
+ l(width - edge, railY),
2087
+ l(width - edge, bottomY),
2088
+ z
2089
+ ])]
2090
+ };
2091
+ const { railY: rail, bottomY: bottom } = source.bounds;
2092
+ const { family, crest, density, curvature, peakHeight, sideComplexity } = source.options;
2093
+ const upper = (rail - edge) * (.94 + peakHeight * .06);
2094
+ const center = width / 2;
2095
+ const half = center - edge;
2096
+ const band = source.contentInsets.left - edge;
2097
+ const structure = [source.structuralPaths[0]];
2098
+ const ornaments = [];
2099
+ const fine = [];
2100
+ const solid = [];
2101
+ const centered = (collection, paths) => {
2102
+ for (const path of paths) {
2103
+ const canonical = canonicalizePath(path);
2104
+ collection.push(canonical);
2105
+ if (Math.round(width * 1e4) % 2 === 1) collection.push(mirrorPath(canonical, width));
2106
+ }
2107
+ };
2108
+ const rightHeightFactor = 1 - (1 - options.symmetry) * (.06 + .04 * sampleScalar(seed, `${family}/proportion/right-lift`));
2109
+ const pair = (collection, path) => {
2110
+ const canonical = canonicalizePath(path);
2111
+ const right = mirrorPath(canonical, width);
2112
+ const isUpper = path.every((command) => Object.entries(command).every(([key, value]) => !key.startsWith("y") || Number(value) <= rail));
2113
+ collection.push(canonical, isUpper && options.symmetry < 1 ? place(right, 0, rail * (1 - rightHeightFactor), 1, rightHeightFactor) : right);
2114
+ };
2115
+ const barX = edge + Math.min(7, band * .36);
2116
+ pair(fine, [m(barX, rail), l(barX, bottom)]);
2117
+ fine.push(canonicalizePath([m(edge, bottom - 7), l(width - edge, bottom - 7)]));
2118
+ for (const fraction of sideComplexity > .65 ? [
2119
+ .2,
2120
+ .5,
2121
+ .8
2122
+ ] : [.28, .72]) {
2123
+ const y = rail + (bottom - rail) * fraction;
2124
+ pair(solid, [
2125
+ m(edge, y - 1.5),
2126
+ l(barX + 2, y - 1.5),
2127
+ l(barX + 2, y + 1.5),
2128
+ l(edge, y + 1.5),
2129
+ z
2130
+ ]);
2131
+ }
2132
+ const crestHeight = Math.min(upper * (.7 + peakHeight * .12), width * .29);
2133
+ const crestTop = rail - crestHeight - 4;
2134
+ const reserve = crest === "fleur" ? crestHeight * .44 + 7 : crest === "none" ? 9 : 16;
2135
+ if (crest === "fleur") {
2136
+ const fleurHeight = crestHeight * .84;
2137
+ const [crown, collar, foot] = fleurDeLys(center, rail - fleurHeight - 4, fleurHeight);
2138
+ centered(fine, [crown, foot]);
2139
+ centered(solid, [collar]);
2140
+ centered(structure, [[m(center, rail), l(center, rail - 4)]]);
2141
+ } else if (crest !== "none") {
2142
+ const capHeight = Math.min(22, crestHeight * .35);
2143
+ const capWidth = capHeight * (crest === "diamond" ? .32 : .22);
2144
+ centered(solid, [placeEmblem([
2145
+ m(0, 0),
2146
+ l(capWidth, capHeight * .58),
2147
+ l(0, capHeight),
2148
+ l(-capWidth, capHeight * .58),
2149
+ z
2150
+ ], center, crestTop, 1)]);
2151
+ centered(structure, [[m(center, crestTop + capHeight), l(center, rail)]]);
2152
+ centered(fine, [canonicalizePath([m(center - 4, crestTop + capHeight + 4), l(center + 4, crestTop + capHeight + 4)])]);
2153
+ }
2154
+ const usable = half - reserve - 9;
2155
+ const variation = sampleScalar(source.seed, `${family}/proportion/study-lift`);
2156
+ const count = Math.max(1, Math.min(family === "arcade" ? 2 + Math.round(density * 2) : 1 + Math.round(density * 2), Math.floor(usable / 46)));
2157
+ const cell = usable / count;
2158
+ if (family === "fan") {
2159
+ const span = half - reserve;
2160
+ const lift = Math.min(upper * (.6 + variation * .12) * (.86 + curvature * .14), span * .9);
2161
+ const a = {
2162
+ x: edge,
2163
+ y: rail
2164
+ };
2165
+ const b = {
2166
+ x: edge + span * .2,
2167
+ y: rail - lift * .7
2168
+ };
2169
+ const d = {
2170
+ x: edge + span,
2171
+ y: rail - lift
2172
+ };
2173
+ const p = {
2174
+ x: edge + span * .65,
2175
+ y: rail - lift
2176
+ };
2177
+ pair(structure, [
2178
+ m(a.x, a.y),
2179
+ c(b.x, b.y, p.x, p.y, d.x, d.y),
2180
+ l(d.x, rail)
2181
+ ]);
2182
+ const rays = Math.min(3 + Math.round(density * 3), Math.max(2, Math.floor(span / 16)));
2183
+ for (let index = 1; index < rays; index++) {
2184
+ const tip = cubicPoint(a, b, p, d, index / rays);
2185
+ pair(ornaments, [m(edge + span, rail), l(tip.x, tip.y)]);
2186
+ }
2187
+ } else if (family === "arcade") for (let index = 0; index < count; index++) {
2188
+ const x = 16 + cell * index;
2189
+ const w = cell - 9;
2190
+ const h = Math.min(upper * (.6 + variation * .1 + .1 * index / count) * (.86 + curvature * .14), w * 1.15);
2191
+ const shoulder = rail - h * .5;
2192
+ pair(ornaments, [
2193
+ m(x, rail),
2194
+ l(x, shoulder),
2195
+ c(x, rail - h * .82, x + w * .22, rail - h, x + w * .5, rail - h),
2196
+ c(x + w * .78, rail - h, x + w, rail - h * .82, x + w, shoulder),
2197
+ l(x + w, rail)
2198
+ ]);
2199
+ }
2200
+ else if (family !== "vine") {
2201
+ const span = half - reserve;
2202
+ const coilWidth = Math.min(span * .57, upper * (family === "volute" ? 1.12 : .9));
2203
+ const coilX = edge + span - coilWidth;
2204
+ const height = Math.min(upper * (family === "courtyard" ? .61 : family === "fleuron" ? .79 : .88) * (.94 + curvature * .06 + variation * .035), coilWidth * 1.25);
2205
+ const top = rail - height;
2206
+ const curl = scroll(coilX, top, coilWidth, height);
2207
+ pair(ornaments, [
2208
+ m(edge, rail),
2209
+ c(edge, rail - height * .72, coilX + coilWidth * .18, top, coilX + coilWidth * .53, top),
2210
+ ...curl.slice(2, coilWidth < 34 ? 5 : void 0)
2211
+ ]);
2212
+ if (span > 120 && density > .25) {
2213
+ const smallWidth = Math.min(upper * .49, span * .25);
2214
+ const smallHeight = height * (family === "volute" ? .36 : .29);
2215
+ pair(fine, scroll(edge + span * .24, rail - smallHeight, smallWidth, smallHeight).slice(0, 5));
2216
+ }
2217
+ } else for (let index = 0; index < count; index++) {
2218
+ const w = Math.min(cell - 10, upper * .94);
2219
+ const x = 16 + cell * index + (cell - 10 - w) / 2;
2220
+ const hierarchy = count === 1 ? .78 : .54 + .24 * index / (count - 1);
2221
+ const h = Math.min(upper * hierarchy * (.88 + curvature * .12 + variation * .08), w * 1.25);
2222
+ const top = rail - h;
2223
+ const curl = scroll(x, top, w, h);
2224
+ pair(ornaments, w < 34 ? curl.slice(0, 5) : curl);
2225
+ if (family === "vine") for (const t of w > 50 ? [.24, .45] : [.34]) {
2226
+ const node = cubicPoint({
2227
+ x,
2228
+ y: rail
2229
+ }, {
2230
+ x,
2231
+ y: top + h * .48
2232
+ }, {
2233
+ x: x + w * .18,
2234
+ y: top
2235
+ }, {
2236
+ x: x + w * .53,
2237
+ y: top
2238
+ }, t);
2239
+ const size = Math.min(11, w * .16);
2240
+ pair(solid, [
2241
+ m(node.x, node.y),
2242
+ c(node.x - size * .6, node.y - size * .05, node.x - size, node.y - size * .75, node.x - size * .7, node.y - size * 1.5),
2243
+ c(node.x - size * .05, node.y - size, node.x + size * .12, node.y - size * .5, node.x, node.y),
2244
+ z
2245
+ ]);
2246
+ }
2247
+ }
2248
+ return deepFreeze({
2249
+ schemaVersion: 1,
2250
+ generationVersion: 3,
2251
+ seed,
2252
+ options,
2253
+ dimensions,
2254
+ viewBox: {
2255
+ minX: 0,
2256
+ minY: 0,
2257
+ width,
2258
+ height
2259
+ },
2260
+ bounds: source.bounds,
2261
+ contentInsets: source.contentInsets,
2262
+ surfacePath: source.surfacePath,
2263
+ structuralPaths: structure,
2264
+ finePaths: fine,
2265
+ decorativePaths: ornaments,
2266
+ solidPaths: solid
2267
+ });
2268
+ }
2269
+ const cubicPoint = (a, b, c, d, t) => {
2270
+ const u = 1 - t;
2271
+ return {
2272
+ x: u ** 3 * a.x + 3 * u ** 2 * t * b.x + 3 * u * t ** 2 * c.x + t ** 3 * d.x,
2273
+ y: u ** 3 * a.y + 3 * u ** 2 * t * b.y + 3 * u * t ** 2 * c.y + t ** 3 * d.y
2274
+ };
2275
+ };
2276
+ const m = (x, y) => ({
2277
+ kind: "M",
2278
+ x,
2279
+ y
2280
+ });
2281
+ const l = (x, y) => ({
2282
+ kind: "L",
2283
+ x,
2284
+ y
2285
+ });
2286
+ const c = (x1, y1, x2, y2, x, y) => ({
2287
+ kind: "C",
2288
+ x1,
2289
+ y1,
2290
+ x2,
2291
+ y2,
2292
+ x,
2293
+ y
2294
+ });
2295
+ const z = { kind: "Z" };
2296
+ const place = (path, x, y, sx, sy = sx) => canonicalizePath(path.map((command) => {
2297
+ if (command.kind === "Z") return command;
2298
+ const point = {
2299
+ x: x + command.x * sx,
2300
+ y: y + command.y * sy
2301
+ };
2302
+ if (command.kind === "C") return {
2303
+ kind: "C",
2304
+ ...point,
2305
+ x1: x + command.x1 * sx,
2306
+ y1: y + command.y1 * sy,
2307
+ x2: x + command.x2 * sx,
2308
+ y2: y + command.y2 * sy
2309
+ };
2310
+ if (command.kind === "Q") return {
2311
+ kind: "Q",
2312
+ ...point,
2313
+ x1: x + command.x1 * sx,
2314
+ y1: y + command.y1 * sy
2315
+ };
2316
+ return {
2317
+ kind: command.kind,
2318
+ ...point
2319
+ };
2320
+ }));
2321
+ /** Heraldic silhouette selected in ART-01. */
2322
+ const placeEmblem = (path, center, top, scale) => {
2323
+ const centerUnits = Math.round(center * 1e4);
2324
+ return canonicalizePath(path.map((command) => Object.fromEntries(Object.entries(command).map(([key, value]) => [key, key.startsWith("x") ? (centerUnits + Math.sign(Number(value)) * Math.round(Math.abs(Number(value)) * scale * 1e4)) / 1e4 : key.startsWith("y") ? top + Number(value) * scale : value]))));
2325
+ };
2326
+ function fleurDeLys(x, top, height) {
2327
+ const crown = [
2328
+ m(-.12, .682),
2329
+ c(-.14, .55, -.25, .39, -.3, .44),
2330
+ c(-.35, .49, -.3, .55, -.25, .5),
2331
+ c(-.28, .68, -.47, .55, -.36, .36),
2332
+ c(-.29, .25, -.15, .32, -.055, .51),
2333
+ c(-.13, .3, -.14, .16, 0, 0),
2334
+ c(.14, .16, .13, .3, .055, .51),
2335
+ c(.15, .32, .29, .25, .36, .36),
2336
+ c(.47, .55, .28, .68, .25, .5),
2337
+ c(.3, .55, .35, .49, .3, .44),
2338
+ c(.25, .39, .14, .55, .12, .682),
2339
+ l(-.12, .682),
2340
+ z
2341
+ ];
2342
+ const collar = [
2343
+ m(-.13, .682),
2344
+ l(.13, .682),
2345
+ l(.13, .708),
2346
+ l(-.13, .708),
2347
+ z
2348
+ ];
2349
+ const foot = [
2350
+ m(-.075, .708),
2351
+ c(-.08, .81, -.14, .85, -.2, .86),
2352
+ c(-.14, .93, -.07, .91, -.035, .86),
2353
+ c(-.04, .93, -.03, .97, 0, 1),
2354
+ c(.03, .97, .04, .93, .035, .86),
2355
+ c(.07, .91, .14, .93, .2, .86),
2356
+ c(.14, .85, .08, .81, .075, .708),
2357
+ l(-.075, .708),
2358
+ z
2359
+ ];
2360
+ return [
2361
+ placeEmblem(crown, x, top, height),
2362
+ placeEmblem(collar, x, top, height),
2363
+ placeEmblem(foot, x, top, height)
2364
+ ];
2365
+ }
2366
+ function scroll(x, top, width, height) {
2367
+ return place([
2368
+ m(0, 1),
2369
+ c(0, .48, .18, 0, .53, 0),
2370
+ c(.82, 0, 1, .18, 1, .44),
2371
+ c(1, .68, .83, .83, .65, .83),
2372
+ c(.49, .83, .39, .72, .39, .6),
2373
+ c(.39, .49, .47, .42, .56, .42),
2374
+ c(.64, .42, .69, .48, .69, .54)
2375
+ ], x, top, width, height);
2376
+ }
2377
+
2378
+ //#endregion
2379
+ //#region src/core/dispatch.ts
2380
+ const V2_ONLY_OPTION_KEYS = [
2381
+ "symmetry",
2382
+ "peakHeight",
2383
+ "sideComplexity",
2384
+ "crest"
2385
+ ];
2386
+ function generateGate(dimensions, options) {
2387
+ if (options !== null && typeof options === "object" && Object.hasOwn(options, "generationVersion") && options.generationVersion === 3) return generateGateV3(dimensions, options);
2388
+ if (options !== null && typeof options === "object" && Object.hasOwn(options, "generationVersion") && options.generationVersion === 2) return generateGateV2(dimensions, options);
2389
+ if (options !== null && typeof options === "object") {
2390
+ for (const key of V2_ONLY_OPTION_KEYS) if (Object.hasOwn(options, key)) throw new TypeError(`${key} requires generationVersion 2`);
2391
+ }
2392
+ return generateGate$1(dimensions, options);
2393
+ }
2394
+
2395
+ //#endregion
2396
+ //#region src/core/svg.ts
2397
+ const HEX_COLOR = /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
2398
+ const COLOR_KEYWORD = /^(?:transparent|currentcolor)$/i;
2399
+ const FUNCTION_COLOR = /^(rgb|rgba|hsl|hsla)\((.*)\)$/i;
2400
+ const DECIMAL_NUMBER = /^[+-]?(?:\d+(?:\.\d+)?|\.\d+)$/;
2401
+ const XML_DELIMITER = /[&<>"']/;
2402
+ const sourceDimensions = (dimensions) => {
2403
+ const { width, height } = dimensions;
2404
+ for (const sourceWidth of [
2405
+ width,
2406
+ width - 499e-7,
2407
+ width + 499e-7
2408
+ ]) for (const sourceHeight of [
2409
+ height,
2410
+ height - 499e-7,
2411
+ height + 499e-7
2412
+ ]) {
2413
+ const ratio = sourceWidth / sourceHeight;
2414
+ if (ratio >= .5 && ratio <= 6) return {
2415
+ width: sourceWidth,
2416
+ height: sourceHeight
2417
+ };
2418
+ }
2419
+ return dimensions;
2420
+ };
2421
+ const requirePlainObject = (value, name) => {
2422
+ if (value === null || typeof value !== "object") throw new TypeError(`${name} must be a plain object`);
2423
+ const prototype = Object.getPrototypeOf(value);
2424
+ if (prototype !== Object.prototype && prototype !== null) throw new TypeError(`${name} must be a plain object`);
2425
+ return value;
2426
+ };
2427
+ const structurallyEqual = (left, right) => {
2428
+ if (left === right) return true;
2429
+ if (Array.isArray(left) || Array.isArray(right)) return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => structurallyEqual(value, right[index]));
2430
+ if (left === null || right === null || typeof left !== "object" || typeof right !== "object") return false;
2431
+ const leftRecord = left;
2432
+ const rightRecord = right;
2433
+ const leftKeys = Reflect.ownKeys(leftRecord);
2434
+ const rightKeys = Reflect.ownKeys(rightRecord);
2435
+ return leftKeys.length === rightKeys.length && leftKeys.every((key) => Object.hasOwn(rightRecord, key) && structurallyEqual(Reflect.get(leftRecord, key), Reflect.get(rightRecord, key)));
2436
+ };
2437
+ const regenerateGeometry = (geometry, sourceDimensions) => {
2438
+ const options = geometry.options;
2439
+ if (geometry.generationVersion === 1) {
2440
+ const publicOptions = {
2441
+ generationVersion: 1,
2442
+ seed: geometry.seed,
2443
+ family: "courtyard",
2444
+ density: options.density,
2445
+ curvature: options.curvature
2446
+ };
2447
+ return generateGate(sourceDimensions, publicOptions);
2448
+ }
2449
+ const publicOptions = {
2450
+ generationVersion: geometry.generationVersion === 3 ? 3 : 2,
2451
+ seed: geometry.seed,
2452
+ family: options.family,
2453
+ density: options.density,
2454
+ curvature: options.curvature,
2455
+ symmetry: options.symmetry,
2456
+ peakHeight: options.peakHeight,
2457
+ sideComplexity: options.sideComplexity,
2458
+ crest: options.crest
2459
+ };
2460
+ return generateGate(sourceDimensions, publicOptions);
2461
+ };
2462
+ const validateGeometryForSerialization = (value) => {
2463
+ const geometry = requirePlainObject(value, "geometry");
2464
+ const dimensions = requirePlainObject(geometry.dimensions, "geometry.dimensions");
2465
+ const canonicalGeometry = regenerateGeometry(geometry, sourceDimensions(dimensions));
2466
+ if (!structurallyEqual(geometry, canonicalGeometry)) throw new TypeError("geometry must exactly match canonical generated geometry");
2467
+ return canonicalGeometry;
2468
+ };
2469
+ const hasXmlBreakingCharacter = (value) => {
2470
+ if (XML_DELIMITER.test(value)) return true;
2471
+ for (let index = 0; index < value.length; index += 1) {
2472
+ const codeUnit = value.charCodeAt(index);
2473
+ if (codeUnit <= 8 || codeUnit === 11 || codeUnit === 12 || codeUnit >= 14 && codeUnit <= 31) return true;
2474
+ if (codeUnit === 127) return true;
2475
+ }
2476
+ return false;
2477
+ };
2478
+ const parseChannel = (token, maximum, percentOnly = false) => {
2479
+ const percent = token.endsWith("%");
2480
+ const numberText = percent ? token.slice(0, -1) : token;
2481
+ if (percentOnly && !percent || !DECIMAL_NUMBER.test(numberText)) return false;
2482
+ const value = Number(numberText);
2483
+ const upperBound = percent ? 100 : maximum;
2484
+ return Number.isFinite(value) && value >= 0 && value <= upperBound;
2485
+ };
2486
+ const isFunctionalColor = (paint) => {
2487
+ const match = FUNCTION_COLOR.exec(paint);
2488
+ if (match === null) return false;
2489
+ const functionName = match[1]?.toLowerCase();
2490
+ const channels = match[2]?.split(",").map((channel) => channel.trim()) ?? [];
2491
+ const hasAlpha = functionName === "rgba" || functionName === "hsla";
2492
+ if (channels.length !== (hasAlpha ? 4 : 3) || channels.some((channel) => channel === "")) return false;
2493
+ if (functionName === "rgb" || functionName === "rgba") {
2494
+ if (!channels.slice(0, 3).every((channel) => parseChannel(channel, 255))) return false;
2495
+ } else {
2496
+ const [hue, saturation, lightness] = channels;
2497
+ if (hue === void 0 || saturation === void 0 || lightness === void 0 || !DECIMAL_NUMBER.test(hue) || !Number.isFinite(Number(hue)) || !parseChannel(saturation, 100, true) || !parseChannel(lightness, 100, true)) return false;
2498
+ }
2499
+ const alpha = channels[3];
2500
+ return alpha === void 0 || parseChannel(alpha, 1);
2501
+ };
2502
+ const normalizePaint = (value, name) => {
2503
+ if (typeof value !== "string") throw new TypeError(`${name} paint must be a string`);
2504
+ const paint = value.trim();
2505
+ if (paint.length === 0 || hasXmlBreakingCharacter(paint) || !HEX_COLOR.test(paint) && !COLOR_KEYWORD.test(paint) && !isFunctionalColor(paint)) throw new TypeError(`${name} paint must be a safe literal CSS color`);
2506
+ return paint;
2507
+ };
2508
+ const escapeXmlAttribute = (value) => value.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("'", "&apos;");
2509
+ const serializeCommand = (command) => {
2510
+ switch (command.kind) {
2511
+ case "M":
2512
+ case "L": return `${command.kind}${command.x} ${command.y}`;
2513
+ case "C": return `C${command.x1} ${command.y1} ${command.x2} ${command.y2} ${command.x} ${command.y}`;
2514
+ case "Q": return `Q${command.x1} ${command.y1} ${command.x} ${command.y}`;
2515
+ case "Z": return "Z";
2516
+ }
2517
+ };
2518
+ const serializePath = (path) => path.map(serializeCommand).join(" ");
2519
+ function renderGateSVG(geometry, paint = {}) {
2520
+ const canonicalGeometry = validateGeometryForSerialization(geometry);
2521
+ const stroke = escapeXmlAttribute(normalizePaint(paint.stroke ?? "currentColor", "stroke"));
2522
+ const surface = escapeXmlAttribute(normalizePaint(paint.surface ?? "transparent", "surface"));
2523
+ const { width, height } = canonicalGeometry.dimensions;
2524
+ const viewBox = canonicalGeometry.viewBox;
2525
+ const surfacePath = `<path d="${serializePath(canonicalGeometry.surfacePath)}" fill="${surface}" fill-rule="evenodd" stroke="none"/>`;
2526
+ if (canonicalGeometry.generationVersion === 3) {
2527
+ const paths = (items) => items.map((path) => `<path d="${serializePath(path)}"/>`).join("");
2528
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="${viewBox.minX} ${viewBox.minY} ${viewBox.width} ${viewBox.height}" data-gate-frame-generation-version="3">${surfacePath}<g stroke="${stroke}" fill="none" stroke-linecap="round" stroke-linejoin="round"><g stroke-width="2">${paths(canonicalGeometry.structuralPaths)}</g><g stroke-width="1.8">${paths(canonicalGeometry.decorativePaths)}</g><g stroke-width="1.2">${paths(canonicalGeometry.finePaths)}</g><g stroke-width="0.8" fill="${stroke}">${paths(canonicalGeometry.solidPaths)}</g></g></svg>`;
2529
+ }
2530
+ const ironPaths = [...canonicalGeometry.structuralPaths.map((path) => `<path d="${serializePath(path)}" fill="none"/>`), ...canonicalGeometry.decorativePaths.map((path) => `<path d="${serializePath(path)}" fill="none"/>`)].join("");
2531
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="${viewBox.minX} ${viewBox.minY} ${viewBox.width} ${viewBox.height}" data-gate-frame-generation-version="${canonicalGeometry.generationVersion}">${surfacePath}<g stroke="${stroke}" stroke-width="${canonicalGeometry.options.strokeWidth}" stroke-linecap="round" stroke-linejoin="round">${ironPaths}</g></svg>`;
2532
+ }
2533
+
2534
+ //#endregion
2535
+ export { isGateFamily as i, generateGate as n, isCompatibleCrest as r, renderGateSVG as t };
2536
+ //# sourceMappingURL=core-DzM1ds_q.js.map