@dixy-ui/themes 0.0.0-stage → 0.1.0-beta.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 (211) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +104 -2
  3. package/dist/ansi/ornament.d.ts +3 -0
  4. package/dist/ansi/ornament.js +8 -0
  5. package/dist/ansi-panes.css +929 -0
  6. package/dist/art-deco.css +850 -0
  7. package/dist/atelier/ornament.d.ts +4 -0
  8. package/dist/atelier/ornament.js +9 -0
  9. package/dist/atomic/ornament.d.ts +3 -0
  10. package/dist/atomic/ornament.js +8 -0
  11. package/dist/atomic-lounge.css +946 -0
  12. package/dist/authoring.d.ts +142 -0
  13. package/dist/authoring.js +83 -0
  14. package/dist/azulejo/ornament.d.ts +3 -0
  15. package/dist/azulejo/ornament.js +8 -0
  16. package/dist/azulejo-tile.css +962 -0
  17. package/dist/blueprint/ornament.d.ts +3 -0
  18. package/dist/blueprint/ornament.js +8 -0
  19. package/dist/blueprint.css +897 -0
  20. package/dist/brutalist/ornament.d.ts +3 -0
  21. package/dist/brutalist/ornament.js +8 -0
  22. package/dist/brutalist-concrete.css +936 -0
  23. package/dist/bytes.json +162 -0
  24. package/dist/candy-clay.css +778 -0
  25. package/dist/catalog.d.ts +1346 -0
  26. package/dist/catalog.js +1379 -0
  27. package/dist/character.d.ts +103 -0
  28. package/dist/character.js +477 -0
  29. package/dist/chrome.d.ts +114 -0
  30. package/dist/chrome.js +247 -0
  31. package/dist/clay/ornament.d.ts +3 -0
  32. package/dist/clay/ornament.js +8 -0
  33. package/dist/compile-css.d.ts +3 -0
  34. package/dist/compile-css.js +133 -0
  35. package/dist/deco/ornament.d.ts +3 -0
  36. package/dist/deco/ornament.js +8 -0
  37. package/dist/diwali/ornament.d.ts +3 -0
  38. package/dist/diwali/ornament.js +8 -0
  39. package/dist/diwali-lights.css +894 -0
  40. package/dist/documents.d.ts +104 -0
  41. package/dist/documents.js +1411 -0
  42. package/dist/forest/ornament.d.ts +3 -0
  43. package/dist/forest/ornament.js +12 -0
  44. package/dist/foundations.d.ts +105 -0
  45. package/dist/foundations.js +140 -0
  46. package/dist/frame-corners.d.ts +5 -0
  47. package/dist/frame-corners.js +5 -0
  48. package/dist/frame-styles.d.ts +36 -0
  49. package/dist/frame-styles.js +146 -0
  50. package/dist/freeze.d.ts +2 -0
  51. package/dist/freeze.js +9 -0
  52. package/dist/freighter/ornament.d.ts +3 -0
  53. package/dist/freighter/ornament.js +8 -0
  54. package/dist/freighter.css +942 -0
  55. package/dist/glass/ornament.d.ts +3 -0
  56. package/dist/glass/ornament.js +8 -0
  57. package/dist/glass-ui.css +923 -0
  58. package/dist/hardware/ornament.d.ts +4 -0
  59. package/dist/hardware/ornament.js +10 -0
  60. package/dist/holi/ornament.d.ts +3 -0
  61. package/dist/holi/ornament.js +8 -0
  62. package/dist/holi-colours.css +887 -0
  63. package/dist/hud/ornament.d.ts +3 -0
  64. package/dist/hud/ornament.js +8 -0
  65. package/dist/light/ornament.d.ts +3 -0
  66. package/dist/light/ornament.js +10 -0
  67. package/dist/living-forest.css +917 -0
  68. package/dist/lunar/ornament.d.ts +3 -0
  69. package/dist/lunar/ornament.js +8 -0
  70. package/dist/lunar-new-year.css +811 -0
  71. package/dist/manifests.d.ts +2485 -0
  72. package/dist/manifests.js +264 -0
  73. package/dist/material-light.css +880 -0
  74. package/dist/neon/ornament.d.ts +3 -0
  75. package/dist/neon/ornament.js +8 -0
  76. package/dist/neon-night-market.css +944 -0
  77. package/dist/ocean/ornament.d.ts +3 -0
  78. package/dist/ocean/ornament.js +8 -0
  79. package/dist/ocean-depths.css +825 -0
  80. package/dist/orbital-hud.css +973 -0
  81. package/dist/organic-css.d.ts +3 -0
  82. package/dist/organic-css.js +23 -0
  83. package/dist/organic-geometry.d.ts +20 -0
  84. package/dist/organic-geometry.js +74 -0
  85. package/dist/organic-ornament.d.ts +9 -0
  86. package/dist/organic-ornament.js +28 -0
  87. package/dist/organic-paint-launch.d.ts +43 -0
  88. package/dist/organic-paint-launch.js +1156 -0
  89. package/dist/organic-paint-world.d.ts +24 -0
  90. package/dist/organic-paint-world.js +676 -0
  91. package/dist/organic-paint.d.ts +50 -0
  92. package/dist/organic-paint.js +695 -0
  93. package/dist/organic-runtime.d.ts +4 -0
  94. package/dist/organic-runtime.js +231 -0
  95. package/dist/organic-settings.d.ts +680 -0
  96. package/dist/organic-settings.js +433 -0
  97. package/dist/ornament-parts.d.ts +20 -0
  98. package/dist/ornament-parts.js +15 -0
  99. package/dist/papel-picado.css +904 -0
  100. package/dist/phosphor/ornament.d.ts +3 -0
  101. package/dist/phosphor/ornament.js +8 -0
  102. package/dist/phosphor-terminal.css +1011 -0
  103. package/dist/picado/ornament.d.ts +3 -0
  104. package/dist/picado/ornament.js +8 -0
  105. package/dist/prism/ornament.d.ts +4 -0
  106. package/dist/prism/ornament.js +9 -0
  107. package/dist/prism-glass.css +970 -0
  108. package/dist/recipes.d.ts +938 -0
  109. package/dist/recipes.js +939 -0
  110. package/dist/resolve.d.ts +40 -0
  111. package/dist/resolve.js +24 -0
  112. package/dist/retro/ornament.d.ts +5 -0
  113. package/dist/retro/ornament.js +10 -0
  114. package/dist/retro-os.css +867 -0
  115. package/dist/riso/ornament.d.ts +3 -0
  116. package/dist/riso/ornament.js +8 -0
  117. package/dist/riso-zine.css +786 -0
  118. package/dist/sail/ornament.d.ts +3 -0
  119. package/dist/sail/ornament.js +8 -0
  120. package/dist/scrapbook.css +685 -0
  121. package/dist/signal/ornament.d.ts +4 -0
  122. package/dist/signal/ornament.js +11 -0
  123. package/dist/skins/ansi-panes.d.ts +68 -0
  124. package/dist/skins/ansi-panes.js +68 -0
  125. package/dist/skins/art-deco.d.ts +68 -0
  126. package/dist/skins/art-deco.js +68 -0
  127. package/dist/skins/atomic-lounge.d.ts +68 -0
  128. package/dist/skins/atomic-lounge.js +68 -0
  129. package/dist/skins/azulejo-tile.d.ts +68 -0
  130. package/dist/skins/azulejo-tile.js +68 -0
  131. package/dist/skins/blueprint.d.ts +68 -0
  132. package/dist/skins/blueprint.js +68 -0
  133. package/dist/skins/brutalist-concrete.d.ts +68 -0
  134. package/dist/skins/brutalist-concrete.js +68 -0
  135. package/dist/skins/candy-clay.d.ts +57 -0
  136. package/dist/skins/candy-clay.js +57 -0
  137. package/dist/skins/diwali-lights.d.ts +68 -0
  138. package/dist/skins/diwali-lights.js +68 -0
  139. package/dist/skins/freighter.d.ts +68 -0
  140. package/dist/skins/freighter.js +68 -0
  141. package/dist/skins/glass-ui.d.ts +68 -0
  142. package/dist/skins/glass-ui.js +68 -0
  143. package/dist/skins/holi-colours.d.ts +68 -0
  144. package/dist/skins/holi-colours.js +68 -0
  145. package/dist/skins/living-forest.d.ts +68 -0
  146. package/dist/skins/living-forest.js +68 -0
  147. package/dist/skins/lunar-new-year.d.ts +68 -0
  148. package/dist/skins/lunar-new-year.js +68 -0
  149. package/dist/skins/material-light.d.ts +57 -0
  150. package/dist/skins/material-light.js +57 -0
  151. package/dist/skins/neon-night-market.d.ts +68 -0
  152. package/dist/skins/neon-night-market.js +68 -0
  153. package/dist/skins/ocean-depths.d.ts +57 -0
  154. package/dist/skins/ocean-depths.js +57 -0
  155. package/dist/skins/orbital-hud.d.ts +68 -0
  156. package/dist/skins/orbital-hud.js +68 -0
  157. package/dist/skins/papel-picado.d.ts +68 -0
  158. package/dist/skins/papel-picado.js +68 -0
  159. package/dist/skins/phosphor-terminal.d.ts +68 -0
  160. package/dist/skins/phosphor-terminal.js +68 -0
  161. package/dist/skins/prism-glass.d.ts +57 -0
  162. package/dist/skins/prism-glass.js +57 -0
  163. package/dist/skins/retro-os.d.ts +57 -0
  164. package/dist/skins/retro-os.js +57 -0
  165. package/dist/skins/riso-zine.d.ts +68 -0
  166. package/dist/skins/riso-zine.js +68 -0
  167. package/dist/skins/scrapbook.d.ts +57 -0
  168. package/dist/skins/scrapbook.js +57 -0
  169. package/dist/skins/solar-sail.d.ts +68 -0
  170. package/dist/skins/solar-sail.js +68 -0
  171. package/dist/skins/studio-hardware.d.ts +57 -0
  172. package/dist/skins/studio-hardware.js +57 -0
  173. package/dist/skins/sumi-ink.d.ts +68 -0
  174. package/dist/skins/sumi-ink.js +68 -0
  175. package/dist/skins/swiss-poster.d.ts +68 -0
  176. package/dist/skins/swiss-poster.js +68 -0
  177. package/dist/skins/tactile-atelier.d.ts +57 -0
  178. package/dist/skins/tactile-atelier.js +57 -0
  179. package/dist/skins/tokyo-transit.d.ts +68 -0
  180. package/dist/skins/tokyo-transit.js +68 -0
  181. package/dist/skins/winamp-signal.d.ts +57 -0
  182. package/dist/skins/winamp-signal.js +57 -0
  183. package/dist/skins/y2k-titanium.d.ts +57 -0
  184. package/dist/skins/y2k-titanium.js +57 -0
  185. package/dist/skins/yule-knit.d.ts +68 -0
  186. package/dist/skins/yule-knit.js +68 -0
  187. package/dist/solar-sail.css +901 -0
  188. package/dist/starters-launch.d.ts +361 -0
  189. package/dist/starters-launch.js +808 -0
  190. package/dist/studio-hardware.css +867 -0
  191. package/dist/sumi/ornament.d.ts +3 -0
  192. package/dist/sumi/ornament.js +8 -0
  193. package/dist/sumi-ink.css +733 -0
  194. package/dist/swiss/ornament.d.ts +3 -0
  195. package/dist/swiss/ornament.js +8 -0
  196. package/dist/swiss-poster.css +727 -0
  197. package/dist/tactile-atelier.css +922 -0
  198. package/dist/theme-document.schema.json +790 -0
  199. package/dist/theme-schema.d.ts +453 -0
  200. package/dist/theme-schema.js +416 -0
  201. package/dist/titanium/ornament.d.ts +3 -0
  202. package/dist/titanium/ornament.js +11 -0
  203. package/dist/tokyo-transit.css +756 -0
  204. package/dist/transit/ornament.d.ts +3 -0
  205. package/dist/transit/ornament.js +8 -0
  206. package/dist/winamp-signal.css +1048 -0
  207. package/dist/y2k-titanium.css +979 -0
  208. package/dist/yule/ornament.d.ts +3 -0
  209. package/dist/yule/ornament.js +8 -0
  210. package/dist/yule-knit.css +850 -0
  211. package/package.json +351 -3
