@prosopo/puzzle-assets 0.1.3

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.
Files changed (60) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/_virtual/_rolldown/runtime.js +3 -0
  3. package/dist/background.d.ts +4 -0
  4. package/dist/background.d.ts.map +1 -0
  5. package/dist/background.js +131 -0
  6. package/dist/background.js.map +1 -0
  7. package/dist/browser.d.ts +15 -0
  8. package/dist/browser.d.ts.map +1 -0
  9. package/dist/browser.js +38 -0
  10. package/dist/browser.js.map +1 -0
  11. package/dist/cjs/_virtual/_rolldown/runtime.cjs +23 -0
  12. package/dist/cjs/background.cjs +131 -0
  13. package/dist/cjs/compose.cjs +126 -0
  14. package/dist/cjs/decoys.cjs +105 -0
  15. package/dist/cjs/encode.cjs +51 -0
  16. package/dist/cjs/index.cjs +74 -0
  17. package/dist/cjs/notch.cjs +122 -0
  18. package/dist/cjs/palette.cjs +94 -0
  19. package/dist/cjs/prng.cjs +44 -0
  20. package/dist/compose.d.ts +9 -0
  21. package/dist/compose.d.ts.map +1 -0
  22. package/dist/compose.js +126 -0
  23. package/dist/compose.js.map +1 -0
  24. package/dist/decoys.d.ts +5 -0
  25. package/dist/decoys.d.ts.map +1 -0
  26. package/dist/decoys.js +104 -0
  27. package/dist/decoys.js.map +1 -0
  28. package/dist/encode.d.ts +6 -0
  29. package/dist/encode.d.ts.map +1 -0
  30. package/dist/encode.js +46 -0
  31. package/dist/encode.js.map +1 -0
  32. package/dist/index.d.ts +12 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +59 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/notch.d.ts +7 -0
  37. package/dist/notch.d.ts.map +1 -0
  38. package/dist/notch.js +121 -0
  39. package/dist/notch.js.map +1 -0
  40. package/dist/palette.d.ts +9 -0
  41. package/dist/palette.d.ts.map +1 -0
  42. package/dist/palette.js +93 -0
  43. package/dist/palette.js.map +1 -0
  44. package/dist/prng.d.ts +10 -0
  45. package/dist/prng.d.ts.map +1 -0
  46. package/dist/prng.js +42 -0
  47. package/dist/prng.js.map +1 -0
  48. package/dist/tests/puzzleAssets.unit.test.d.ts +2 -0
  49. package/dist/tests/puzzleAssets.unit.test.d.ts.map +1 -0
  50. package/dist/tests/puzzleAssets.unit.test.js +257 -0
  51. package/dist/tests/puzzleAssets.unit.test.js.map +1 -0
  52. package/dist/types.d.ts +28 -0
  53. package/dist/types.d.ts.map +1 -0
  54. package/dist/types.js +2 -0
  55. package/dist/types.js.map +1 -0
  56. package/package.json +61 -0
  57. package/scripts/preview.ts +197 -0
  58. package/vite.cjs.config.ts +23 -0
  59. package/vite.esm.config.ts +27 -0
  60. package/vite.test.config.ts +32 -0
