@dopaminefx/core 0.1.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.
Files changed (136) hide show
  1. package/dist/engine/color.d.ts +71 -0
  2. package/dist/engine/color.d.ts.map +1 -0
  3. package/dist/engine/color.js +107 -0
  4. package/dist/engine/color.js.map +1 -0
  5. package/dist/engine/context.d.ts +54 -0
  6. package/dist/engine/context.d.ts.map +1 -0
  7. package/dist/engine/context.js +0 -0
  8. package/dist/engine/context.js.map +1 -0
  9. package/dist/engine/gl.d.ts +9 -0
  10. package/dist/engine/gl.d.ts.map +1 -0
  11. package/dist/engine/gl.js +39 -0
  12. package/dist/engine/gl.js.map +1 -0
  13. package/dist/engine/look/glsl.d.ts +95 -0
  14. package/dist/engine/look/glsl.d.ts.map +1 -0
  15. package/dist/engine/look/glsl.js +171 -0
  16. package/dist/engine/look/glsl.js.map +1 -0
  17. package/dist/engine/look/particles.glsl.d.ts +21 -0
  18. package/dist/engine/look/particles.glsl.d.ts.map +1 -0
  19. package/dist/engine/look/particles.glsl.js +44 -0
  20. package/dist/engine/look/particles.glsl.js.map +1 -0
  21. package/dist/engine/sdf.d.ts +77 -0
  22. package/dist/engine/sdf.d.ts.map +1 -0
  23. package/dist/engine/sdf.js +255 -0
  24. package/dist/engine/sdf.js.map +1 -0
  25. package/dist/engine/seed.d.ts +10 -0
  26. package/dist/engine/seed.d.ts.map +1 -0
  27. package/dist/engine/seed.js +20 -0
  28. package/dist/engine/seed.js.map +1 -0
  29. package/dist/engine/shadow.d.ts +41 -0
  30. package/dist/engine/shadow.d.ts.map +1 -0
  31. package/dist/engine/shadow.js +39 -0
  32. package/dist/engine/shadow.js.map +1 -0
  33. package/dist/engine/tempo.d.ts +33 -0
  34. package/dist/engine/tempo.d.ts.map +1 -0
  35. package/dist/engine/tempo.js +51 -0
  36. package/dist/engine/tempo.js.map +1 -0
  37. package/dist/framework/conductor.d.ts +100 -0
  38. package/dist/framework/conductor.d.ts.map +1 -0
  39. package/dist/framework/conductor.js +493 -0
  40. package/dist/framework/conductor.js.map +1 -0
  41. package/dist/framework/content.d.ts +67 -0
  42. package/dist/framework/content.d.ts.map +1 -0
  43. package/dist/framework/content.js +72 -0
  44. package/dist/framework/content.js.map +1 -0
  45. package/dist/framework/dope-pass.d.ts +131 -0
  46. package/dist/framework/dope-pass.d.ts.map +1 -0
  47. package/dist/framework/dope-pass.js +346 -0
  48. package/dist/framework/dope-pass.js.map +1 -0
  49. package/dist/framework/dope-zip.d.ts +22 -0
  50. package/dist/framework/dope-zip.d.ts.map +1 -0
  51. package/dist/framework/dope-zip.js +116 -0
  52. package/dist/framework/dope-zip.js.map +1 -0
  53. package/dist/framework/effect.d.ts +128 -0
  54. package/dist/framework/effect.d.ts.map +1 -0
  55. package/dist/framework/effect.js +19 -0
  56. package/dist/framework/effect.js.map +1 -0
  57. package/dist/framework/frame-expr.d.ts +124 -0
  58. package/dist/framework/frame-expr.d.ts.map +1 -0
  59. package/dist/framework/frame-expr.js +135 -0
  60. package/dist/framework/frame-expr.js.map +1 -0
  61. package/dist/framework/load-effect.d.ts +77 -0
  62. package/dist/framework/load-effect.d.ts.map +1 -0
  63. package/dist/framework/load-effect.js +135 -0
  64. package/dist/framework/load-effect.js.map +1 -0
  65. package/dist/framework/loader.d.ts +309 -0
  66. package/dist/framework/loader.d.ts.map +1 -0
  67. package/dist/framework/loader.js +266 -0
  68. package/dist/framework/loader.js.map +1 -0
  69. package/dist/framework/mood-registry.d.ts +58 -0
  70. package/dist/framework/mood-registry.d.ts.map +1 -0
  71. package/dist/framework/mood-registry.js +58 -0
  72. package/dist/framework/mood-registry.js.map +1 -0
  73. package/dist/framework/panel-runner.d.ts +96 -0
  74. package/dist/framework/panel-runner.d.ts.map +1 -0
  75. package/dist/framework/panel-runner.js +137 -0
  76. package/dist/framework/panel-runner.js.map +1 -0
  77. package/dist/framework/pass-common.d.ts +97 -0
  78. package/dist/framework/pass-common.d.ts.map +1 -0
  79. package/dist/framework/pass-common.js +178 -0
  80. package/dist/framework/pass-common.js.map +1 -0
  81. package/dist/framework/pass-runner.d.ts +183 -0
  82. package/dist/framework/pass-runner.d.ts.map +1 -0
  83. package/dist/framework/pass-runner.js +212 -0
  84. package/dist/framework/pass-runner.js.map +1 -0
  85. package/dist/framework/programs.d.ts +54 -0
  86. package/dist/framework/programs.d.ts.map +1 -0
  87. package/dist/framework/programs.js +33 -0
  88. package/dist/framework/programs.js.map +1 -0
  89. package/dist/framework/registry.d.ts +29 -0
  90. package/dist/framework/registry.d.ts.map +1 -0
  91. package/dist/framework/registry.js +38 -0
  92. package/dist/framework/registry.js.map +1 -0
  93. package/dist/framework/runtime.d.ts +19 -0
  94. package/dist/framework/runtime.d.ts.map +1 -0
  95. package/dist/framework/runtime.js +37 -0
  96. package/dist/framework/runtime.js.map +1 -0
  97. package/dist/index.d.ts +63 -0
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.js +126 -0
  100. package/dist/index.js.map +1 -0
  101. package/dist/overlay.d.ts +46 -0
  102. package/dist/overlay.d.ts.map +1 -0
  103. package/dist/overlay.js +79 -0
  104. package/dist/overlay.js.map +1 -0
  105. package/dist/types.d.ts +68 -0
  106. package/dist/types.d.ts.map +1 -0
  107. package/dist/types.js +10 -0
  108. package/dist/types.js.map +1 -0
  109. package/package.json +37 -0
  110. package/src/engine/color.ts +154 -0
  111. package/src/engine/context.ts +0 -0
  112. package/src/engine/gl.ts +46 -0
  113. package/src/engine/look/glsl.ts +183 -0
  114. package/src/engine/look/particles.glsl.ts +44 -0
  115. package/src/engine/sdf.ts +298 -0
  116. package/src/engine/seed.ts +23 -0
  117. package/src/engine/shadow.ts +66 -0
  118. package/src/engine/tempo.ts +54 -0
  119. package/src/framework/conductor.ts +604 -0
  120. package/src/framework/content.ts +113 -0
  121. package/src/framework/dope-pass.ts +432 -0
  122. package/src/framework/dope-zip.ts +125 -0
  123. package/src/framework/effect.ts +127 -0
  124. package/src/framework/frame-expr.ts +217 -0
  125. package/src/framework/load-effect.ts +204 -0
  126. package/src/framework/loader.ts +502 -0
  127. package/src/framework/mood-registry.ts +87 -0
  128. package/src/framework/panel-runner.ts +233 -0
  129. package/src/framework/pass-common.ts +222 -0
  130. package/src/framework/pass-runner.ts +391 -0
  131. package/src/framework/programs.ts +62 -0
  132. package/src/framework/registry.ts +44 -0
  133. package/src/framework/runtime.ts +38 -0
  134. package/src/index.ts +227 -0
  135. package/src/overlay.ts +109 -0
  136. package/src/types.ts +63 -0
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Shared GPU-particle GLSL chunk.
3
+ *
4
+ * Solarbloom's drifting "motes" and Verdict's flung "droplets" are both
5
+ * point-sprite particle fields that differ only in their MOTION model (motes:
6
+ * outward drift + buoyancy + curl, with motion-blur streaks; droplets: a
7
+ * ballistic arc under gravity) and their styling. The cross-pollination plan
8
+ * asks for one parametric particle module behind a shared include + params.
9
+ *
10
+ * Here we extract the parts that were duplicated verbatim between the two: the
11
+ * per-particle soft round sprite (`particleSprite`), the ballistic position
12
+ * (`ballisticPos`), and the standard fade-in/out over a particle's life
13
+ * (`particleFade`). Each effect keeps its own emit shape + motion (the part that
14
+ * is its identity) and composes these shared primitives, so the dot falloff,
15
+ * the gravity arc and the lifetime curve no longer drift between effects.
16
+ *
17
+ * Comic debris can adopt the same primitives later (deferred — noted in the
18
+ * plan as P2). Requires no other chunk.
19
+ */
20
+ export const GLSL_PARTICLES = /* glsl */ `
21
+ // Soft round particle sprite: an inverse-distance dot that, squared, gives the
22
+ // glowing-photon falloff both motes and droplets use. \`d\` is distance to the
23
+ // particle centre, \`size\` its radius in device px.
24
+ float particleSprite(float d, float size){
25
+ float s = size / (d + size * 0.5);
26
+ return s * s;
27
+ }
28
+
29
+ // Ballistic arc: launch from \`origin\` along \`dir\` at \`speed\`, pulled down by
30
+ // \`gravity\` (device px) over normalized particle life \`t\` (0..1). Screen y is
31
+ // up, so gravity subtracts on y. Used by Verdict's flung ink droplets and any
32
+ // effect that wants sparks thrown off an impact.
33
+ vec2 ballisticPos(vec2 origin, vec2 dir, float speed, float gravity, float t){
34
+ return origin + dir * speed * t - vec2(0.0, 1.0) * gravity * t * t;
35
+ }
36
+
37
+ // Standard particle fade: ramps in fast then fades out across its life. \`t\` is
38
+ // normalized particle life (0..1); \`tailPow\` shapes the decay (higher = longer
39
+ // luminous body before it dims).
40
+ float particleFade(float t, float tailPow){
41
+ return (1.0 - pow(t, tailPow)) * smoothstep(0.0, 0.08, t);
42
+ }
43
+ `;
44
+ //# sourceMappingURL=particles.glsl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"particles.glsl.js","sourceRoot":"","sources":["../../../src/engine/look/particles.glsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;CAuBxC,CAAC"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * SVG path → signed-distance-field (SDF) baker + runtime decoder.
3
+ *
4
+ * This is the "geometry seam": an effect's icon outline lives in its `.dope` as
5
+ * an `svgPath` string (authored, host-swappable). A BUILD step (scripts/bake-sdf
6
+ * or scripts/pack-dope) rasterizes that path into a small, self-contained SDF and
7
+ * inlines it into the distributed `.dope` (a `data:`-style base64 blob, no remote
8
+ * fetch). At RUNTIME the effect only DECODES + SAMPLES the SDF — it never does a
9
+ * live path→SDF conversion — so swapping the path in the `.dope` changes the
10
+ * rendered icon with no shader edit.
11
+ *
12
+ * The SDF here is a stroked-outline distance field: distance to the path's
13
+ * centerline (a polyline flattened from the bezier/line segments), so the icon
14
+ * reads as a *drawn stroke in light* (a tick, a cross, a custom mark), matching
15
+ * the "drawn-in-light" language of the built-ins. The field is signed only in the
16
+ * stroke sense (we store distance-to-stroke, with a coverage falloff applied at
17
+ * sample time by the shader against the declared stroke width), which is all the
18
+ * "drawn in light" look needs and keeps the bake free of robust point-in-polygon
19
+ * winding for arbitrary self-intersecting glyphs.
20
+ *
21
+ * Encoding (intentionally tiny, dependency-free, portable to Swift):
22
+ * - a fixed-size square grid of `size`×`size` 8-bit samples,
23
+ * - each sample = clamp(distance / range, 0..1) * 255 (0 = on the stroke,
24
+ * 255 = `range` author-units or more away),
25
+ * - serialized as a 4-byte header (magic 'D','S', size hi/lo) + the bytes,
26
+ * base64-encoded. `range` + `viewBox` travel as JSON next to the blob.
27
+ */
28
+ /** A flattened 2D point in author/viewBox units. */
29
+ export interface Pt {
30
+ x: number;
31
+ y: number;
32
+ }
33
+ /**
34
+ * Parse a (subset of) SVG path data into a list of polylines (each a list of
35
+ * points in author units). Supports absolute + relative M/L/H/V/C/Q/Z (the
36
+ * commands a designer's checkmark / cross / icon outline actually use). Curves
37
+ * are flattened to line segments at `steps` subdivisions — plenty for an SDF.
38
+ */
39
+ export declare function parseSvgPath(d: string, steps?: number): Pt[][];
40
+ /** A baked SDF: a square grid + the metadata a sampler needs. */
41
+ export interface BakedSdf {
42
+ /** Grid resolution (square). */
43
+ size: number;
44
+ /** Author-units of distance that map to the full 0..255 byte range. */
45
+ range: number;
46
+ /** The viewBox the path was authored in: [minX, minY, w, h]. */
47
+ viewBox: [number, number, number, number];
48
+ /**
49
+ * A `data:` URI carrying the header + size×size 8-bit distance bytes (base64).
50
+ * A `data:` URI (not a remote/absolute ref) keeps the `.dope` self-contained
51
+ * and passes the loader's standalone guard. The runtime decodes + samples it.
52
+ */
53
+ data: string;
54
+ }
55
+ /**
56
+ * BAKE: rasterize an SVG path into a stroked-distance SDF. Pure + deterministic.
57
+ * `size` is the grid resolution; `range` is how many author-units of distance map
58
+ * to the full byte range (a larger range = a softer, wider usable falloff). The
59
+ * path is normalized into a centered square that preserves its aspect inside the
60
+ * grid, leaving a small margin so the stroke + its glow never clip the edge.
61
+ */
62
+ export declare function bakeSdf(svgPath: string, viewBox: [number, number, number, number], size?: number, range?: number): BakedSdf;
63
+ /** A decoded SDF ready to upload: the raw single-channel bytes + its size. */
64
+ export interface DecodedSdf {
65
+ size: number;
66
+ range: number;
67
+ viewBox: [number, number, number, number];
68
+ /** size×size single-channel (0..255) distance bytes. */
69
+ bytes: Uint8Array;
70
+ }
71
+ /**
72
+ * DECODE a baked SDF blob back to its raw distance bytes. Validates the magic +
73
+ * the declared size against the byte count. Used by the runtime to upload an
74
+ * `R8`/alpha texture — the runtime SAMPLES this; it never re-bakes.
75
+ */
76
+ export declare function decodeSdf(baked: BakedSdf): DecodedSdf;
77
+ //# sourceMappingURL=sdf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdf.d.ts","sourceRoot":"","sources":["../../src/engine/sdf.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,oDAAoD;AACpD,MAAM,WAAW,EAAE;IACjB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,EAAE,EAAE,EAAE,CA8H1D;AAcD,iEAAiE;AACjE,MAAM,WAAW,QAAQ;IACvB,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AA4BD;;;;;;GAMG;AACH,wBAAgB,OAAO,CACrB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EACzC,IAAI,SAAK,EACT,KAAK,SAAK,GACT,QAAQ,CAiCV;AAED,8EAA8E;AAC9E,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,wDAAwD;IACxD,KAAK,EAAE,UAAU,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,QAAQ,GAAG,UAAU,CAcrD"}
@@ -0,0 +1,255 @@
1
+ /**
2
+ * SVG path → signed-distance-field (SDF) baker + runtime decoder.
3
+ *
4
+ * This is the "geometry seam": an effect's icon outline lives in its `.dope` as
5
+ * an `svgPath` string (authored, host-swappable). A BUILD step (scripts/bake-sdf
6
+ * or scripts/pack-dope) rasterizes that path into a small, self-contained SDF and
7
+ * inlines it into the distributed `.dope` (a `data:`-style base64 blob, no remote
8
+ * fetch). At RUNTIME the effect only DECODES + SAMPLES the SDF — it never does a
9
+ * live path→SDF conversion — so swapping the path in the `.dope` changes the
10
+ * rendered icon with no shader edit.
11
+ *
12
+ * The SDF here is a stroked-outline distance field: distance to the path's
13
+ * centerline (a polyline flattened from the bezier/line segments), so the icon
14
+ * reads as a *drawn stroke in light* (a tick, a cross, a custom mark), matching
15
+ * the "drawn-in-light" language of the built-ins. The field is signed only in the
16
+ * stroke sense (we store distance-to-stroke, with a coverage falloff applied at
17
+ * sample time by the shader against the declared stroke width), which is all the
18
+ * "drawn in light" look needs and keeps the bake free of robust point-in-polygon
19
+ * winding for arbitrary self-intersecting glyphs.
20
+ *
21
+ * Encoding (intentionally tiny, dependency-free, portable to Swift):
22
+ * - a fixed-size square grid of `size`×`size` 8-bit samples,
23
+ * - each sample = clamp(distance / range, 0..1) * 255 (0 = on the stroke,
24
+ * 255 = `range` author-units or more away),
25
+ * - serialized as a 4-byte header (magic 'D','S', size hi/lo) + the bytes,
26
+ * base64-encoded. `range` + `viewBox` travel as JSON next to the blob.
27
+ */
28
+ /**
29
+ * Parse a (subset of) SVG path data into a list of polylines (each a list of
30
+ * points in author units). Supports absolute + relative M/L/H/V/C/Q/Z (the
31
+ * commands a designer's checkmark / cross / icon outline actually use). Curves
32
+ * are flattened to line segments at `steps` subdivisions — plenty for an SDF.
33
+ */
34
+ export function parseSvgPath(d, steps = 24) {
35
+ const polylines = [];
36
+ let cur = [];
37
+ let cx = 0;
38
+ let cy = 0;
39
+ let startX = 0;
40
+ let startY = 0;
41
+ // Tokenize into command letters + numeric runs.
42
+ const tokens = d.match(/[a-zA-Z]|-?\d*\.?\d+(?:e[-+]?\d+)?/gi) ?? [];
43
+ let i = 0;
44
+ const num = () => Number(tokens[i++]);
45
+ const has = () => i < tokens.length && /^-?\.?\d/.test(tokens[i]);
46
+ const push = (x, y) => {
47
+ cur.push({ x, y });
48
+ cx = x;
49
+ cy = y;
50
+ };
51
+ const flushCur = () => {
52
+ if (cur.length > 1)
53
+ polylines.push(cur);
54
+ cur = [];
55
+ };
56
+ let cmd = "";
57
+ while (i < tokens.length) {
58
+ const t = tokens[i];
59
+ if (/[a-zA-Z]/.test(t)) {
60
+ cmd = t;
61
+ i++;
62
+ }
63
+ else if (!cmd) {
64
+ i++;
65
+ continue;
66
+ }
67
+ const rel = cmd === cmd.toLowerCase();
68
+ switch (cmd.toUpperCase()) {
69
+ case "M": {
70
+ flushCur();
71
+ const x = num() + (rel ? cx : 0);
72
+ const y = num() + (rel ? cy : 0);
73
+ startX = x;
74
+ startY = y;
75
+ push(x, y);
76
+ cmd = rel ? "l" : "L"; // subsequent pairs are implicit lineto
77
+ while (has()) {
78
+ const lx = num() + (rel ? cx : 0);
79
+ const ly = num() + (rel ? cy : 0);
80
+ push(lx, ly);
81
+ }
82
+ break;
83
+ }
84
+ case "L": {
85
+ do {
86
+ const x = num() + (rel ? cx : 0);
87
+ const y = num() + (rel ? cy : 0);
88
+ push(x, y);
89
+ } while (has());
90
+ break;
91
+ }
92
+ case "H": {
93
+ do {
94
+ const x = num() + (rel ? cx : 0);
95
+ push(x, cy);
96
+ } while (has());
97
+ break;
98
+ }
99
+ case "V": {
100
+ do {
101
+ const y = num() + (rel ? cy : 0);
102
+ push(cx, y);
103
+ } while (has());
104
+ break;
105
+ }
106
+ case "C": {
107
+ do {
108
+ const x1 = num() + (rel ? cx : 0);
109
+ const y1 = num() + (rel ? cy : 0);
110
+ const x2 = num() + (rel ? cx : 0);
111
+ const y2 = num() + (rel ? cy : 0);
112
+ const x = num() + (rel ? cx : 0);
113
+ const y = num() + (rel ? cy : 0);
114
+ const p0 = { x: cx, y: cy };
115
+ for (let s = 1; s <= steps; s++) {
116
+ const u = s / steps;
117
+ const mt = 1 - u;
118
+ const bx = mt * mt * mt * p0.x + 3 * mt * mt * u * x1 + 3 * mt * u * u * x2 + u * u * u * x;
119
+ const by = mt * mt * mt * p0.y + 3 * mt * mt * u * y1 + 3 * mt * u * u * y2 + u * u * u * y;
120
+ push(bx, by);
121
+ }
122
+ } while (has());
123
+ break;
124
+ }
125
+ case "Q": {
126
+ do {
127
+ const x1 = num() + (rel ? cx : 0);
128
+ const y1 = num() + (rel ? cy : 0);
129
+ const x = num() + (rel ? cx : 0);
130
+ const y = num() + (rel ? cy : 0);
131
+ const p0 = { x: cx, y: cy };
132
+ for (let s = 1; s <= steps; s++) {
133
+ const u = s / steps;
134
+ const mt = 1 - u;
135
+ const bx = mt * mt * p0.x + 2 * mt * u * x1 + u * u * x;
136
+ const by = mt * mt * p0.y + 2 * mt * u * y1 + u * u * y;
137
+ push(bx, by);
138
+ }
139
+ } while (has());
140
+ break;
141
+ }
142
+ case "Z": {
143
+ if (cur.length) {
144
+ push(startX, startY);
145
+ flushCur();
146
+ }
147
+ break;
148
+ }
149
+ default:
150
+ // Unknown command — skip its number to avoid an infinite loop.
151
+ if (has())
152
+ num();
153
+ break;
154
+ }
155
+ }
156
+ flushCur();
157
+ return polylines;
158
+ }
159
+ /** Distance from point p to segment a→b, in author units. */
160
+ function distToSeg(px, py, ax, ay, bx, by) {
161
+ const dx = bx - ax;
162
+ const dy = by - ay;
163
+ const len2 = dx * dx + dy * dy;
164
+ let t = len2 > 1e-9 ? ((px - ax) * dx + (py - ay) * dy) / len2 : 0;
165
+ t = t < 0 ? 0 : t > 1 ? 1 : t;
166
+ const qx = ax + dx * t;
167
+ const qy = ay + dy * t;
168
+ return Math.hypot(px - qx, py - qy);
169
+ }
170
+ /** The MIME used for the inline SDF `data:` URI. */
171
+ const SDF_MIME = "application/octet-stream";
172
+ const SDF_DATA_PREFIX = `data:${SDF_MIME};base64,`;
173
+ const MAGIC0 = 0x44; // 'D'
174
+ const MAGIC1 = 0x53; // 'S'
175
+ /** Encode raw bytes to base64 in both Node and the browser. */
176
+ function bytesToBase64(bytes) {
177
+ if (typeof Buffer !== "undefined")
178
+ return Buffer.from(bytes).toString("base64");
179
+ let bin = "";
180
+ for (let i = 0; i < bytes.length; i++)
181
+ bin += String.fromCharCode(bytes[i]);
182
+ // eslint-disable-next-line no-undef
183
+ return btoa(bin);
184
+ }
185
+ /** Decode base64 to raw bytes in both Node and the browser. */
186
+ function base64ToBytes(b64) {
187
+ if (typeof Buffer !== "undefined")
188
+ return new Uint8Array(Buffer.from(b64, "base64"));
189
+ // eslint-disable-next-line no-undef
190
+ const bin = atob(b64);
191
+ const out = new Uint8Array(bin.length);
192
+ for (let i = 0; i < bin.length; i++)
193
+ out[i] = bin.charCodeAt(i);
194
+ return out;
195
+ }
196
+ /**
197
+ * BAKE: rasterize an SVG path into a stroked-distance SDF. Pure + deterministic.
198
+ * `size` is the grid resolution; `range` is how many author-units of distance map
199
+ * to the full byte range (a larger range = a softer, wider usable falloff). The
200
+ * path is normalized into a centered square that preserves its aspect inside the
201
+ * grid, leaving a small margin so the stroke + its glow never clip the edge.
202
+ */
203
+ export function bakeSdf(svgPath, viewBox, size = 64, range = 18) {
204
+ const polylines = parseSvgPath(svgPath);
205
+ const [vx, vy, vw, vh] = viewBox;
206
+ const bytes = new Uint8Array(size * size);
207
+ // Map a grid cell center to author/viewBox coordinates (y stays top-down, the
208
+ // shader flips as needed). The whole viewBox maps to the grid 0..size.
209
+ for (let gy = 0; gy < size; gy++) {
210
+ for (let gx = 0; gx < size; gx++) {
211
+ const ax = vx + ((gx + 0.5) / size) * vw;
212
+ const ay = vy + ((gy + 0.5) / size) * vh;
213
+ let best = Infinity;
214
+ for (const poly of polylines) {
215
+ for (let k = 0; k + 1 < poly.length; k++) {
216
+ const d = distToSeg(ax, ay, poly[k].x, poly[k].y, poly[k + 1].x, poly[k + 1].y);
217
+ if (d < best)
218
+ best = d;
219
+ }
220
+ }
221
+ const norm = best === Infinity ? 1 : Math.min(1, best / range);
222
+ bytes[gy * size + gx] = Math.round(norm * 255);
223
+ }
224
+ }
225
+ const header = new Uint8Array(4);
226
+ header[0] = MAGIC0;
227
+ header[1] = MAGIC1;
228
+ header[2] = (size >> 8) & 0xff;
229
+ header[3] = size & 0xff;
230
+ const blob = new Uint8Array(header.length + bytes.length);
231
+ blob.set(header, 0);
232
+ blob.set(bytes, header.length);
233
+ return { size, range, viewBox, data: SDF_DATA_PREFIX + bytesToBase64(blob) };
234
+ }
235
+ /**
236
+ * DECODE a baked SDF blob back to its raw distance bytes. Validates the magic +
237
+ * the declared size against the byte count. Used by the runtime to upload an
238
+ * `R8`/alpha texture — the runtime SAMPLES this; it never re-bakes.
239
+ */
240
+ export function decodeSdf(baked) {
241
+ const b64 = baked.data.startsWith(SDF_DATA_PREFIX)
242
+ ? baked.data.slice(SDF_DATA_PREFIX.length)
243
+ : baked.data; // tolerate a bare base64 blob too
244
+ const blob = base64ToBytes(b64);
245
+ if (blob.length < 4 || blob[0] !== MAGIC0 || blob[1] !== MAGIC1) {
246
+ throw new Error("dope: not a baked SDF blob (bad magic)");
247
+ }
248
+ const size = (blob[2] << 8) | blob[3];
249
+ const bytes = blob.subarray(4);
250
+ if (bytes.length !== size * size) {
251
+ throw new Error(`dope: SDF size mismatch (header ${size}^2 != ${bytes.length} bytes)`);
252
+ }
253
+ return { size, range: baked.range, viewBox: baked.viewBox, bytes: bytes.slice() };
254
+ }
255
+ //# sourceMappingURL=sdf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdf.js","sourceRoot":"","sources":["../../src/engine/sdf.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /** Deterministic, seedable PRNG so a given `seed` always yields the same look. */
2
+ export type Rng = () => number;
3
+ /**
4
+ * mulberry32 — tiny, fast, good-enough-for-visuals PRNG. Returns values in
5
+ * [0, 1). Deterministic for a given 32-bit seed.
6
+ */
7
+ export declare function mulberry32(seed: number): Rng;
8
+ /** A fresh 32-bit seed — used when the caller doesn't pin one. */
9
+ export declare function randomSeed(): number;
10
+ //# sourceMappingURL=seed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../../src/engine/seed.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAElF,MAAM,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC;AAE/B;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAS5C;AAED,kEAAkE;AAClE,wBAAgB,UAAU,IAAI,MAAM,CAEnC"}
@@ -0,0 +1,20 @@
1
+ /** Deterministic, seedable PRNG so a given `seed` always yields the same look. */
2
+ /**
3
+ * mulberry32 — tiny, fast, good-enough-for-visuals PRNG. Returns values in
4
+ * [0, 1). Deterministic for a given 32-bit seed.
5
+ */
6
+ export function mulberry32(seed) {
7
+ let a = seed >>> 0;
8
+ return function next() {
9
+ a |= 0;
10
+ a = (a + 0x6d2b79f5) | 0;
11
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
12
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
13
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
14
+ };
15
+ }
16
+ /** A fresh 32-bit seed — used when the caller doesn't pin one. */
17
+ export function randomSeed() {
18
+ return (Math.random() * 0xffffffff) >>> 0;
19
+ }
20
+ //# sourceMappingURL=seed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed.js","sourceRoot":"","sources":["../../src/engine/seed.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAIlF;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,IAAI,CAAC,GAAG,IAAI,KAAK,CAAC,CAAC;IACnB,OAAO,SAAS,IAAI;QAClB,CAAC,IAAI,CAAC,CAAC;QACP,CAAC,GAAG,CAAC,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACzC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAC/C,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,UAAU,CAAC;IAC/C,CAAC,CAAC;AACJ,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,UAAU;IACxB,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Shadow-pass geometry — the pure math that turns an effect's amplitude,
3
+ * "height" above the page, and stylization into the offset / softness /
4
+ * strength of the cast soft shadow. Kept framework- and GL-free so it can be
5
+ * unit-tested and reused by any effect that adopts the multiply shadow layer.
6
+ *
7
+ * Conventions (device pixels, gl coords where Y is UP):
8
+ * - The implied key light sits up-and-left of the floating effect, so the
9
+ * shadow falls DOWN-and-right: offset = (+x, -y).
10
+ * - `offset` grows with the occluder's height and with amplitude (a brighter,
11
+ * higher source throws a longer shadow).
12
+ * - `soft` (penumbra blur radius) is larger for a soft photoreal source and
13
+ * tightens toward the hard graphic drop-shadow of the cel end.
14
+ * - `strength` is the max darkening of the multiply layer (0 = none); kept
15
+ * ambient-occlusion subtle, a touch firmer toward cel.
16
+ */
17
+ export interface ShadowGeometry {
18
+ /** Silhouette offset in device px, gl coords (x right, y up). */
19
+ offsetX: number;
20
+ offsetY: number;
21
+ /** Penumbra blur tap radius in device px. */
22
+ soft: number;
23
+ /** Max multiply darkening, 0..1. */
24
+ strength: number;
25
+ }
26
+ export interface ShadowInput {
27
+ /** Smaller canvas dimension in device px. */
28
+ minDim: number;
29
+ /**
30
+ * The occluder's "height" above the page as a fraction of `minDim` — bigger
31
+ * forms read as floating higher and cast longer, softer shadows. For
32
+ * Solarbloom this is the bloom radius fraction; for Verdict, the stroke scale.
33
+ */
34
+ heightFrac: number;
35
+ /** Envelope amplitude (peaks > 1). */
36
+ amp: number;
37
+ /** Stylization 0..1 (photoreal → cel). */
38
+ style: number;
39
+ }
40
+ export declare function shadowGeometry({ minDim, heightFrac, amp, style }: ShadowInput): ShadowGeometry;
41
+ //# sourceMappingURL=shadow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shadow.d.ts","sourceRoot":"","sources":["../../src/engine/shadow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;CACf;AAKD,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,WAAW,GAAG,cAAc,CAoB9F"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Shadow-pass geometry — the pure math that turns an effect's amplitude,
3
+ * "height" above the page, and stylization into the offset / softness /
4
+ * strength of the cast soft shadow. Kept framework- and GL-free so it can be
5
+ * unit-tested and reused by any effect that adopts the multiply shadow layer.
6
+ *
7
+ * Conventions (device pixels, gl coords where Y is UP):
8
+ * - The implied key light sits up-and-left of the floating effect, so the
9
+ * shadow falls DOWN-and-right: offset = (+x, -y).
10
+ * - `offset` grows with the occluder's height and with amplitude (a brighter,
11
+ * higher source throws a longer shadow).
12
+ * - `soft` (penumbra blur radius) is larger for a soft photoreal source and
13
+ * tightens toward the hard graphic drop-shadow of the cel end.
14
+ * - `strength` is the max darkening of the multiply layer (0 = none); kept
15
+ * ambient-occlusion subtle, a touch firmer toward cel.
16
+ */
17
+ const clamp = (x, lo, hi) => x < lo ? lo : x > hi ? hi : x;
18
+ export function shadowGeometry({ minDim, heightFrac, amp, style }) {
19
+ const height = heightFrac * minDim;
20
+ // Offset length: scales with height and (clamped) amplitude. A meaningful
21
+ // drop so the silhouette clears the bright core (which the screen light owns)
22
+ // and lands as a distinct shadow on the UI beside/below it.
23
+ const off = height * 0.16 * (0.6 + 0.5 * Math.min(amp, 1.5));
24
+ // Penumbra: wide & soft when photoreal, tight when cel; always a small floor.
25
+ const soft = minDim * 0.014 * (1 - 0.6 * style) + minDim * 0.005;
26
+ // Darkening of the multiply layer. Kept ambient at the soft end, firmer (a
27
+ // graphic drop-shadow) toward cel. Reads clearly where it falls on the
28
+ // lighter raised faces / the white primary button.
29
+ const strength = clamp(0.6 * (0.8 + 0.45 * style), 0, 1);
30
+ return {
31
+ // Down-and-right: x positive, y negative (gl Y up). x is a fraction of the
32
+ // drop so the shadow leans, not straight down.
33
+ offsetX: off * 0.55,
34
+ offsetY: -off,
35
+ soft,
36
+ strength,
37
+ };
38
+ }
39
+ //# sourceMappingURL=shadow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shadow.js","sourceRoot":"","sources":["../../src/engine/shadow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AA2BH,MAAM,KAAK,GAAG,CAAC,CAAS,EAAE,EAAU,EAAE,EAAU,EAAU,EAAE,CAC1D,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AAEhC,MAAM,UAAU,cAAc,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAe;IAC5E,MAAM,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;IACnC,0EAA0E;IAC1E,8EAA8E;IAC9E,4DAA4D;IAC5D,MAAM,GAAG,GAAG,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IAC7D,8EAA8E;IAC9E,MAAM,IAAI,GAAG,MAAM,GAAG,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,GAAG,KAAK,CAAC,GAAG,MAAM,GAAG,KAAK,CAAC;IACjE,2EAA2E;IAC3E,uEAAuE;IACvE,mDAAmD;IACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACzD,OAAO;QACL,2EAA2E;QAC3E,+CAA+C;QAC/C,OAAO,EAAE,GAAG,GAAG,IAAI;QACnB,OAAO,EAAE,CAAC,GAAG;QACb,IAAI;QACJ,QAAQ;KACT,CAAC;AACJ,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Animation tempo PRIMITIVES — the generic easing + envelope building blocks
3
+ * shared across effects.
4
+ *
5
+ * Linear motion reads as unnatural, so everything here is eased. These are the
6
+ * GENERIC primitives only: each effect's BESPOKE envelope (the comic slam, the
7
+ * fail stamp/shake, the heartburst lub-dub, the lightning strike/strobe, the
8
+ * solarbloom check draw, the inkstroke stroke draw, …) lives in that effect's
9
+ * own `<name>-tempo.ts` inside `@dopaminefx/effect-<name>`, built on top of these.
10
+ */
11
+ /**
12
+ * Coarse animation step (ms) for the hand-drawn "animate on twos" look at full
13
+ * whimsy — ~12 updates/sec, i.e. 24fps on twos. Motion is snapped toward this
14
+ * grid as style rises (see the pass-runner), giving discrete, posed beats
15
+ * instead of smooth interpolation.
16
+ */
17
+ export declare const NPR_TIME_STEP_MS: number;
18
+ /** Clamp a value into [0, 1]. */
19
+ export declare const clamp01: (x: number) => number;
20
+ /** Classic ease-out cubic — quick start, gentle settle. */
21
+ export declare function easeOutCubic(x: number): number;
22
+ /**
23
+ * Ease-out "back" — overshoots past 1 then settles exactly to 1 at x=1. This is
24
+ * the held-breath release. `overshoot` scales how far past 1 it swells.
25
+ */
26
+ export declare function easeOutBack(x: number, overshoot?: number): number;
27
+ /**
28
+ * Bloom amplitude over normalized life `t` ∈ [0, 1].
29
+ * Fast attack with overshoot in the first ~18%, then a long decay to zero.
30
+ * `envelope(0) === 0`, `envelope(1) === 0`, peak > 1 during the attack.
31
+ */
32
+ export declare function envelope(t: number, overshoot?: number): number;
33
+ //# sourceMappingURL=tempo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tempo.d.ts","sourceRoot":"","sources":["../../src/engine/tempo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,QAAY,CAAC;AAE1C,iCAAiC;AACjC,eAAO,MAAM,OAAO,GAAI,GAAG,MAAM,KAAG,MAAqC,CAAC;AAE1E,2DAA2D;AAC3D,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAG9C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,SAAI,GAAG,MAAM,CAK5D;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,SAAI,GAAG,MAAM,CASzD"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Animation tempo PRIMITIVES — the generic easing + envelope building blocks
3
+ * shared across effects.
4
+ *
5
+ * Linear motion reads as unnatural, so everything here is eased. These are the
6
+ * GENERIC primitives only: each effect's BESPOKE envelope (the comic slam, the
7
+ * fail stamp/shake, the heartburst lub-dub, the lightning strike/strobe, the
8
+ * solarbloom check draw, the inkstroke stroke draw, …) lives in that effect's
9
+ * own `<name>-tempo.ts` inside `@dopaminefx/effect-<name>`, built on top of these.
10
+ */
11
+ /**
12
+ * Coarse animation step (ms) for the hand-drawn "animate on twos" look at full
13
+ * whimsy — ~12 updates/sec, i.e. 24fps on twos. Motion is snapped toward this
14
+ * grid as style rises (see the pass-runner), giving discrete, posed beats
15
+ * instead of smooth interpolation.
16
+ */
17
+ export const NPR_TIME_STEP_MS = 1000 / 12;
18
+ /** Clamp a value into [0, 1]. */
19
+ export const clamp01 = (x) => (x < 0 ? 0 : x > 1 ? 1 : x);
20
+ /** Classic ease-out cubic — quick start, gentle settle. */
21
+ export function easeOutCubic(x) {
22
+ const t = clamp01(x);
23
+ return 1 - Math.pow(1 - t, 3);
24
+ }
25
+ /**
26
+ * Ease-out "back" — overshoots past 1 then settles exactly to 1 at x=1. This is
27
+ * the held-breath release. `overshoot` scales how far past 1 it swells.
28
+ */
29
+ export function easeOutBack(x, overshoot = 1) {
30
+ const t = clamp01(x);
31
+ const c1 = 1.70158 * overshoot;
32
+ const c3 = c1 + 1;
33
+ return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
34
+ }
35
+ /**
36
+ * Bloom amplitude over normalized life `t` ∈ [0, 1].
37
+ * Fast attack with overshoot in the first ~18%, then a long decay to zero.
38
+ * `envelope(0) === 0`, `envelope(1) === 0`, peak > 1 during the attack.
39
+ */
40
+ export function envelope(t, overshoot = 1) {
41
+ if (t <= 0 || t >= 1)
42
+ return 0;
43
+ const attack = 0.18;
44
+ if (t < attack) {
45
+ return easeOutBack(t / attack, overshoot);
46
+ }
47
+ const x = (t - attack) / (1 - attack);
48
+ // Decays from 1 → 0; exponent > 1 keeps a slow, luxurious tail.
49
+ return Math.pow(1 - x, 1.6);
50
+ }
51
+ //# sourceMappingURL=tempo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tempo.js","sourceRoot":"","sources":["../../src/engine/tempo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAE,CAAC;AAE1C,iCAAiC;AACjC,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE1E,2DAA2D;AAC3D,MAAM,UAAU,YAAY,CAAC,CAAS;IACpC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACrB,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,CAAS,EAAE,SAAS,GAAG,CAAC;IAClD,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACrB,MAAM,EAAE,GAAG,OAAO,GAAG,SAAS,CAAC;IAC/B,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAClB,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,CAAS,EAAE,SAAS,GAAG,CAAC;IAC/C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,CAAC,GAAG,MAAM,EAAE,CAAC;QACf,OAAO,WAAW,CAAC,CAAC,GAAG,MAAM,EAAE,SAAS,CAAC,CAAC;IAC5C,CAAC;IACD,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;IACtC,gEAAgE;IAChE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;AAC9B,CAAC"}