@@ -0,0 +1,695 @@
1
+ import { boundedLeafLength } from "./organic-geometry.js";
2
+ const tau = Math.PI * 2;
3
+ const clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
4
+ const rand = (n) => {
5
+ const x = Math.sin(n * 127.13 + 39.71) * 43758.5453;
6
+ return x - Math.floor(x);
7
+ };
8
+ /**
9
+ * The frame contract every painter shares: the outline follows the card's own rounded
10
+ * rectangle, and hand-made character may waver it by at most this many pixels. Character
11
+ * comes from ornaments, texture and colour, never from bending the edge away from the
12
+ * crisp controls inside it.
13
+ */
14
+ export const maxWaver = 1.5;
15
+ function trace(ctx, p, offset = 0, waver = 0, seed = 0, phase = 0) {
16
+ const noise = Math.min(Math.abs(waver), maxWaver);
17
+ ctx.beginPath();
18
+ for (let d = 0; d < p.total; d += 3) {
19
+ const q = p.at(d), o = offset +
20
+ noise *
21
+ (Math.sin(d * 0.027 + seed) * 0.6 +
22
+ Math.sin(d * 0.081 + seed * 2 + phase) * 0.4), x = q.x + Math.cos(q.t - Math.PI / 2) * o, y = q.y + Math.sin(q.t - Math.PI / 2) * o;
23
+ if (!d)
24
+ ctx.moveTo(x, y);
25
+ else
26
+ ctx.lineTo(x, y);
27
+ }
28
+ ctx.closePath();
29
+ }
30
+ function petal(ctx, x, y, angle, l, species, colors, seed, width = 1) {
31
+ if (l < 0.25)
32
+ return;
33
+ ctx.save();
34
+ ctx.translate(x, y);
35
+ ctx.rotate(angle);
36
+ const bend = (rand(seed + 19) - 0.5) * l * 0.24;
37
+ // Shapes taken from real plants. Each is drawn in leaf space: base at the origin, tip along +x.
38
+ if (species === "ginkgo") {
39
+ // Fan leaf on a long petiole, with the notch that gives ginkgo its two lobes.
40
+ const stalk = l * 0.2, r = l * 0.62, spread = 0.78;
41
+ ctx.strokeStyle = colors.d;
42
+ ctx.lineWidth = 0.7;
43
+ ctx.beginPath();
44
+ ctx.moveTo(0, 0);
45
+ ctx.lineTo(stalk, bend * 0.2);
46
+ ctx.stroke();
47
+ ctx.translate(stalk, bend * 0.2);
48
+ ctx.beginPath();
49
+ ctx.moveTo(0, 0);
50
+ ctx.lineTo(Math.cos(-spread) * r, Math.sin(-spread) * r);
51
+ for (let j = 0; j <= 16; j++) {
52
+ const a = -spread + (spread * 2 * j) / 16, notch = Math.abs(a) < 0.1 ? 0.72 + Math.abs(a) * 2.8 : 1, wave = 1 + Math.sin(j * 2.3 + seed) * 0.035;
53
+ ctx.lineTo(Math.cos(a) * r * notch * wave, Math.sin(a) * r * notch * wave);
54
+ }
55
+ ctx.closePath();
56
+ const fan = ctx.createRadialGradient(0, 0, 0, 0, 0, r);
57
+ fan.addColorStop(0, colors.a);
58
+ fan.addColorStop(1, rand(seed + 4) > 0.5 ? colors.c : colors.b);
59
+ ctx.fillStyle = fan;
60
+ ctx.fill();
61
+ // Fine radiating veins, the ginkgo's signature.
62
+ ctx.globalAlpha = 0.35;
63
+ ctx.strokeStyle = colors.a;
64
+ ctx.lineWidth = 0.4;
65
+ for (let j = 1; j < 8; j++) {
66
+ const a = -spread + (spread * 2 * j) / 8;
67
+ ctx.beginPath();
68
+ ctx.moveTo(0, 0);
69
+ ctx.lineTo(Math.cos(a) * r * 0.9, Math.sin(a) * r * 0.9);
70
+ ctx.stroke();
71
+ }
72
+ ctx.restore();
73
+ return;
74
+ }
75
+ if (species === "maple") {
76
+ // Palmate leaf: five pointed lobes with shallow serration and veins to each tip.
77
+ const stalk = l * 0.14, R = l * 0.55;
78
+ ctx.strokeStyle = colors.d;
79
+ ctx.lineWidth = 0.7;
80
+ ctx.beginPath();
81
+ ctx.moveTo(0, 0);
82
+ ctx.lineTo(stalk, 0);
83
+ ctx.stroke();
84
+ ctx.translate(stalk + R * 0.18, 0);
85
+ const lobes = [
86
+ [-1.35, 0.62],
87
+ [-0.68, 0.88],
88
+ [0, 1],
89
+ [0.68, 0.88],
90
+ [1.35, 0.62],
91
+ ];
92
+ ctx.beginPath();
93
+ ctx.moveTo(-R * 0.18, 0);
94
+ for (const [k, [a, len]] of lobes.entries()) {
95
+ const prev = k ? lobes[k - 1][0] : a - 0.6;
96
+ const sinus = (a + prev) / 2;
97
+ ctx.lineTo(Math.cos(sinus) * R * 0.38, Math.sin(sinus) * R * 0.38);
98
+ ctx.lineTo(Math.cos(a - 0.16) * R * len * 0.72, Math.sin(a - 0.16) * R * len * 0.72);
99
+ ctx.lineTo(Math.cos(a) * R * len, Math.sin(a) * R * len);
100
+ ctx.lineTo(Math.cos(a + 0.16) * R * len * 0.72, Math.sin(a + 0.16) * R * len * 0.72);
101
+ }
102
+ ctx.lineTo(Math.cos(1.95) * R * 0.32, Math.sin(1.95) * R * 0.32);
103
+ ctx.closePath();
104
+ const g = ctx.createLinearGradient(-R * 0.2, -R, R, R);
105
+ g.addColorStop(0, colors.b);
106
+ g.addColorStop(1, colors.a);
107
+ ctx.fillStyle = g;
108
+ ctx.fill();
109
+ ctx.globalAlpha = 0.45;
110
+ ctx.strokeStyle = colors.c;
111
+ ctx.lineWidth = 0.45;
112
+ for (const [a, len] of lobes) {
113
+ ctx.beginPath();
114
+ ctx.moveTo(-R * 0.12, 0);
115
+ ctx.lineTo(Math.cos(a) * R * len * 0.9, Math.sin(a) * R * len * 0.9);
116
+ ctx.stroke();
117
+ }
118
+ ctx.restore();
119
+ return;
120
+ }
121
+ if (species === "eucalyptus") {
122
+ // Silver-dollar sprig: round coin leaves in opposite pairs, shrinking toward the tip.
123
+ ctx.strokeStyle = colors.d;
124
+ ctx.lineWidth = 0.6;
125
+ ctx.beginPath();
126
+ ctx.moveTo(0, 0);
127
+ ctx.quadraticCurveTo(l * 0.5, bend, l, bend * 0.4);
128
+ ctx.stroke();
129
+ for (let j = 0; j < 4; j++) {
130
+ const u = 0.18 + j * 0.24, r = l * (0.17 - j * 0.03), y = bend * Math.sin(u * Math.PI) * 0.5;
131
+ for (const sign of j === 3 ? [0] : [-1, 1]) {
132
+ ctx.beginPath();
133
+ ctx.ellipse(l * u, y + sign * r * 0.95, r, r * 0.92, 0, 0, tau);
134
+ ctx.fillStyle = (j + sign) % 2 ? colors.c : colors.b;
135
+ ctx.globalAlpha = 0.92;
136
+ ctx.fill();
137
+ }
138
+ }
139
+ ctx.restore();
140
+ return;
141
+ }
142
+ if (species === "clover") {
143
+ // Trefoil: three heart-shaped leaflets, each with the pale chevron clover carries.
144
+ ctx.strokeStyle = colors.d;
145
+ ctx.lineWidth = 0.6;
146
+ ctx.beginPath();
147
+ ctx.moveTo(0, 0);
148
+ ctx.lineTo(l * 0.16, bend * 0.1);
149
+ ctx.stroke();
150
+ ctx.translate(l * 0.16, bend * 0.1);
151
+ const r = l * 0.26;
152
+ for (const a of [-1.05, 0, 1.05]) {
153
+ ctx.save();
154
+ ctx.rotate(a);
155
+ ctx.beginPath();
156
+ ctx.moveTo(0, 0);
157
+ ctx.bezierCurveTo(r * 0.2, -r * 1.1, r * 1.5, -r * 0.9, r * 1.25, -r * 0.05);
158
+ ctx.lineTo(r * 1.1, 0);
159
+ ctx.lineTo(r * 1.25, r * 0.05);
160
+ ctx.bezierCurveTo(r * 1.5, r * 0.9, r * 0.2, r * 1.1, 0, 0);
161
+ ctx.fillStyle = colors.b;
162
+ ctx.fill();
163
+ ctx.strokeStyle = colors.a;
164
+ ctx.lineWidth = 0.6;
165
+ ctx.stroke();
166
+ ctx.strokeStyle = colors.c;
167
+ ctx.globalAlpha = 0.55;
168
+ ctx.lineWidth = 0.5;
169
+ ctx.beginPath();
170
+ ctx.moveTo(r * 0.45, -r * 0.35);
171
+ ctx.lineTo(r * 0.75, 0);
172
+ ctx.lineTo(r * 0.45, r * 0.35);
173
+ ctx.stroke();
174
+ ctx.globalAlpha = 1;
175
+ ctx.restore();
176
+ }
177
+ ctx.restore();
178
+ return;
179
+ }
180
+ if (species === "blossom") {
181
+ // Five-petal wildflower (dog rose / bramble): cream petals, warm centre.
182
+ const r = l * 0.32;
183
+ ctx.translate(l * 0.5, 0);
184
+ for (let j = 0; j < 5; j++) {
185
+ ctx.save();
186
+ ctx.rotate((tau * j) / 5 + seed);
187
+ ctx.beginPath();
188
+ ctx.ellipse(r * 0.62, 0, r * 0.62, r * 0.42, 0, 0, tau);
189
+ ctx.fillStyle = "rgba(255,250,238,.94)";
190
+ ctx.fill();
191
+ ctx.strokeStyle = colors.b;
192
+ ctx.globalAlpha = 0.5;
193
+ ctx.lineWidth = 0.4;
194
+ ctx.stroke();
195
+ ctx.restore();
196
+ }
197
+ ctx.beginPath();
198
+ ctx.arc(0, 0, r * 0.28, 0, tau);
199
+ ctx.fillStyle = "#d9a441";
200
+ ctx.fill();
201
+ ctx.restore();
202
+ return;
203
+ }
204
+ if (species === "berry") {
205
+ // A small hanging cluster, like rowan or elder.
206
+ ctx.strokeStyle = colors.d;
207
+ ctx.lineWidth = 0.5;
208
+ const r = Math.max(1.4, l * 0.11);
209
+ for (const [u, v] of [
210
+ [0.55, -0.12],
211
+ [0.7, 0.1],
212
+ [0.62, 0.32],
213
+ [0.82, -0.05],
214
+ ]) {
215
+ ctx.beginPath();
216
+ ctx.moveTo(0, 0);
217
+ ctx.lineTo(l * u, l * v);
218
+ ctx.stroke();
219
+ ctx.beginPath();
220
+ ctx.arc(l * u, l * v, r, 0, tau);
221
+ ctx.fillStyle = "#a8473a";
222
+ ctx.fill();
223
+ ctx.beginPath();
224
+ ctx.arc(l * u - r * 0.35, l * v - r * 0.35, r * 0.3, 0, tau);
225
+ ctx.fillStyle = "rgba(255,255,255,.45)";
226
+ ctx.fill();
227
+ }
228
+ ctx.restore();
229
+ return;
230
+ }
231
+ if (species === "fern") {
232
+ ctx.strokeStyle = colors.c;
233
+ ctx.lineWidth = 0.7;
234
+ ctx.beginPath();
235
+ ctx.moveTo(0, 0);
236
+ ctx.quadraticCurveTo(l * 0.6, bend, l, bend * 0.5);
237
+ ctx.stroke();
238
+ for (let j = 1; j < 7; j++) {
239
+ const u = j / 8, leafL = l * (1 - u) * 0.43;
240
+ for (const sign of [-1, 1])
241
+ petal(ctx, l * u, bend * Math.sin(u * Math.PI) * 0.6, sign * (0.75 + 0.25 * u), leafL, "willow", colors, seed + j + sign, 0.8);
242
+ }
243
+ ctx.restore();
244
+ return;
245
+ }
246
+ const widths = {
247
+ ivy: 0.38,
248
+ oak: 0.32,
249
+ birch: 0.35,
250
+ willow: 0.16,
251
+ bud: 0.22,
252
+ olive: 0.13,
253
+ }, w = l * (widths[species] || 0.3) * width;
254
+ const gradient = ctx.createLinearGradient(0, -w, l * 0.25, w);
255
+ if (species === "olive") {
256
+ // Olive leaves show a silvery underside along one edge.
257
+ gradient.addColorStop(0, colors.c);
258
+ gradient.addColorStop(0.5, colors.c);
259
+ gradient.addColorStop(0.52, colors.a);
260
+ gradient.addColorStop(1, colors.a);
261
+ }
262
+ else {
263
+ gradient.addColorStop(0, colors.b);
264
+ gradient.addColorStop(0.42, rand(seed + 4) > 0.65 ? colors.c : colors.b);
265
+ gradient.addColorStop(1, colors.a);
266
+ }
267
+ ctx.beginPath();
268
+ ctx.moveTo(0, 0);
269
+ if (species === "ivy") {
270
+ ctx.bezierCurveTo(l * 0.07, -w * 0.7, l * 0.2, -w * 1.15, l * 0.39, -w * 0.72);
271
+ ctx.quadraticCurveTo(l * 0.36, -w * 1.65, l * 0.59, -w * 0.8);
272
+ ctx.quadraticCurveTo(l * 0.84, -w * 0.9, l, bend);
273
+ ctx.quadraticCurveTo(l * 0.77, w * 0.6, l * 0.55, w * 0.8);
274
+ ctx.quadraticCurveTo(l * 0.38, w * 1.65, l * 0.35, w * 0.66);
275
+ ctx.bezierCurveTo(l * 0.12, w * 1.1, l * 0.05, w * 0.5, 0, 0);
276
+ }
277
+ else {
278
+ for (let side = -1; side <= 1; side += 2) {
279
+ for (let j = side === -1 ? 1 : 24; side === -1 ? j <= 24 : j >= 0; j += side === -1 ? 1 : -1) {
280
+ const u = j / 24;
281
+ let bulge = Math.sin(Math.PI * u);
282
+ if (species === "oak")
283
+ bulge *= 0.68 + 0.32 * Math.cos(u * Math.PI * 9);
284
+ if (species === "birch")
285
+ bulge *= 1 + (j % 2 ? -0.08 : 0.08);
286
+ ctx.lineTo(u * l, Math.sin(u * Math.PI) * bend +
287
+ side * w * bulge * (1 + 0.08 * Math.sin(seed + j)));
288
+ }
289
+ }
290
+ }
291
+ ctx.closePath();
292
+ ctx.shadowColor = "rgba(0,0,0,.12)";
293
+ ctx.shadowBlur = 1.5;
294
+ ctx.shadowOffsetY = 1;
295
+ ctx.fillStyle = gradient;
296
+ ctx.fill();
297
+ ctx.shadowBlur = 0;
298
+ ctx.shadowOffsetY = 0;
299
+ ctx.globalAlpha = 0.52;
300
+ ctx.strokeStyle = colors.c;
301
+ ctx.lineWidth = 0.55;
302
+ ctx.beginPath();
303
+ ctx.moveTo(0, 0);
304
+ ctx.quadraticCurveTo(l * 0.55, bend, l * 0.94, bend * 0.15);
305
+ ctx.stroke();
306
+ for (let j = 1; j <= 4; j++) {
307
+ const u = j / 6;
308
+ ctx.beginPath();
309
+ ctx.moveTo(l * u, bend * Math.sin(u * Math.PI) * 0.4);
310
+ ctx.lineTo(l * (u + 0.14), -w * Math.sin(u * Math.PI) * 0.7 + bend * 0.3);
311
+ ctx.moveTo(l * u, bend * Math.sin(u * Math.PI) * 0.4);
312
+ ctx.lineTo(l * (u + 0.1), w * Math.sin(u * Math.PI) * 0.66 + bend * 0.3);
313
+ ctx.stroke();
314
+ }
315
+ ctx.restore();
316
+ }
317
+ function paintState(settings) {
318
+ return {
319
+ amount: settings.density,
320
+ corners: settings.cornerDensity,
321
+ drape: settings.canopyDepth,
322
+ wild: settings.variation,
323
+ scale: settings.scale,
324
+ weight: settings.stemWeight,
325
+ motion: settings.motion,
326
+ seed: settings.seed,
327
+ style: settings.style,
328
+ canopy: settings.canopy,
329
+ stem: settings.stem,
330
+ };
331
+ }
332
+ function anchor(p, i, seed, state) {
333
+ const clustered = rand(seed + 1) < (state.corners / 100) * 0.72;
334
+ const base = clustered
335
+ ? p.corners[i % 4] + (rand(seed + 2) - 0.5) * (65 + state.wild * 0.6)
336
+ : ((i * 0.61803398875 + rand(state.seed + 1) * 0.18) % 1) * p.total;
337
+ return p.at(base);
338
+ }
339
+ function bead(ctx, x, y, rx, ry, angle, fill, colors, glass = false) {
340
+ ctx.save();
341
+ ctx.translate(x, y);
342
+ ctx.rotate(angle);
343
+ const g = ctx.createRadialGradient(-rx * 0.25, -ry * 0.4, 1, 0, 0, Math.max(rx, ry));
344
+ g.addColorStop(0, colors.paper);
345
+ g.addColorStop(glass ? 0.25 : 0.1, fill);
346
+ g.addColorStop(1, glass ? colors.a : fill);
347
+ ctx.globalAlpha = glass ? 0.7 : 1;
348
+ ctx.shadowColor = "rgba(0,0,0,.12)";
349
+ ctx.shadowBlur = glass ? 4 : 2;
350
+ ctx.shadowOffsetY = 2;
351
+ ctx.beginPath();
352
+ ctx.ellipse(0, 0, rx, ry, 0, 0, tau);
353
+ ctx.fillStyle = g;
354
+ ctx.fill();
355
+ ctx.shadowBlur = 0;
356
+ ctx.shadowOffsetY = 0;
357
+ ctx.beginPath();
358
+ ctx.ellipse(-rx * 0.2, -ry * 0.4, rx * 0.38, ry * 0.12, -0.2, 0, tau);
359
+ ctx.fillStyle = colors.paper;
360
+ ctx.globalAlpha = 0.55;
361
+ ctx.fill();
362
+ ctx.restore();
363
+ }
364
+ export const paintForest = (ctx, p, w, h, settings, colors, clock, playing, index, limits, shown = settings.density) => {
365
+ const state = paintState(settings);
366
+ const tender = state.stem === "Tender shoots", woody = state.stem === "Woody branches", mossy = state.stem === "Mossy roots";
367
+ const weight = (1.15 + state.weight * 0.035) *
368
+ (tender ? 0.5 : woody ? 1.5 : mossy ? 1.8 : 1), vari = state.wild / 100, seed = state.seed + index * 719;
369
+ trace(ctx, p, -0.5, vari * (woody ? 3.2 : 2.2), seed);
370
+ ctx.strokeStyle = tender ? colors.b : colors.d;
371
+ ctx.lineWidth = weight + (tender ? 0.2 : 1.2);
372
+ ctx.stroke();
373
+ if (!tender) {
374
+ trace(ctx, p, 1.3, vari * 2.4, seed + 1);
375
+ ctx.strokeStyle = colors.b;
376
+ ctx.lineWidth = weight * 0.6;
377
+ ctx.stroke();
378
+ }
379
+ trace(ctx, p, 0, vari, seed);
380
+ ctx.strokeStyle = colors.a;
381
+ ctx.lineWidth = 0.65;
382
+ ctx.stroke();
383
+ if (woody || mossy)
384
+ for (let j = 0; j < p.total; j += mossy ? 7 : 15) {
385
+ const q = p.at(j);
386
+ if (mossy) {
387
+ ctx.globalAlpha = 0.65;
388
+ ctx.beginPath();
389
+ ctx.ellipse(q.x + Math.sin(j) * 2, q.y + Math.cos(j) * 2, 1.3 + rand(j) * 2, 0.9, q.t, 0, tau);
390
+ ctx.fillStyle = rand(j) > 0.6 ? colors.b : colors.a;
391
+ ctx.fill();
392
+ ctx.globalAlpha = 1;
393
+ }
394
+ else {
395
+ ctx.beginPath();
396
+ ctx.moveTo(q.x - Math.cos(q.t + 1) * 2, q.y - Math.sin(q.t + 1) * 2);
397
+ ctx.lineTo(q.x + Math.cos(q.t + 1) * 2, q.y + Math.sin(q.t + 1) * 2);
398
+ ctx.strokeStyle = colors.d;
399
+ ctx.lineWidth = 0.6;
400
+ ctx.stroke();
401
+ }
402
+ }
403
+ const maxCanopy = Math.max(0, limits.top - state.scale - 6), depth = Math.min(state.drape * 0.48, maxCanopy), stems = [], stemCount = state.canopy === "Open crown"
404
+ ? 2
405
+ : state.canopy === "Corner cascades"
406
+ ? 4
407
+ : state.canopy === "Long tendrils"
408
+ ? 8
409
+ : state.canopy === "Overhead boughs"
410
+ ? 3
411
+ : 6;
412
+ for (let k = 0; k < stemCount; k++) {
413
+ let fraction = (k + 0.45) / stemCount + (rand(seed + k) - 0.5) * 0.08;
414
+ if (state.canopy === "Corner cascades")
415
+ fraction = [0.02, 0.13, 0.87, 0.98][k];
416
+ if (state.canopy === "Open crown")
417
+ fraction = k ? 0.97 : 0.03;
418
+ const x = p.r + 8 + (w - 2 * p.r - 16) * fraction, len = Math.min(maxCanopy, (state.canopy === "Long tendrils" ? 20 : 8) +
419
+ depth * (0.35 + rand(seed + k + 4) * 0.65));
420
+ const bend = (rand(seed + k + 9) - 0.5) *
421
+ (state.canopy === "Overhead boughs"
422
+ ? 64
423
+ : state.canopy === "Long tendrils"
424
+ ? 10
425
+ : 22);
426
+ stems.push({ x, len, bend });
427
+ if (state.drape > 0 && shown > 0) {
428
+ // One gentle arc, like a stem hanging under its own weight (no S-wiggle).
429
+ ctx.beginPath();
430
+ ctx.moveTo(x, 0);
431
+ ctx.bezierCurveTo(x + bend * 0.3, len * 0.3, x + bend, len * 0.65, x + bend * 0.8, len);
432
+ ctx.lineCap = "round";
433
+ ctx.strokeStyle = colors.d;
434
+ ctx.lineWidth = weight * 0.42;
435
+ ctx.stroke();
436
+ }
437
+ }
438
+ const total = clamp(shown / 100) * 160;
439
+ const mixes = {
440
+ "Woodland mix": [
441
+ "ivy",
442
+ "birch",
443
+ "oak",
444
+ "willow",
445
+ "fern",
446
+ "ivy",
447
+ "birch",
448
+ "bud",
449
+ ],
450
+ "Wild ivy": ["ivy", "ivy", "ivy", "birch", "bud"],
451
+ "Fern grove": ["fern", "fern", "birch", "willow"],
452
+ "Willow canopy": ["willow", "willow", "willow", "birch", "bud"],
453
+ "Oak & moss": ["oak", "oak", "fern", "birch"],
454
+ "Spring growth": ["bud", "birch", "ivy", "bud", "willow"],
455
+ "Ginkgo fans": ["ginkgo", "ginkgo", "ginkgo", "bud"],
456
+ "Maple grove": ["maple", "maple", "birch", "maple", "bud"],
457
+ Eucalyptus: ["eucalyptus", "eucalyptus", "willow"],
458
+ "Olive branch": ["olive", "olive", "olive", "willow", "berry"],
459
+ "Clover meadow": ["clover", "clover", "blossom", "fern", "clover"],
460
+ "Wildflower hedge": [
461
+ "ivy",
462
+ "blossom",
463
+ "birch",
464
+ "berry",
465
+ "ivy",
466
+ "fern",
467
+ "blossom",
468
+ "clover",
469
+ ],
470
+ }, mix = mixes[state.style] ?? mixes["Woodland mix"];
471
+ for (let i = 0; i < Math.ceil(total); i++) {
472
+ const s = seed + i * 31.23, grow = clamp(total - i);
473
+ let q = anchor(p, i, s, state), tipX, tipY, angle;
474
+ if (rand(s + 3) <
475
+ (state.drape / 100) *
476
+ (state.canopy === "Open crown"
477
+ ? 0.14
478
+ : state.canopy === "Overhead boughs"
479
+ ? 0.48
480
+ : 0.32)) {
481
+ const st = stems[i % stems.length], u = 0.2 + rand(s + 9) * 0.8, v = 1 - u;
482
+ // Point and tangent on the hanging stem's actual bezier, so leaves sit on it.
483
+ const bx = [
484
+ st.x,
485
+ st.x + st.bend * 0.3,
486
+ st.x + st.bend,
487
+ st.x + st.bend * 0.8,
488
+ ], by = [0, st.len * 0.3, st.len * 0.65, st.len];
489
+ const at = (c) => v * v * v * c[0] +
490
+ 3 * v * v * u * c[1] +
491
+ 3 * v * u * u * c[2] +
492
+ u * u * u * c[3], slope = (c) => 3 * v * v * (c[1] - c[0]) +
493
+ 6 * v * u * (c[2] - c[1]) +
494
+ 3 * u * u * (c[3] - c[2]);
495
+ const t = Math.atan2(slope(by), slope(bx));
496
+ q = { x: at(bx), y: at(by), t };
497
+ // Leaves angle down-and-out from the stem, alternating sides like real nodes.
498
+ angle = t + (i % 2 ? 1 : -1) * (0.55 + rand(s + 7) * 0.35);
499
+ tipX = q.x;
500
+ tipY = q.y;
501
+ }
502
+ else {
503
+ // A real side shoot leaves the vine at a slant, in the vine's growth direction,
504
+ // then arcs outward. The leaf continues the twig's last direction, so there is no kink.
505
+ const side = rand(s + 5) > 0.24 ? -1 : 1, lean = (rand(s + 8) > 0.5 ? 1 : -1) * (0.35 + rand(s + 6) * 0.5), out = q.t + side * (Math.PI / 2 - lean), start = q.t + side * (Math.PI / 2 - lean * 1.7), twig = (2.5 + rand(s + 7) * 6) * (0.6 + vari * 0.5);
506
+ tipX = q.x + Math.cos(out) * twig;
507
+ tipY = q.y + Math.sin(out) * twig;
508
+ const cx = q.x + Math.cos(start) * twig * 0.55, cy = q.y + Math.sin(start) * twig * 0.55;
509
+ angle = Math.atan2(tipY - cy, tipX - cx) + (rand(s + 4) - 0.5) * 0.3;
510
+ ctx.beginPath();
511
+ ctx.moveTo(q.x, q.y);
512
+ ctx.quadraticCurveTo(cx, cy, tipX, tipY);
513
+ ctx.strokeStyle = colors.d;
514
+ ctx.lineCap = "round";
515
+ ctx.lineWidth = 0.5 + rand(s) * 0.35;
516
+ ctx.stroke();
517
+ }
518
+ const sway = ((Math.sin(clock * 1.25 + s * 0.08) * 0.14 +
519
+ Math.sin(clock * 0.72 + i) * 0.06) *
520
+ state.motion) /
521
+ 100;
522
+ angle += sway;
523
+ let len = state.scale * (0.53 + rand(s + 12) * 0.83) * grow, form = mix[Math.floor(rand(s + 20) * mix.length)];
524
+ if (form === "fern")
525
+ len *= 1.3;
526
+ if (form === "bud")
527
+ len *= 0.58;
528
+ if (form === "eucalyptus" || form === "ginkgo")
529
+ len *= 1.15;
530
+ if (form === "blossom" || form === "berry")
531
+ len *= 0.7;
532
+ len = boundedLeafLength(tipX, tipY, angle, len, w, h);
533
+ const dx = Math.abs(Math.cos(angle)) + 0.45 * Math.abs(Math.sin(angle)), dy = Math.abs(Math.sin(angle)) + 0.45 * Math.abs(Math.cos(angle));
534
+ const innerX = tipX < limits.inline
535
+ ? limits.inline - tipX
536
+ : Math.max(0, tipX - (w - limits.inline));
537
+ const innerY = tipY < limits.top
538
+ ? limits.top - tipY
539
+ : Math.max(0, tipY - (h - limits.bottom));
540
+ len = Math.min(len, Math.max(innerX / dx, innerY / dy));
541
+ petal(ctx, tipX, tipY, angle, len, form, colors, s, 0.85 + rand(s + 23) * 0.3);
542
+ if (state.style === "Spring growth" && i % 17 === 0) {
543
+ ctx.beginPath();
544
+ ctx.arc(tipX, tipY, 2, 0, tau);
545
+ ctx.fillStyle = colors.c;
546
+ ctx.fill();
547
+ }
548
+ }
549
+ };
550
+ export const paintClay = (ctx, p, w, h, settings, colors, clock, playing, index, limits, shown = settings.density) => {
551
+ const state = paintState(settings);
552
+ const seed = state.seed + index * 719, twist = state.style === "Twisted rope", thick = 5 + state.weight * 0.11, phase = clock * state.motion * 0.008;
553
+ trace(ctx, p, -1, 1 + state.wild * 0.025, seed, phase);
554
+ ctx.strokeStyle = colors.b;
555
+ ctx.lineWidth = thick + 5;
556
+ ctx.lineJoin = "round";
557
+ ctx.stroke();
558
+ trace(ctx, p, 2, 1 + state.wild * 0.025, seed, phase);
559
+ ctx.strokeStyle = colors.c;
560
+ ctx.lineWidth = thick * 0.55;
561
+ ctx.stroke();
562
+ trace(ctx, p, 3, 1 + state.wild * 0.02, seed, phase);
563
+ ctx.strokeStyle = colors.paper;
564
+ ctx.lineWidth = 1.2;
565
+ ctx.globalAlpha = 0.65;
566
+ ctx.stroke();
567
+ ctx.globalAlpha = 1;
568
+ const count = shown * (twist ? 1.2 : 0.65);
569
+ for (let i = 0; i < Math.ceil(count); i++) {
570
+ const s = seed + i * 29, q = twist
571
+ ? p.at((i / Math.max(1, count)) * p.total)
572
+ : anchor(p, i, s, state), wave = ((Math.sin(clock * 2 + i * 0.8) * state.motion) / 100) * 0.08, squish = 1 + ((Math.sin(clock * 1.8 + i * 0.6) * state.drape) / 100) * 0.08, l = state.scale * (0.28 + rand(s + 3) * 0.35) * clamp(count - i), fill = [colors.a, colors.b, colors.c, colors.d][i % 4];
573
+ if (state.style === "Piped petals" &&
574
+ rand(s + 2) < (state.corners / 100) * 0.6) {
575
+ for (let j = 0; j < 5; j++) {
576
+ const a = (j * tau) / 5 + q.t;
577
+ bead(ctx, q.x + Math.cos(a) * l * 0.5, q.y + Math.sin(a) * l * 0.5, l * 0.55, l * 0.3, a, fill, colors);
578
+ }
579
+ bead(ctx, q.x, q.y, l * 0.26, l * 0.26, 0, colors.d, colors);
580
+ }
581
+ else if (state.style === "Ribbon folds") {
582
+ ctx.save();
583
+ ctx.translate(q.x, q.y);
584
+ ctx.rotate(q.t + wave);
585
+ ctx.beginPath();
586
+ ctx.moveTo(-l, -l * 0.5);
587
+ ctx.bezierCurveTo(0, l * 0.8, 0, -l, l, l * 0.5);
588
+ ctx.lineTo(l, l);
589
+ ctx.bezierCurveTo(0, -l * 0.5, 0, l * 1.3, -l, 0);
590
+ ctx.closePath();
591
+ ctx.fillStyle = fill;
592
+ ctx.fill();
593
+ ctx.restore();
594
+ }
595
+ else
596
+ bead(ctx, q.x, q.y, l * (twist ? 1.15 : 1) * squish, (l * (twist ? 0.55 : 0.8)) / squish, q.t + (twist ? 0.6 : rand(s) * 2) + wave, fill, colors);
597
+ }
598
+ };
599
+ export const paintGlass = (ctx, p, w, h, settings, colors, clock, playing, index, limits, shown = settings.density) => {
600
+ const state = paintState(settings);
601
+ const seed = state.seed + index * 719, phase = clock * state.motion * 0.012, quiet = state.style === "Quiet currents", layers = quiet ? 3 : 5;
602
+ // Strands share one current so they stay parallel, like contour lines, instead of crossing.
603
+ for (let j = 0; j < layers; j++) {
604
+ trace(ctx, p, (j - (layers - 1) / 2) * 1.6, 0.4 + state.drape * 0.015, seed, phase + j * 0.15);
605
+ ctx.strokeStyle = [colors.a, colors.b, colors.c, colors.d, colors.paper][j];
606
+ ctx.lineWidth = 2 + state.weight * 0.035;
607
+ ctx.globalAlpha = 0.4 + j * 0.08;
608
+ ctx.stroke();
609
+ }
610
+ ctx.globalAlpha = 1;
611
+ const count = shown * (quiet ? 0.12 : state.style === "Seafoam beads" ? 0.9 : 0.45);
612
+ for (let i = 0; i < Math.ceil(count); i++) {
613
+ const s = seed + i * 37, q = anchor(p, i, s, state), move = Math.sin(clock * 0.8 + i) * state.motion * 0.012, n = q.t - Math.PI / 2, spread = (rand(s + 5) - 0.5) * state.drape * 0.05, l = state.scale * (0.16 + rand(s + 3) * 0.28) * clamp(count - i);
614
+ bead(ctx, q.x + Math.cos(n) * (spread + move), q.y + Math.sin(n) * (spread + move), l, l * (state.style === "Glass droplets" ? 1.4 : 0.8), q.t, [colors.b, colors.c, colors.d][i % 3], colors, true);
615
+ }
616
+ };
617
+ export const paintBrass = (ctx, p, w, h, settings, colors, clock, playing, index, limits, shown = settings.density) => {
618
+ const state = paintState(settings);
619
+ const seed = state.seed + index * 719, weight = 1.1 + state.weight * 0.055;
620
+ trace(ctx, p, 0, 0.2 + state.wild * 0.005, seed);
621
+ const g = ctx.createLinearGradient(0, 0, w, h);
622
+ g.addColorStop(0, colors.c);
623
+ g.addColorStop(0.28, colors.a);
624
+ g.addColorStop(0.6, colors.b);
625
+ g.addColorStop(1, colors.d);
626
+ ctx.shadowColor = colors.d;
627
+ ctx.shadowOffsetY = state.drape * 0.02;
628
+ ctx.shadowBlur = state.drape * 0.025;
629
+ ctx.strokeStyle = g;
630
+ ctx.lineWidth = weight;
631
+ ctx.stroke();
632
+ ctx.shadowOffsetY = 0;
633
+ ctx.shadowBlur = 0;
634
+ trace(ctx, p, -4);
635
+ ctx.strokeStyle = colors.b;
636
+ ctx.lineWidth = 0.65;
637
+ ctx.stroke();
638
+ const quiet = state.style === "Quiet gilding", count = shown * (quiet ? 0.15 : 0.8);
639
+ for (let i = 0; i < Math.ceil(count); i++) {
640
+ const s = seed + i * 41, q = anchor(p, i, s, state), n = q.t - Math.PI / 2, l = state.scale * (0.12 + rand(s) * 0.17) * clamp(count - i);
641
+ ctx.save();
642
+ ctx.translate(q.x, q.y);
643
+ ctx.rotate(q.t);
644
+ ctx.strokeStyle = rand(s + 3) > 0.6 ? colors.c : colors.a;
645
+ ctx.lineWidth = 0.7;
646
+ if (state.style === "Art deco") {
647
+ ctx.beginPath();
648
+ ctx.moveTo(-l, 0);
649
+ ctx.lineTo(-l, -l);
650
+ ctx.lineTo(l, -l);
651
+ ctx.lineTo(l, 0);
652
+ ctx.stroke();
653
+ }
654
+ else if (state.style === "Hammered edge")
655
+ bead(ctx, 0, 0, l * 0.35, l * 0.5, rand(s) * tau, colors.b, colors);
656
+ else {
657
+ ctx.beginPath();
658
+ ctx.ellipse(0, -l * 0.35, l, l * 0.55, -0.45, 0, tau);
659
+ ctx.stroke();
660
+ }
661
+ ctx.restore();
662
+ }
663
+ if (!quiet)
664
+ for (let j = 0; j < 4; j++) {
665
+ const q = p.at(p.corners[j]), a = q.t + Math.PI / 2, sz = 7 + state.corners * 0.18;
666
+ ctx.save();
667
+ ctx.translate(q.x, q.y);
668
+ ctx.rotate(a);
669
+ ctx.strokeStyle = colors.b;
670
+ ctx.lineWidth = 0.85;
671
+ ctx.beginPath();
672
+ for (let k = 0; k < 80; k++) {
673
+ const t = (k / 80) * tau * 1.4, rad = (1 - k / 90) * sz * 0.48;
674
+ const x = Math.cos(t) * rad, y = Math.sin(t) * rad;
675
+ if (!k)
676
+ ctx.moveTo(x, y);
677
+ else
678
+ ctx.lineTo(x, y);
679
+ }
680
+ ctx.stroke();
681
+ ctx.restore();
682
+ }
683
+ if (playing && state.motion > 0) {
684
+ const q = p.at(((clock * 0.025) % 1) * p.total);
685
+ const shine = ctx.createRadialGradient(q.x, q.y, 0, q.x, q.y, 14);
686
+ shine.addColorStop(0, colors.c);
687
+ shine.addColorStop(1, "rgba(0,0,0,0)");
688
+ ctx.fillStyle = shine;
689
+ ctx.globalAlpha = 0.65;
690
+ ctx.fillRect(q.x - 14, q.y - 14, 28, 28);
691
+ ctx.globalAlpha = 1;
692
+ }
693
+ };
694
+ /** Shared drawing primitives for painters in other modules (organic-paint-world.ts). */
695
+ export { tau, clamp, rand, trace, anchor, paintState, bead };
@@ -0,0 +1,4 @@
1
+ import { type OrganicPainter } from "./organic-paint.ts";
2
+ import { type OrganicFrame } from "./organic-settings.ts";
3
+ /** Mount only after hydration. Every observer, listener and queued paint is released on disposal. */
4
+ export declare function mountOrganicCanvas(canvas: HTMLCanvasElement, kind: OrganicFrame, painter: OrganicPainter, instance: number, animate: boolean, motion: string): () => void;