@@ -0,0 +1,257 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { generateBackground } from "../background.js";
3
+ import { cutNotch } from "../compose.js";
4
+ import { paintDecoyPiece, paintDecoys } from "../decoys.js";
5
+ import { DEFAULT_GEOMETRY, DEFAULT_RENDER_SETTINGS, createBackground, renderPuzzle, } from "../index.js";
6
+ import { coverageFromDistance, createNotchShape } from "../notch.js";
7
+ import { createPrng, createSeed } from "../prng.js";
8
+ const SEED_A = Buffer.from("0123456789abcdef", "utf-8");
9
+ const SEED_B = Buffer.from("fedcba9876543210", "utf-8");
10
+ const meanLuma = (image) => {
11
+ let total = 0;
12
+ const pixels = image.width * image.height;
13
+ for (let i = 0; i < pixels; i++) {
14
+ const o = i * 4;
15
+ total +=
16
+ 0.299 * (image.data[o] ?? 0) +
17
+ 0.587 * (image.data[o + 1] ?? 0) +
18
+ 0.114 * (image.data[o + 2] ?? 0);
19
+ }
20
+ return total / pixels;
21
+ };
22
+ describe("prng", () => {
23
+ it("is deterministic for a given seed", () => {
24
+ const a = createPrng(SEED_A);
25
+ const b = createPrng(SEED_A);
26
+ const drawA = [a.next(), a.next(), a.next()];
27
+ const drawB = [b.next(), b.next(), b.next()];
28
+ expect(drawA).toEqual(drawB);
29
+ });
30
+ it("diverges for different seeds", () => {
31
+ const a = createPrng(SEED_A);
32
+ const b = createPrng(SEED_B);
33
+ expect(a.next()).not.toBe(b.next());
34
+ });
35
+ it("stays within bounds", () => {
36
+ const prng = createPrng(SEED_A);
37
+ for (let i = 0; i < 500; i++) {
38
+ const v = prng.next();
39
+ expect(v).toBeGreaterThanOrEqual(0);
40
+ expect(v).toBeLessThan(1);
41
+ const n = prng.int(3, 7);
42
+ expect(n).toBeGreaterThanOrEqual(3);
43
+ expect(n).toBeLessThanOrEqual(7);
44
+ }
45
+ });
46
+ it("rejects a seed shorter than 128 bits", () => {
47
+ expect(() => createPrng(Buffer.alloc(8))).toThrow(/seed must be at least/);
48
+ });
49
+ it("issues 128-bit seeds", () => {
50
+ expect(createSeed()).toHaveLength(16);
51
+ expect(createSeed().equals(createSeed())).toBe(false);
52
+ });
53
+ });
54
+ describe("generateBackground", () => {
55
+ it("fills every pixel opaque", () => {
56
+ const image = generateBackground(createPrng(SEED_A), 60, 40);
57
+ expect(image.data).toHaveLength(60 * 40 * 4);
58
+ for (let i = 0; i < 60 * 40; i++) {
59
+ expect(image.data[i * 4 + 3]).toBe(255);
60
+ }
61
+ });
62
+ it("is reproducible from its seed", () => {
63
+ const a = generateBackground(createPrng(SEED_A), 60, 40);
64
+ const b = generateBackground(createPrng(SEED_A), 60, 40);
65
+ expect(a.data.equals(b.data)).toBe(true);
66
+ });
67
+ it("produces a different picture for a different seed", () => {
68
+ const a = generateBackground(createPrng(SEED_A), 60, 40);
69
+ const b = generateBackground(createPrng(SEED_B), 60, 40);
70
+ expect(a.data.equals(b.data)).toBe(false);
71
+ });
72
+ it("is not a flat wash", () => {
73
+ const image = generateBackground(createPrng(SEED_A), 300, 200);
74
+ let min = 255;
75
+ let max = 0;
76
+ for (let i = 0; i < image.width * image.height; i++) {
77
+ const o = i * 4;
78
+ const luma = 0.299 * (image.data[o] ?? 0) +
79
+ 0.587 * (image.data[o + 1] ?? 0) +
80
+ 0.114 * (image.data[o + 2] ?? 0);
81
+ min = Math.min(min, luma);
82
+ max = Math.max(max, luma);
83
+ }
84
+ expect(max - min).toBeGreaterThan(25);
85
+ });
86
+ });
87
+ describe("cutNotch", () => {
88
+ const geometry = DEFAULT_GEOMETRY;
89
+ const placement = { targetX: 200, targetY: 100 };
90
+ it("darkens the hole and leaves the rest of the frame alone", () => {
91
+ const prng = createPrng(SEED_A);
92
+ const original = generateBackground(createPrng(SEED_A), geometry.width, geometry.height);
93
+ const working = generateBackground(createPrng(SEED_A), geometry.width, geometry.height);
94
+ const shape = createNotchShape(prng, geometry.notchSize);
95
+ const { background, piece } = cutNotch(prng, working, shape, geometry.notchSize, placement, DEFAULT_RENDER_SETTINGS.holeDarken);
96
+ const centre = (placement.targetY * geometry.width + placement.targetX) * 4;
97
+ expect(background.data[centre]).toBeLessThan(original.data[centre] ?? 255);
98
+ const far = (10 * geometry.width + 10) * 4;
99
+ expect(background.data[far]).toBe(original.data[far]);
100
+ expect(piece.width).toBe(geometry.notchSize);
101
+ expect(piece.height).toBe(geometry.notchSize);
102
+ });
103
+ it("makes the piece opaque inside the shape and transparent at the corners", () => {
104
+ const prng = createPrng(SEED_A);
105
+ const background = createBackground(geometry);
106
+ const shape = createNotchShape(prng, geometry.notchSize);
107
+ const { piece } = cutNotch(prng, background, shape, geometry.notchSize, placement, DEFAULT_RENDER_SETTINGS.holeDarken);
108
+ const half = Math.floor(geometry.notchSize / 2);
109
+ const centreAlpha = piece.data[(half * geometry.notchSize + half) * 4 + 3] ?? 0;
110
+ expect(centreAlpha).toBe(255);
111
+ expect(piece.data[3] ?? 255).toBeLessThan(128);
112
+ });
113
+ it("clips the piece to transparent where the placement overhangs the frame", () => {
114
+ const prng = createPrng(SEED_A);
115
+ const bg = createBackground(geometry);
116
+ const shape = createNotchShape(prng, geometry.notchSize);
117
+ const overhangPlacement = {
118
+ targetX: geometry.width,
119
+ targetY: 0,
120
+ };
121
+ const { piece } = cutNotch(prng, bg, shape, geometry.notchSize, overhangPlacement, DEFAULT_RENDER_SETTINGS.holeDarken);
122
+ const half = geometry.notchSize / 2;
123
+ const left = Math.round(overhangPlacement.targetX - half);
124
+ const top = Math.round(overhangPlacement.targetY - half);
125
+ let outOfFrameOpaque = 0;
126
+ for (let ly = 0; ly < geometry.notchSize; ly++) {
127
+ for (let lx = 0; lx < geometry.notchSize; lx++) {
128
+ const bx = left + lx;
129
+ const by = top + ly;
130
+ const inFrame = bx >= 0 && by >= 0 && bx < geometry.width && by < geometry.height;
131
+ if (inFrame)
132
+ continue;
133
+ const alpha = piece.data[(ly * geometry.notchSize + lx) * 4 + 3] ?? 0;
134
+ if (alpha !== 0)
135
+ outOfFrameOpaque++;
136
+ }
137
+ }
138
+ expect(outOfFrameOpaque).toBe(0);
139
+ });
140
+ it("does not reproduce the background pixel-exactly in the piece", () => {
141
+ const prng = createPrng(SEED_A);
142
+ const reference = generateBackground(createPrng(SEED_A), geometry.width, geometry.height);
143
+ const working = generateBackground(createPrng(SEED_A), geometry.width, geometry.height);
144
+ const shape = createNotchShape(prng, geometry.notchSize);
145
+ const { piece } = cutNotch(prng, working, shape, geometry.notchSize, placement, DEFAULT_RENDER_SETTINGS.holeDarken);
146
+ const half = geometry.notchSize / 2;
147
+ const left = Math.round(placement.targetX - half);
148
+ const top = Math.round(placement.targetY - half);
149
+ let identical = 0;
150
+ let compared = 0;
151
+ for (let ly = 0; ly < geometry.notchSize; ly++) {
152
+ for (let lx = 0; lx < geometry.notchSize; lx++) {
153
+ const pi = (ly * geometry.notchSize + lx) * 4;
154
+ if ((piece.data[pi + 3] ?? 0) < 255)
155
+ continue;
156
+ const bi = ((top + ly) * geometry.width + (left + lx)) * 4;
157
+ compared++;
158
+ if (piece.data[pi] === reference.data[bi])
159
+ identical++;
160
+ }
161
+ }
162
+ expect(compared).toBeGreaterThan(100);
163
+ expect(identical / compared).toBeLessThan(0.5);
164
+ });
165
+ });
166
+ describe("renderPuzzle", () => {
167
+ it("encodes both halves as webp", async () => {
168
+ const rendered = await renderPuzzle(createBackground(), {
169
+ targetX: 200,
170
+ targetY: 100,
171
+ });
172
+ expect(rendered.background.subarray(0, 4).toString("ascii")).toBe("RIFF");
173
+ expect(rendered.background.subarray(8, 12).toString("ascii")).toBe("WEBP");
174
+ expect(rendered.piece.subarray(8, 12).toString("ascii")).toBe("WEBP");
175
+ expect(rendered.pieceSize).toBe(DEFAULT_GEOMETRY.notchSize);
176
+ });
177
+ it("honors a `pieceSize` override independently of `notchSize`", async () => {
178
+ const overrideSize = 180;
179
+ const rendered = await renderPuzzle(createBackground(), { targetX: 150, targetY: 100 }, { ...DEFAULT_GEOMETRY, pieceSize: overrideSize });
180
+ expect(rendered.pieceSize).toBe(overrideSize);
181
+ });
182
+ it("stays small enough to inline as a data uri", async () => {
183
+ const rendered = await renderPuzzle(createBackground(), {
184
+ targetX: 200,
185
+ targetY: 100,
186
+ });
187
+ const base64Bytes = Math.ceil(rendered.background.length / 3) * 4 +
188
+ Math.ceil(rendered.piece.length / 3) * 4;
189
+ expect(base64Bytes).toBeLessThan(120_000);
190
+ });
191
+ });
192
+ describe("coverageFromDistance", () => {
193
+ it("is 1 well inside, 0 well outside and partial on the boundary", () => {
194
+ expect(coverageFromDistance(-5)).toBe(1);
195
+ expect(coverageFromDistance(5)).toBe(0);
196
+ const edge = coverageFromDistance(0);
197
+ expect(edge).toBeGreaterThan(0);
198
+ expect(edge).toBeLessThan(1);
199
+ });
200
+ });
201
+ describe("background brightness", () => {
202
+ it("keeps a usable mid-range so the piece stays visible", () => {
203
+ for (const seed of [SEED_A, SEED_B]) {
204
+ const luma = meanLuma(generateBackground(createPrng(seed), 300, 200));
205
+ expect(luma).toBeGreaterThan(60);
206
+ expect(luma).toBeLessThan(215);
207
+ }
208
+ });
209
+ });
210
+ describe("paintDecoys", () => {
211
+ const notch = { targetX: 220, targetY: 140 };
212
+ const { decoyEdgeDarkness, decoyBodyBrightness, decoyHoleDarken } = DEFAULT_RENDER_SETTINGS;
213
+ it("is a no-op for count <= 0", () => {
214
+ const bg = generateBackground(createPrng(SEED_A), 300, 200);
215
+ const before = Buffer.from(bg.data);
216
+ paintDecoys(bg, createPrng(SEED_A), 0, DEFAULT_GEOMETRY.notchSize, notch, decoyEdgeDarkness, decoyBodyBrightness, decoyHoleDarken);
217
+ expect(bg.data.equals(before)).toBe(true);
218
+ });
219
+ it("mutates the background when count > 0", () => {
220
+ const bg = generateBackground(createPrng(SEED_A), 300, 200);
221
+ const before = Buffer.from(bg.data);
222
+ paintDecoys(bg, createPrng(SEED_B), 3, DEFAULT_GEOMETRY.notchSize, notch, decoyEdgeDarkness, decoyBodyBrightness, decoyHoleDarken);
223
+ expect(bg.data.equals(before)).toBe(false);
224
+ });
225
+ it("is deterministic for a given prng seed", () => {
226
+ const bg1 = generateBackground(createPrng(SEED_A), 300, 200);
227
+ const bg2 = generateBackground(createPrng(SEED_A), 300, 200);
228
+ paintDecoys(bg1, createPrng(SEED_B), 3, DEFAULT_GEOMETRY.notchSize, notch, decoyEdgeDarkness, decoyBodyBrightness, decoyHoleDarken);
229
+ paintDecoys(bg2, createPrng(SEED_B), 3, DEFAULT_GEOMETRY.notchSize, notch, decoyEdgeDarkness, decoyBodyBrightness, decoyHoleDarken);
230
+ expect(bg1.data.equals(bg2.data)).toBe(true);
231
+ });
232
+ it("blackens the decoy interior when holeDarken is zero", () => {
233
+ const bg = generateBackground(createPrng(SEED_A), 300, 200);
234
+ const cx = 150;
235
+ const cy = 100;
236
+ paintDecoyPiece(bg, createPrng(SEED_B), DEFAULT_GEOMETRY.notchSize, cx, cy, 0, 0, 0);
237
+ const i = (cy * bg.width + cx) * 4;
238
+ expect(bg.data[i]).toBe(0);
239
+ expect(bg.data[i + 1]).toBe(0);
240
+ expect(bg.data[i + 2]).toBe(0);
241
+ });
242
+ });
243
+ describe("renderPuzzle with decoys", () => {
244
+ it("still returns a matching piece when decoys are present", async () => {
245
+ const bg = createBackground(DEFAULT_GEOMETRY);
246
+ const rendered = await renderPuzzle(bg, { targetX: 150, targetY: 100 }, DEFAULT_GEOMETRY, { ...DEFAULT_RENDER_SETTINGS, decoyCount: 3 });
247
+ expect(rendered.background.length).toBeGreaterThan(0);
248
+ expect(rendered.piece.length).toBeGreaterThan(0);
249
+ expect(rendered.pieceSize).toBe(DEFAULT_GEOMETRY.notchSize);
250
+ });
251
+ it("honors decoyCount 0 (no decoys) end-to-end", async () => {
252
+ const target = { targetX: 150, targetY: 100 };
253
+ const rendered = await renderPuzzle(createBackground(DEFAULT_GEOMETRY), target, DEFAULT_GEOMETRY, { ...DEFAULT_RENDER_SETTINGS, decoyCount: 0 });
254
+ expect(rendered.background.length).toBeGreaterThan(0);
255
+ });
256
+ });
257
+ //# sourceMappingURL=puzzleAssets.unit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"puzzleAssets.unit.test.js","sourceRoot":"","sources":["../../src/tests/puzzleAssets.unit.test.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EACN,gBAAgB,EAChB,uBAAuB,EACvB,gBAAgB,EAChB,YAAY,GACZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAGpD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC;AACxD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC;AAExD,MAAM,QAAQ,GAAG,CAAC,KAAgB,EAAU,EAAE;IAC7C,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC;IAC1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACjC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChB,KAAK;YACJ,KAAK,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;gBAC5B,KAAK,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;gBAChC,KAAK,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,KAAK,GAAG,MAAM,CAAC;AACvB,CAAC,CAAC;AAEF,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE;IACrB,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAC7B,MAAM,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAC7B,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC7C,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAC7B,MAAM,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAC7B,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC9B,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9B,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YACtB,MAAM,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;YACpC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC1B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACzB,MAAM,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;YACpC,MAAM,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;QAClC,CAAC;IACF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC/B,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QAGtC,MAAM,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QACnC,MAAM,KAAK,GAAG,kBAAkB,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QAC7D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;QAC7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;YAClC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzC,CAAC;IACF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACxC,MAAM,CAAC,GAAG,kBAAkB,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QACzD,MAAM,CAAC,GAAG,kBAAkB,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QACzD,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC5D,MAAM,CAAC,GAAG,kBAAkB,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QACzD,MAAM,CAAC,GAAG,kBAAkB,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QACzD,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAG7B,MAAM,KAAK,GAAG,kBAAkB,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAC/D,IAAI,GAAG,GAAG,GAAG,CAAC;QACd,IAAI,GAAG,GAAG,CAAC,CAAC;QACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACrD,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAChB,MAAM,IAAI,GACT,KAAK,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;gBAC5B,KAAK,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;gBAChC,KAAK,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YAClC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAC1B,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC3B,CAAC;QACD,MAAM,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;IACzB,MAAM,QAAQ,GAAG,gBAAgB,CAAC;IAClC,MAAM,SAAS,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;IAEjD,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QAClE,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,QAAQ,GAAG,kBAAkB,CAClC,UAAU,CAAC,MAAM,CAAC,EAClB,QAAQ,CAAC,KAAK,EACd,QAAQ,CAAC,MAAM,CACf,CAAC;QACF,MAAM,OAAO,GAAG,kBAAkB,CACjC,UAAU,CAAC,MAAM,CAAC,EAClB,QAAQ,CAAC,KAAK,EACd,QAAQ,CAAC,MAAM,CACf,CAAC;QACF,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACzD,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,QAAQ,CACrC,IAAI,EACJ,OAAO,EACP,KAAK,EACL,QAAQ,CAAC,SAAS,EAClB,SAAS,EACT,uBAAuB,CAAC,UAAU,CAClC,CAAC;QAGF,MAAM,MAAM,GAAG,CAAC,SAAS,CAAC,OAAO,GAAG,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5E,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC;QAG3E,MAAM,GAAG,GAAG,CAAC,EAAE,GAAG,QAAQ,CAAC,KAAK,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QAC3C,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAEtD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wEAAwE,EAAE,GAAG,EAAE;QACjF,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,UAAU,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAC9C,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACzD,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,CACzB,IAAI,EACJ,UAAU,EACV,KAAK,EACL,QAAQ,CAAC,SAAS,EAClB,SAAS,EACT,uBAAuB,CAAC,UAAU,CAClC,CAAC;QAEF,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;QAChD,MAAM,WAAW,GAChB,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;QAC7D,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAI9B,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wEAAwE,EAAE,GAAG,EAAE;QAKjF,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,EAAE,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACzD,MAAM,iBAAiB,GAAG;YACzB,OAAO,EAAE,QAAQ,CAAC,KAAK;YACvB,OAAO,EAAE,CAAC;SACV,CAAC;QACF,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,CACzB,IAAI,EACJ,EAAE,EACF,KAAK,EACL,QAAQ,CAAC,SAAS,EAClB,iBAAiB,EACjB,uBAAuB,CAAC,UAAU,CAClC,CAAC;QAEF,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,GAAG,CAAC,CAAC;QACpC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;QAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;QACzD,IAAI,gBAAgB,GAAG,CAAC,CAAC;QACzB,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,QAAQ,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,CAAC;YAChD,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,QAAQ,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,CAAC;gBAChD,MAAM,EAAE,GAAG,IAAI,GAAG,EAAE,CAAC;gBACrB,MAAM,EAAE,GAAG,GAAG,GAAG,EAAE,CAAC;gBACpB,MAAM,OAAO,GACZ,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,QAAQ,CAAC,KAAK,IAAI,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC;gBACnE,IAAI,OAAO;oBAAE,SAAS;gBACtB,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,QAAQ,CAAC,SAAS,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;gBACtE,IAAI,KAAK,KAAK,CAAC;oBAAE,gBAAgB,EAAE,CAAC;YACrC,CAAC;QACF,CAAC;QACD,MAAM,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QAGvE,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,SAAS,GAAG,kBAAkB,CACnC,UAAU,CAAC,MAAM,CAAC,EAClB,QAAQ,CAAC,KAAK,EACd,QAAQ,CAAC,MAAM,CACf,CAAC;QACF,MAAM,OAAO,GAAG,kBAAkB,CACjC,UAAU,CAAC,MAAM,CAAC,EAClB,QAAQ,CAAC,KAAK,EACd,QAAQ,CAAC,MAAM,CACf,CAAC;QACF,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACzD,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,CACzB,IAAI,EACJ,OAAO,EACP,KAAK,EACL,QAAQ,CAAC,SAAS,EAClB,SAAS,EACT,uBAAuB,CAAC,UAAU,CAClC,CAAC;QAEF,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,GAAG,CAAC,CAAC;QACpC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;QACjD,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,QAAQ,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,CAAC;YAChD,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,QAAQ,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,CAAC;gBAChD,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,QAAQ,CAAC,SAAS,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;gBAC9C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG;oBAAE,SAAS;gBAC9C,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,QAAQ,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;gBAC3D,QAAQ,EAAE,CAAC;gBACX,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;oBAAE,SAAS,EAAE,CAAC;YACxD,CAAC;QACF,CAAC;QACD,MAAM,CAAC,QAAQ,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,CAAC,SAAS,GAAG,QAAQ,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;QAC5C,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,gBAAgB,EAAE,EAAE;YACvD,OAAO,EAAE,GAAG;YACZ,OAAO,EAAE,GAAG;SACZ,CAAC,CAAC;QAEH,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1E,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC3E,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACtE,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QAC3E,MAAM,YAAY,GAAG,GAAG,CAAC;QACzB,MAAM,QAAQ,GAAG,MAAM,YAAY,CAClC,gBAAgB,EAAE,EAClB,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,EAC9B,EAAE,GAAG,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,CAChD,CAAC;QACF,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,gBAAgB,EAAE,EAAE;YACvD,OAAO,EAAE,GAAG;YACZ,OAAO,EAAE,GAAG;SACZ,CAAC,CAAC;QACH,MAAM,WAAW,GAChB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC;YAC7C,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,CAAC,WAAW,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACvE,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACzC,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC9D,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,GAAG,QAAQ,CAAC,kBAAkB,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;YACtE,MAAM,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;YACjC,MAAM,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;IACF,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC5B,MAAM,KAAK,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;IAC7C,MAAM,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,eAAe,EAAE,GAChE,uBAAuB,CAAC;IAEzB,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACpC,MAAM,EAAE,GAAG,kBAAkB,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QACpC,WAAW,CACV,EAAE,EACF,UAAU,CAAC,MAAM,CAAC,EAClB,CAAC,EACD,gBAAgB,CAAC,SAAS,EAC1B,KAAK,EACL,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,CACf,CAAC;QACF,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAChD,MAAM,EAAE,GAAG,kBAAkB,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QACpC,WAAW,CACV,EAAE,EACF,UAAU,CAAC,MAAM,CAAC,EAClB,CAAC,EACD,gBAAgB,CAAC,SAAS,EAC1B,KAAK,EACL,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,CACf,CAAC;QACF,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QACjD,MAAM,GAAG,GAAG,kBAAkB,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAC7D,MAAM,GAAG,GAAG,kBAAkB,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAC7D,WAAW,CACV,GAAG,EACH,UAAU,CAAC,MAAM,CAAC,EAClB,CAAC,EACD,gBAAgB,CAAC,SAAS,EAC1B,KAAK,EACL,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,CACf,CAAC;QACF,WAAW,CACV,GAAG,EACH,UAAU,CAAC,MAAM,CAAC,EAClB,CAAC,EACD,gBAAgB,CAAC,SAAS,EAC1B,KAAK,EACL,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,CACf,CAAC;QACF,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAOH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC9D,MAAM,EAAE,GAAG,kBAAkB,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAC5D,MAAM,EAAE,GAAG,GAAG,CAAC;QACf,MAAM,EAAE,GAAG,GAAG,CAAC;QAIf,eAAe,CACd,EAAE,EACF,UAAU,CAAC,MAAM,CAAC,EAClB,gBAAgB,CAAC,SAAS,EAC1B,EAAE,EACF,EAAE,EACF,CAAC,EACD,CAAC,EACD,CAAC,CACD,CAAC;QACF,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3B,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACzC,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACvE,MAAM,EAAE,GAAG,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;QAC9C,MAAM,QAAQ,GAAG,MAAM,YAAY,CAClC,EAAE,EACF,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,EAC9B,gBAAgB,EAChB,EAAE,GAAG,uBAAuB,EAAE,UAAU,EAAE,CAAC,EAAE,CAC7C,CAAC;QACF,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACtD,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;QAC9C,MAAM,QAAQ,GAAG,MAAM,YAAY,CAClC,gBAAgB,CAAC,gBAAgB,CAAC,EAClC,MAAM,EACN,gBAAgB,EAChB,EAAE,GAAG,uBAAuB,EAAE,UAAU,EAAE,CAAC,EAAE,CAC7C,CAAC;QACF,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,28 @@
1
+ export interface RgbaImage {
2
+ data: Buffer;
3
+ width: number;
4
+ height: number;
5
+ }
6
+ export interface NotchPlacement {
7
+ targetX: number;
8
+ targetY: number;
9
+ }
10
+ export interface RenderedPuzzle {
11
+ background: Buffer;
12
+ piece: Buffer;
13
+ pieceSize: number;
14
+ }
15
+ export interface PuzzleGeometry {
16
+ width: number;
17
+ height: number;
18
+ notchSize: number;
19
+ pieceSize?: number;
20
+ }
21
+ export interface PuzzleRenderSettings {
22
+ decoyCount: number;
23
+ decoyEdgeDarkness: number;
24
+ decoyBodyBrightness: number;
25
+ holeDarken: number;
26
+ decoyHoleDarken: number;
27
+ }
28
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CACf;AAGD,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAE9B,UAAU,EAAE,MAAM,CAAC;IAEnB,KAAK,EAAE,MAAM,CAAC;IAEd,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IAMf,SAAS,EAAE,MAAM,CAAC;IAOlB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAOD,MAAM,WAAW,oBAAoB;IAOpC,UAAU,EAAE,MAAM,CAAC;IAMnB,iBAAiB,EAAE,MAAM,CAAC;IAM1B,mBAAmB,EAAE,MAAM,CAAC;IAM5B,UAAU,EAAE,MAAM,CAAC;IAOnB,eAAe,EAAE,MAAM,CAAC;CACxB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@prosopo/puzzle-assets",
3
+ "version": "0.1.3",
4
+ "description": "Procedural imagery for the slider puzzle captcha",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "engines": {
9
+ "node": "^24",
10
+ "npm": "^11"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "require": "./dist/cjs/index.cjs"
17
+ },
18
+ "./browser": {
19
+ "types": "./dist/browser.d.ts",
20
+ "import": "./dist/browser.js",
21
+ "require": "./dist/cjs/browser.cjs"
22
+ }
23
+ },
24
+ "scripts": {
25
+ "clean": "del-cli --verbose dist tsconfig.tsbuildinfo",
26
+ "build": "npm run build:cross-env -- --mode ${NODE_ENV:-development}",
27
+ "build:cross-env": "vite build --config vite.esm.config.ts",
28
+ "build:tsc": "tsc --build --verbose",
29
+ "build:cjs": "NODE_ENV=${NODE_ENV:-development}; vite build --config vite.cjs.config.ts --mode $NODE_ENV",
30
+ "preview": "tsx scripts/preview.ts",
31
+ "test": "NODE_ENV=${NODE_ENV:-test}; npx vitest run --config ./vite.test.config.ts",
32
+ "typecheck": "tsc --project tsconfig.types.json"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/prosopo/captcha.git",
37
+ "directory": "packages/puzzle-assets"
38
+ },
39
+ "author": "Prosopo Limited",
40
+ "license": "Apache-2.0",
41
+ "bugs": {
42
+ "url": "https://github.com/prosopo/captcha/issues"
43
+ },
44
+ "homepage": "https://github.com/prosopo/captcha#readme",
45
+ "dependencies": {
46
+ "sharp": "0.35.4"
47
+ },
48
+ "devDependencies": {
49
+ "@prosopo/config": "3.3.13",
50
+ "@types/node": "22.10.2",
51
+ "@vitest/coverage-v8": "4.1.11",
52
+ "del-cli": "6.0.0",
53
+ "dotenv": "16.4.5",
54
+ "tslib": "2.7.0",
55
+ "tsx": "4.20.3",
56
+ "typescript": "5.6.2",
57
+ "vite": "8.1.5",
58
+ "vitest": "4.1.11"
59
+ },
60
+ "sideEffects": false
61
+ }
@@ -0,0 +1,197 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ /**
16
+ * Eyeball the generator.
17
+ *
18
+ * Writes N puzzles as PNG pairs plus a contact-sheet HTML page that lays the
19
+ * piece over the background at its origin, so the output can be judged the way
20
+ * a user would see it.
21
+ *
22
+ * npm -w @prosopo/puzzle-assets run preview -- --count 24 --out ./preview
23
+ */
24
+
25
+ import { mkdirSync, writeFileSync } from "node:fs";
26
+ import { join, resolve } from "node:path";
27
+ import sharp from "sharp";
28
+ import { cutNotch } from "../src/compose.js";
29
+ import {
30
+ DEFAULT_GEOMETRY,
31
+ createBackground,
32
+ encodePng,
33
+ renderPuzzle,
34
+ } from "../src/index.js";
35
+ import { createNotchShape } from "../src/notch.js";
36
+ import { createPrng, createSeed } from "../src/prng.js";
37
+
38
+ interface Args {
39
+ count: number;
40
+ out: string;
41
+ }
42
+
43
+ const parseArgs = (argv: readonly string[]): Args => {
44
+ const args: Args = { count: 24, out: "./preview" };
45
+ for (let i = 0; i < argv.length; i++) {
46
+ const flag = argv[i];
47
+ const value = argv[i + 1];
48
+ if (flag === "--count" && value) {
49
+ args.count = Number.parseInt(value, 10);
50
+ i++;
51
+ } else if (flag === "--out" && value) {
52
+ args.out = value;
53
+ i++;
54
+ }
55
+ }
56
+ return args;
57
+ };
58
+
59
+ // Mirrors the provider's placement ranges (puzzleTasks.ts).
60
+ const placementFor = (): {
61
+ targetX: number;
62
+ targetY: number;
63
+ originX: number;
64
+ originY: number;
65
+ } => {
66
+ const prng = createPrng(createSeed());
67
+ return {
68
+ targetX: prng.int(150, 280),
69
+ targetY: prng.int(30, 170),
70
+ originX: prng.int(20, 130),
71
+ originY: prng.int(30, 170),
72
+ };
73
+ };
74
+
75
+ const main = async (): Promise<void> => {
76
+ const { count, out } = parseArgs(process.argv.slice(2));
77
+ const outDir = resolve(out);
78
+ mkdirSync(outDir, { recursive: true });
79
+
80
+ const cells: string[] = [];
81
+ const sheetTiles: sharp.OverlayOptions[] = [];
82
+ const sheetCols = 3;
83
+ const sheetGap = 10;
84
+ const started = process.hrtime.bigint();
85
+
86
+ for (let i = 0; i < count; i++) {
87
+ const placement = placementFor();
88
+
89
+ // Same pipeline as renderPuzzle, but kept in PNG so the preview shows
90
+ // the raw output rather than WebP artefacts.
91
+ const background = createBackground(DEFAULT_GEOMETRY);
92
+ const prng = createPrng(createSeed());
93
+ const shape = createNotchShape(prng, DEFAULT_GEOMETRY.notchSize);
94
+ const { background: cut, piece } = cutNotch(
95
+ prng,
96
+ background,
97
+ shape,
98
+ DEFAULT_GEOMETRY.notchSize,
99
+ placement,
100
+ );
101
+
102
+ const [bgPng, piecePng] = await Promise.all([
103
+ encodePng(cut),
104
+ encodePng(piece),
105
+ ]);
106
+ writeFileSync(join(outDir, `puzzle-${i}-bg.png`), bgPng);
107
+ writeFileSync(join(outDir, `puzzle-${i}-piece.png`), piecePng);
108
+
109
+ const half = DEFAULT_GEOMETRY.notchSize / 2;
110
+
111
+ // Contact-sheet tile: the piece composited at its origin, so a whole
112
+ // batch can be judged in one image rather than file by file.
113
+ sheetTiles.push({
114
+ input: await sharp(bgPng)
115
+ .composite([
116
+ {
117
+ input: piecePng,
118
+ left: Math.round(placement.originX - half),
119
+ top: Math.round(placement.originY - half),
120
+ },
121
+ ])
122
+ .png()
123
+ .toBuffer(),
124
+ left: (i % sheetCols) * (DEFAULT_GEOMETRY.width + sheetGap),
125
+ top: Math.floor(i / sheetCols) * (DEFAULT_GEOMETRY.height + sheetGap),
126
+ });
127
+ cells.push(`
128
+ <figure>
129
+ <div class="stage">
130
+ <img class="bg" src="puzzle-${i}-bg.png" alt="">
131
+ <img class="piece" style="left:${placement.originX - half}px;top:${placement.originY - half}px" src="puzzle-${i}-piece.png" alt="">
132
+ </div>
133
+ <figcaption>#${i} &middot; target ${placement.targetX},${placement.targetY}</figcaption>
134
+ </figure>`);
135
+ }
136
+
137
+ const sheetRows = Math.ceil(count / sheetCols);
138
+ writeFileSync(
139
+ join(outDir, "sheet.png"),
140
+ await sharp({
141
+ create: {
142
+ width: sheetCols * DEFAULT_GEOMETRY.width + (sheetCols - 1) * sheetGap,
143
+ height:
144
+ sheetRows * DEFAULT_GEOMETRY.height + (sheetRows - 1) * sheetGap,
145
+ channels: 4,
146
+ background: { r: 20, g: 22, b: 28, alpha: 1 },
147
+ },
148
+ })
149
+ .composite(sheetTiles)
150
+ .png()
151
+ .toBuffer(),
152
+ );
153
+
154
+ // Timing for the real (WebP) path, which is what the provider will run.
155
+ const benchStart = process.hrtime.bigint();
156
+ const benchRuns = 20;
157
+ for (let i = 0; i < benchRuns; i++) {
158
+ await renderPuzzle(createBackground(), { targetX: 200, targetY: 100 });
159
+ }
160
+ const perRenderMs =
161
+ Number(process.hrtime.bigint() - benchStart) / 1e6 / benchRuns;
162
+
163
+ writeFileSync(
164
+ join(outDir, "index.html"),
165
+ `<!doctype html>
166
+ <meta charset="utf-8">
167
+ <title>puzzle-assets preview</title>
168
+ <style>
169
+ body { background:#14161c; color:#e7e9ee; font:14px/1.5 ui-sans-serif,system-ui,sans-serif; margin:24px; }
170
+ h1 { font-size:16px; font-weight:600; margin:0 0 4px; }
171
+ p { color:#9aa1ad; margin:0 0 24px; }
172
+ .grid { display:flex; flex-wrap:wrap; gap:20px; }
173
+ figure { margin:0; }
174
+ .stage { position:relative; width:${DEFAULT_GEOMETRY.width}px; height:${DEFAULT_GEOMETRY.height}px; border-radius:8px; overflow:hidden; }
175
+ .bg { display:block; width:100%; height:100%; }
176
+ .piece { position:absolute; width:${DEFAULT_GEOMETRY.notchSize}px; height:${DEFAULT_GEOMETRY.notchSize}px; }
177
+ figcaption { color:#9aa1ad; font-size:12px; margin-top:6px; }
178
+ </style>
179
+ <h1>puzzle-assets preview &mdash; ${count} samples</h1>
180
+ <p>Piece shown at its origin. WebP render: ${perRenderMs.toFixed(1)} ms per puzzle.</p>
181
+ <div class="grid">${cells.join("")}
182
+ </div>
183
+ `,
184
+ );
185
+
186
+ const totalMs = Number(process.hrtime.bigint() - started) / 1e6;
187
+ console.log(`wrote ${count} puzzles to ${outDir}`);
188
+ console.log(`open ${join(outDir, "index.html")}`);
189
+ console.log(
190
+ `render cost: ${perRenderMs.toFixed(1)} ms/puzzle (webp), preview total ${totalMs.toFixed(0)} ms`,
191
+ );
192
+ };
193
+
194
+ main().catch((error) => {
195
+ console.error(error);
196
+ process.exit(1);
197
+ });
@@ -0,0 +1,23 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import path from "node:path";
16
+ import { ViteCommonJSConfig } from "@prosopo/config";
17
+
18
+ export default function () {
19
+ return ViteCommonJSConfig(
20
+ path.basename("."),
21
+ path.resolve("./tsconfig.json"),
22
+ );
23
+ }
@@ -0,0 +1,27 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import path from "node:path";
16
+ import { ViteEsmConfig } from "@prosopo/config";
17
+
18
+ // Both `.` and `./browser` are advertised in package.json exports. Vite
19
+ // only emits `dist/browser.js` if it's declared as an entry — otherwise
20
+ // downstream browser consumers can't resolve @prosopo/puzzle-assets/browser
21
+ // after the ESM build runs.
22
+ export default function () {
23
+ return ViteEsmConfig(path.basename("."), path.resolve("./tsconfig.json"), [
24
+ "src/index.ts",
25
+ "src/browser.ts",
26
+ ]);
27
+ }
@@ -0,0 +1,32 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
4
+ //
5
+ // Licensed under the Apache License, Version 2.0 (the "License");
6
+ // you may not use this file except in compliance with the License.
7
+ // You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing, software
12
+ // distributed under the License is distributed on an "AS IS" BASIS,
13
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ // See the License for the specific language governing permissions and
15
+ // limitations under the License.
16
+ import { ViteTestConfig } from "@prosopo/config";
17
+ import dotenv from "dotenv";
18
+ process.env.NODE_ENV = "test";
19
+ // if .env.test exists at this level, use it, otherwise use the one at the root
20
+ const envFile = `.env.${process.env.NODE_ENV || "development"}`;
21
+ let envPath = envFile;
22
+ if (fs.existsSync(envFile)) {
23
+ envPath = path.resolve(envFile);
24
+ } else if (fs.existsSync(`../../${envFile}`)) {
25
+ envPath = path.resolve(`../../${envFile}`);
26
+ } else {
27
+ throw new Error(`No ${envFile} file found`);
28
+ }
29
+
30
+ dotenv.config({ path: envPath });
31
+
32
+ export default ViteTestConfig();