sliccy 1.22.2 → 1.22.4

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 (24) hide show
  1. package/dist/node-server/electron-controller.js +15 -6
  2. package/dist/node-server/electron-runtime.js +9 -10
  3. package/dist/node-server/index.js +6 -4
  4. package/dist/ui/assets/{___vite-browser-external_commonjs-proxy-BaP7fEvT.js → ___vite-browser-external_commonjs-proxy-CdTWn1k_.js} +1 -1
  5. package/dist/ui/assets/{bsh-watchdog-Cm3Aj_S6.js → bsh-watchdog-D42k4Edm.js} +1 -1
  6. package/dist/ui/assets/{index-BnDoetcE.js → index-B0xTpQYL.js} +1 -1
  7. package/dist/ui/assets/index-BfNzQpoL.js +131 -0
  8. package/dist/ui/assets/index-BlSQnyL2.css +1 -0
  9. package/dist/ui/assets/{index-B5Pp2r00.js → index-C0eFsJDV.js} +1 -1
  10. package/dist/ui/assets/{index-Xfgq0sxD.js → index-CCybN7e8.js} +1033 -718
  11. package/dist/ui/assets/{index-DlCxPcMj.js → index-CbC7FGzC.js} +1 -1
  12. package/dist/ui/assets/{index-i3ABK3vk.js → index-CsOERWYO.js} +1 -1
  13. package/dist/ui/assets/{index-tzzI-w2Q.js → index-D54P7vsh.js} +1 -1
  14. package/dist/ui/assets/{index-CTwqCjQD.js → index-DjKjcXeW.js} +1 -1
  15. package/dist/ui/assets/{index-DzGPKzGT.js → index-pQ08_oTm.js} +9 -9
  16. package/dist/ui/assets/{offscreen-client-CO5fJLap.js → offscreen-client-DmhRES2I.js} +1 -1
  17. package/dist/ui/assets/{sql-wasm-CgIJhlRQ.js → sql-wasm-Vb3Bc41A.js} +1 -1
  18. package/dist/ui/index.html +2 -2
  19. package/dist/ui/logos/logo-editor.html +944 -839
  20. package/dist/ui/logos/macos-icon.icon/icon.json +19 -24
  21. package/dist/ui/packages/webapp/index.html +2 -2
  22. package/package.json +20 -25
  23. package/dist/ui/assets/index-Cj6gFnz_.js +0 -131
  24. package/dist/ui/assets/index-D2mLc9tI.css +0 -1
@@ -1,805 +1,747 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Sliccy Logo Editor</title>
7
- <style>
8
- * { box-sizing: border-box; margin: 0; padding: 0; }
9
- body {
10
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11
- display: flex;
12
- min-height: 100vh;
13
- transition: background-color 0.3s, color 0.3s;
14
- }
15
- body.light { background: #f5f5f5; color: #333; }
16
- body.dark { background: #1a1a1a; color: #eee; }
17
-
18
- .controls {
19
- width: 320px;
20
- padding: 20px;
21
- overflow-y: auto;
22
- border-right: 1px solid #ccc;
23
- }
24
- body.dark .controls { border-color: #444; }
25
-
26
- .preview {
27
- flex: 1;
28
- display: flex;
29
- align-items: center;
30
- justify-content: center;
31
- padding: 20px;
32
- }
33
-
34
- .logo-variants {
35
- display: flex;
36
- gap: 20px;
37
- align-items: flex-start;
38
- }
39
- .logo-variant {
40
- display: flex;
41
- flex-direction: column;
42
- align-items: center;
43
- }
44
- .logo-variant-label {
45
- font-size: 0.85em;
46
- margin-bottom: 8px;
47
- opacity: 0.7;
48
- }
49
- .logo-container {
50
- width: 300px;
51
- height: 300px;
52
- position: relative;
53
- border: 1px dashed #ccc;
54
- }
55
- body.dark .logo-container {
56
- border-color: #444;
57
- }
58
- .logo-container svg {
59
- width: 100%;
60
- height: 100%;
61
- }
62
-
63
- h1 { font-size: 1.4em; margin-bottom: 20px; }
64
- h2 { font-size: 1em; margin: 20px 0 10px; border-bottom: 1px solid #ccc; padding-bottom: 5px; }
65
- body.dark h2 { border-color: #444; }
66
-
67
- .control-group { margin-bottom: 15px; }
68
- label { display: block; margin-bottom: 5px; font-size: 0.9em; }
69
- input[type="range"] { width: 100%; }
70
- input[type="number"] { width: 60px; padding: 4px; }
71
- select {
72
- width: 100%;
73
- padding: 6px;
74
- border-radius: 4px;
75
- border: 1px solid #ccc;
76
- background: #fff;
77
- color: #333;
78
- font-size: 0.9em;
79
- }
80
- body.dark select {
81
- background: #333;
82
- color: #eee;
83
- border-color: #555;
84
- }
85
-
86
- .toggle-btn {
87
- padding: 8px 16px;
88
- border: none;
89
- border-radius: 4px;
90
- cursor: pointer;
91
- font-size: 0.9em;
92
- margin-right: 8px;
93
- }
94
- body.light .toggle-btn { background: #333; color: #fff; }
95
- body.dark .toggle-btn { background: #fff; color: #333; }
96
-
97
- .value-display {
98
- font-size: 0.85em;
99
- color: #666;
100
- margin-left: 8px;
101
- }
102
- body.dark .value-display { color: #aaa; }
103
-
104
- /* Eye animations */
105
- @keyframes eyeroll {
106
- 0%, 100% { transform: translate(0, 0); }
107
- 25% { transform: translate(-15px, -10px); }
108
- 50% { transform: translate(0, -15px); }
109
- 75% { transform: translate(15px, -10px); }
110
- }
111
-
112
- .pupil-animated {
113
- animation-iteration-count: infinite;
114
- animation-timing-function: ease-in-out;
115
- }
116
- </style>
117
- </head>
118
- <body class="light">
119
- <div class="controls">
120
- <h1>🍦 Sliccy Logo Editor</h1>
121
-
122
- <div class="control-group">
123
- <label>Color Mode:</label>
124
- <select id="color-mode">
125
- <option value="mono-light">Monochrome Light</option>
126
- <option value="mono-dark">Monochrome Dark</option>
127
- <option value="color">Color</option>
128
- </select>
129
- </div>
130
-
131
- <h2>Scoops</h2>
132
- <div class="control-group">
133
- <label>Number of Scoops: <span class="value-display" id="scoop-count-val">5</span></label>
134
- <input type="range" id="scoop-count" min="0" max="10" value="5">
135
- </div>
136
-
137
- <h2>Scoop Stacking</h2>
138
- <div class="control-group">
139
- <label>Vertical Offset per Scoop: <span class="value-display" id="scoop-offset-val">95</span></label>
140
- <input type="range" id="scoop-offset" min="50" max="200" value="95">
141
- </div>
142
- <div class="control-group">
143
- <label>Scale per Additional Scoop: <span class="value-display" id="scoop-scale-val">0.95</span></label>
144
- <input type="range" id="scoop-scale" min="0.5" max="1" step="0.01" value="0.95">
145
- </div>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Sliccy Logo Editor</title>
7
+ <style>
8
+ * {
9
+ box-sizing: border-box;
10
+ margin: 0;
11
+ padding: 0;
12
+ }
13
+ body {
14
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
15
+ display: flex;
16
+ min-height: 100vh;
17
+ transition:
18
+ background-color 0.3s,
19
+ color 0.3s;
20
+ }
21
+ body.light {
22
+ background: #f5f5f5;
23
+ color: #333;
24
+ }
25
+ body.dark {
26
+ background: #1a1a1a;
27
+ color: #eee;
28
+ }
146
29
 
147
- <h2>Mid-Range Adjustments (1-9 Scoops)</h2>
148
- <div class="control-group">
149
- <label>Mid-Range Scale Factor: <span class="value-display" id="midrange-scale-val">1.0</span></label>
150
- <input type="range" id="midrange-scale" min="0.8" max="1.2" step="0.01" value="1.0">
151
- </div>
152
- <div class="control-group">
153
- <label>Mid-Range Y Offset: <span class="value-display" id="midrange-y-val">0</span></label>
154
- <input type="range" id="midrange-y" min="-200" max="200" value="0">
155
- </div>
156
- <div class="control-group">
157
- <label>Horizontal Wobble: <span class="value-display" id="scoop-wobble-val">14</span></label>
158
- <input type="range" id="scoop-wobble" min="0" max="50" value="14">
159
- </div>
160
- <div class="control-group">
161
- <label>Angular Wobble (°): <span class="value-display" id="angular-wobble-val">8</span></label>
162
- <input type="range" id="angular-wobble" min="0" max="30" value="8">
163
- </div>
164
- <div class="control-group">
165
- <button class="toggle-btn" id="randomize-wobble">🎲 Randomize Wobble</button>
166
- </div>
167
- <div class="control-group">
168
- <label>Cone Push-Down per Scoop: <span class="value-display" id="cone-push-val">-21</span></label>
169
- <input type="range" id="cone-push" min="-100" max="0" value="-21">
170
- </div>
30
+ .controls {
31
+ width: 320px;
32
+ padding: 20px;
33
+ overflow-y: auto;
34
+ border-right: 1px solid #ccc;
35
+ }
36
+ body.dark .controls {
37
+ border-color: #444;
38
+ }
171
39
 
172
- <h2>Eye Animations</h2>
173
- <div class="control-group">
174
- <label>
175
- <input type="checkbox" id="eye-animate"> Enable Eye Animations
176
- </label>
177
- </div>
178
- <div class="control-group">
179
- <label>Eyeroll Speed (s): <span class="value-display" id="eyeroll-speed-val">3</span></label>
180
- <input type="range" id="eyeroll-speed" min="0.5" max="10" step="0.5" value="3">
181
- </div>
182
- <div class="control-group">
183
- <label>Eye Distance: <span class="value-display" id="eye-distance-val">1.0</span></label>
184
- <input type="range" id="eye-distance" min="0.5" max="1.5" step="0.05" value="1.0">
185
- </div>
186
- <div class="control-group">
187
- <label>Eye Size: <span class="value-display" id="eye-size-val">1.0</span></label>
188
- <input type="range" id="eye-size" min="0.5" max="2" step="0.05" value="1.0">
189
- </div>
190
- <div class="control-group">
191
- <label>Eye Angle (°): <span class="value-display" id="eye-angle-val">0</span></label>
192
- <input type="range" id="eye-angle" min="-45" max="45" value="0">
193
- </div>
40
+ .preview {
41
+ flex: 1;
42
+ display: flex;
43
+ align-items: center;
44
+ justify-content: center;
45
+ padding: 20px;
46
+ }
194
47
 
195
- <h2>Scoop Colors</h2>
196
- <div id="color-controls-info" style="font-size: 0.85em; opacity: 0.7; margin-bottom: 10px;">
197
- Only visible in Color mode
198
- </div>
199
- <div id="color-controls"></div>
48
+ .logo-variants {
49
+ display: flex;
50
+ gap: 20px;
51
+ align-items: flex-start;
52
+ }
53
+ .logo-variant {
54
+ display: flex;
55
+ flex-direction: column;
56
+ align-items: center;
57
+ }
58
+ .logo-variant-label {
59
+ font-size: 0.85em;
60
+ margin-bottom: 8px;
61
+ opacity: 0.7;
62
+ }
63
+ .logo-container {
64
+ width: 300px;
65
+ height: 300px;
66
+ position: relative;
67
+ border: 1px dashed #ccc;
68
+ }
69
+ body.dark .logo-container {
70
+ border-color: #444;
71
+ }
72
+ .logo-container svg {
73
+ width: 100%;
74
+ height: 100%;
75
+ }
200
76
 
201
- <h2>Export</h2>
202
- <div class="control-group">
203
- <button class="toggle-btn" id="generate-variants">Generate All Variants</button>
204
- </div>
205
- <div id="export-status" style="font-size: 0.85em; margin-top: 10px;"></div>
206
- </div>
207
-
208
- <div class="preview">
209
- <div class="logo-variants">
210
- <div class="logo-variant">
211
- <div class="logo-variant-label">No Scoops</div>
212
- <div class="logo-container" id="logo-container-0"></div>
77
+ h1 {
78
+ font-size: 1.4em;
79
+ margin-bottom: 20px;
80
+ }
81
+ h2 {
82
+ font-size: 1em;
83
+ margin: 20px 0 10px;
84
+ border-bottom: 1px solid #ccc;
85
+ padding-bottom: 5px;
86
+ }
87
+ body.dark h2 {
88
+ border-color: #444;
89
+ }
90
+
91
+ .control-group {
92
+ margin-bottom: 15px;
93
+ }
94
+ label {
95
+ display: block;
96
+ margin-bottom: 5px;
97
+ font-size: 0.9em;
98
+ }
99
+ input[type='range'] {
100
+ width: 100%;
101
+ }
102
+ input[type='number'] {
103
+ width: 60px;
104
+ padding: 4px;
105
+ }
106
+ select {
107
+ width: 100%;
108
+ padding: 6px;
109
+ border-radius: 4px;
110
+ border: 1px solid #ccc;
111
+ background: #fff;
112
+ color: #333;
113
+ font-size: 0.9em;
114
+ }
115
+ body.dark select {
116
+ background: #333;
117
+ color: #eee;
118
+ border-color: #555;
119
+ }
120
+
121
+ .toggle-btn {
122
+ padding: 8px 16px;
123
+ border: none;
124
+ border-radius: 4px;
125
+ cursor: pointer;
126
+ font-size: 0.9em;
127
+ margin-right: 8px;
128
+ }
129
+ body.light .toggle-btn {
130
+ background: #333;
131
+ color: #fff;
132
+ }
133
+ body.dark .toggle-btn {
134
+ background: #fff;
135
+ color: #333;
136
+ }
137
+
138
+ .value-display {
139
+ font-size: 0.85em;
140
+ color: #666;
141
+ margin-left: 8px;
142
+ }
143
+ body.dark .value-display {
144
+ color: #aaa;
145
+ }
146
+
147
+ /* Eye animations */
148
+ @keyframes eyeroll {
149
+ 0%,
150
+ 100% {
151
+ transform: translate(0, 0);
152
+ }
153
+ 25% {
154
+ transform: translate(-15px, -10px);
155
+ }
156
+ 50% {
157
+ transform: translate(0, -15px);
158
+ }
159
+ 75% {
160
+ transform: translate(15px, -10px);
161
+ }
162
+ }
163
+
164
+ .pupil-animated {
165
+ animation-iteration-count: infinite;
166
+ animation-timing-function: ease-in-out;
167
+ }
168
+ </style>
169
+ </head>
170
+ <body class="light">
171
+ <div class="controls">
172
+ <h1>🍦 Sliccy Logo Editor</h1>
173
+
174
+ <div class="control-group">
175
+ <label>Color Mode:</label>
176
+ <select id="color-mode">
177
+ <option value="mono-light">Monochrome Light</option>
178
+ <option value="mono-dark">Monochrome Dark</option>
179
+ <option value="color">Color</option>
180
+ </select>
181
+ </div>
182
+
183
+ <h2>Scoops</h2>
184
+ <div class="control-group">
185
+ <label>Number of Scoops: <span class="value-display" id="scoop-count-val">5</span></label>
186
+ <input type="range" id="scoop-count" min="0" max="10" value="5" />
187
+ </div>
188
+
189
+ <h2>Scoop Stacking</h2>
190
+ <div class="control-group">
191
+ <label
192
+ >Vertical Offset per Scoop:
193
+ <span class="value-display" id="scoop-offset-val">95</span></label
194
+ >
195
+ <input type="range" id="scoop-offset" min="50" max="200" value="95" />
196
+ </div>
197
+ <div class="control-group">
198
+ <label
199
+ >Scale per Additional Scoop:
200
+ <span class="value-display" id="scoop-scale-val">0.95</span></label
201
+ >
202
+ <input type="range" id="scoop-scale" min="0.5" max="1" step="0.01" value="0.95" />
203
+ </div>
204
+
205
+ <h2>Mid-Range Adjustments (1-9 Scoops)</h2>
206
+ <div class="control-group">
207
+ <label
208
+ >Mid-Range Scale Factor:
209
+ <span class="value-display" id="midrange-scale-val">1.0</span></label
210
+ >
211
+ <input type="range" id="midrange-scale" min="0.8" max="1.2" step="0.01" value="1.0" />
212
+ </div>
213
+ <div class="control-group">
214
+ <label>Mid-Range Y Offset: <span class="value-display" id="midrange-y-val">0</span></label>
215
+ <input type="range" id="midrange-y" min="-200" max="200" value="0" />
213
216
  </div>
214
- <div class="logo-variant">
215
- <div class="logo-variant-label" id="label-user">5 Scoops</div>
216
- <div class="logo-container" id="logo-container-user"></div>
217
+ <div class="control-group">
218
+ <label
219
+ >Horizontal Wobble: <span class="value-display" id="scoop-wobble-val">14</span></label
220
+ >
221
+ <input type="range" id="scoop-wobble" min="0" max="50" value="14" />
217
222
  </div>
218
- <div class="logo-variant">
219
- <div class="logo-variant-label">10 Scoops</div>
220
- <div class="logo-container" id="logo-container-10"></div>
223
+ <div class="control-group">
224
+ <label
225
+ >Angular Wobble (°): <span class="value-display" id="angular-wobble-val">8</span></label
226
+ >
227
+ <input type="range" id="angular-wobble" min="0" max="30" value="8" />
221
228
  </div>
229
+ <div class="control-group">
230
+ <button class="toggle-btn" id="randomize-wobble">🎲 Randomize Wobble</button>
231
+ </div>
232
+ <div class="control-group">
233
+ <label
234
+ >Cone Push-Down per Scoop:
235
+ <span class="value-display" id="cone-push-val">-21</span></label
236
+ >
237
+ <input type="range" id="cone-push" min="-100" max="0" value="-21" />
238
+ </div>
239
+
240
+ <h2>Eye Animations</h2>
241
+ <div class="control-group">
242
+ <label> <input type="checkbox" id="eye-animate" /> Enable Eye Animations </label>
243
+ </div>
244
+ <div class="control-group">
245
+ <label
246
+ >Eyeroll Speed (s): <span class="value-display" id="eyeroll-speed-val">3</span></label
247
+ >
248
+ <input type="range" id="eyeroll-speed" min="0.5" max="10" step="0.5" value="3" />
249
+ </div>
250
+ <div class="control-group">
251
+ <label>Eye Distance: <span class="value-display" id="eye-distance-val">1.0</span></label>
252
+ <input type="range" id="eye-distance" min="0.5" max="1.5" step="0.05" value="1.0" />
253
+ </div>
254
+ <div class="control-group">
255
+ <label>Eye Size: <span class="value-display" id="eye-size-val">1.0</span></label>
256
+ <input type="range" id="eye-size" min="0.5" max="2" step="0.05" value="1.0" />
257
+ </div>
258
+ <div class="control-group">
259
+ <label>Eye Angle (°): <span class="value-display" id="eye-angle-val">0</span></label>
260
+ <input type="range" id="eye-angle" min="-45" max="45" value="0" />
261
+ </div>
262
+
263
+ <h2>Scoop Colors</h2>
264
+ <div id="color-controls-info" style="font-size: 0.85em; opacity: 0.7; margin-bottom: 10px">
265
+ Only visible in Color mode
266
+ </div>
267
+ <div id="color-controls"></div>
268
+
269
+ <h2>Export</h2>
270
+ <div class="control-group">
271
+ <button class="toggle-btn" id="generate-variants">Generate All Variants</button>
272
+ </div>
273
+ <div id="export-status" style="font-size: 0.85em; margin-top: 10px"></div>
222
274
  </div>
223
- </div>
224
-
225
- <script>
226
- let svgDoc = null;
227
- let scoopTemplate = null;
228
- let coneTemplate = null;
229
- let wobbleSeeds = []; // Random seeds for consistent wobble per scoop
230
-
231
- // Ice cream color palette with base colors and darker outline colors
232
- const scoopColors = [
233
- { base: '#FFB6C1', dark: '#E89AAB', name: 'Strawberry' }, // Pink
234
- { base: '#98FB98', dark: '#7FD87F', name: 'Mint' }, // Mint green
235
- { base: '#87CEEB', dark: '#6BB6D8', name: 'Blueberry' }, // Sky blue
236
- { base: '#DDA0DD', dark: '#C77DC7', name: 'Grape' }, // Plum
237
- { base: '#F0E68C', dark: '#D4C970', name: 'Vanilla' }, // Khaki
238
- { base: '#FFD700', dark: '#E6C200', name: 'Mango' }, // Gold
239
- { base: '#FFA07A', dark: '#E88A64', name: 'Peach' }, // Light salmon
240
- { base: '#DEB887', dark: '#C8A277', name: 'Caramel' }, // Burlywood
241
- { base: '#F08080', dark: '#D86A6A', name: 'Cherry' }, // Light coral
242
- { base: '#E0BBE4', dark: '#C9A5CD', name: 'Lavender' } // Lavender
243
- ];
244
-
245
- // Cone colors
246
- const coneColors = {
247
- base: '#D2691E', // Golden brown (chocolate)
248
- crosshatch: '#E8A75C', // Lighter brown
249
- outline: '#8B4513' // Saddle brown (darker)
250
- };
251
-
252
- // Generate random wobble seeds for scoops
253
- function regenerateWobbleSeeds(count) {
254
- wobbleSeeds = [];
255
- for (let i = 0; i < count; i++) {
256
- wobbleSeeds.push({
257
- x: Math.random() * 2 - 1, // -1 to 1
258
- angle: Math.random() * 2 - 1 // -1 to 1
259
- });
260
- }
261
- }
262
-
263
- async function loadSVG() {
264
- const response = await fetch('Sliccy Logo.svg');
265
- const text = await response.text();
266
- const parser = new DOMParser();
267
- svgDoc = parser.parseFromString(text, 'image/svg+xml');
268
-
269
- scoopTemplate = {
270
- outline: svgDoc.getElementById('Scoop_Outline').cloneNode(true),
271
- rightEye: svgDoc.getElementById('Right_Eye').cloneNode(true),
272
- leftEye: svgDoc.getElementById('Left_Eye').cloneNode(true)
273
- };
274
- coneTemplate = svgDoc.getElementById('Cone').cloneNode(true);
275
-
276
- generateColorControls();
277
- render();
278
- }
279
-
280
- function generateColorControls() {
281
- const container = document.getElementById('color-controls');
282
- container.innerHTML = '';
283
- const count = parseInt(document.getElementById('scoop-count').value);
284
- const colorMode = document.getElementById('color-mode').value;
285
-
286
- // Only show color pickers in color mode
287
- if (colorMode !== 'color') {
288
- return;
289
- }
290
-
291
- for (let i = 0; i < count; i++) {
292
- const div = document.createElement('div');
293
- div.className = 'control-group';
294
- const colorData = scoopColors[i % scoopColors.length];
295
- div.innerHTML = `
275
+
276
+ <div class="preview">
277
+ <div class="logo-variants">
278
+ <div class="logo-variant">
279
+ <div class="logo-variant-label">No Scoops</div>
280
+ <div class="logo-container" id="logo-container-0"></div>
281
+ </div>
282
+ <div class="logo-variant">
283
+ <div class="logo-variant-label" id="label-user">5 Scoops</div>
284
+ <div class="logo-container" id="logo-container-user"></div>
285
+ </div>
286
+ <div class="logo-variant">
287
+ <div class="logo-variant-label">10 Scoops</div>
288
+ <div class="logo-container" id="logo-container-10"></div>
289
+ </div>
290
+ </div>
291
+ </div>
292
+
293
+ <script>
294
+ let svgDoc = null;
295
+ let scoopTemplate = null;
296
+ let coneTemplate = null;
297
+ let wobbleSeeds = []; // Random seeds for consistent wobble per scoop
298
+
299
+ // Ice cream color palette with base colors and darker outline colors
300
+ const scoopColors = [
301
+ { base: '#FFB6C1', dark: '#E89AAB', name: 'Strawberry' }, // Pink
302
+ { base: '#98FB98', dark: '#7FD87F', name: 'Mint' }, // Mint green
303
+ { base: '#87CEEB', dark: '#6BB6D8', name: 'Blueberry' }, // Sky blue
304
+ { base: '#DDA0DD', dark: '#C77DC7', name: 'Grape' }, // Plum
305
+ { base: '#F0E68C', dark: '#D4C970', name: 'Vanilla' }, // Khaki
306
+ { base: '#FFD700', dark: '#E6C200', name: 'Mango' }, // Gold
307
+ { base: '#FFA07A', dark: '#E88A64', name: 'Peach' }, // Light salmon
308
+ { base: '#DEB887', dark: '#C8A277', name: 'Caramel' }, // Burlywood
309
+ { base: '#F08080', dark: '#D86A6A', name: 'Cherry' }, // Light coral
310
+ { base: '#E0BBE4', dark: '#C9A5CD', name: 'Lavender' }, // Lavender
311
+ ];
312
+
313
+ // Cone colors
314
+ const coneColors = {
315
+ base: '#D2691E', // Golden brown (chocolate)
316
+ crosshatch: '#E8A75C', // Lighter brown
317
+ outline: '#8B4513', // Saddle brown (darker)
318
+ };
319
+
320
+ // Generate random wobble seeds for scoops
321
+ function regenerateWobbleSeeds(count) {
322
+ wobbleSeeds = [];
323
+ for (let i = 0; i < count; i++) {
324
+ wobbleSeeds.push({
325
+ x: Math.random() * 2 - 1, // -1 to 1
326
+ angle: Math.random() * 2 - 1, // -1 to 1
327
+ });
328
+ }
329
+ }
330
+
331
+ async function loadSVG() {
332
+ const response = await fetch('Sliccy Logo.svg');
333
+ const text = await response.text();
334
+ const parser = new DOMParser();
335
+ svgDoc = parser.parseFromString(text, 'image/svg+xml');
336
+
337
+ scoopTemplate = {
338
+ outline: svgDoc.getElementById('Scoop_Outline').cloneNode(true),
339
+ rightEye: svgDoc.getElementById('Right_Eye').cloneNode(true),
340
+ leftEye: svgDoc.getElementById('Left_Eye').cloneNode(true),
341
+ };
342
+ coneTemplate = svgDoc.getElementById('Cone').cloneNode(true);
343
+
344
+ generateColorControls();
345
+ render();
346
+ }
347
+
348
+ function generateColorControls() {
349
+ const container = document.getElementById('color-controls');
350
+ container.innerHTML = '';
351
+ const count = parseInt(document.getElementById('scoop-count').value);
352
+ const colorMode = document.getElementById('color-mode').value;
353
+
354
+ // Only show color pickers in color mode
355
+ if (colorMode !== 'color') {
356
+ return;
357
+ }
358
+
359
+ for (let i = 0; i < count; i++) {
360
+ const div = document.createElement('div');
361
+ div.className = 'control-group';
362
+ const colorData = scoopColors[i % scoopColors.length];
363
+ div.innerHTML = `
296
364
  <label>Scoop ${i + 1} (${colorData.name}):</label>
297
365
  <input type="color" id="scoop-color-${i}" value="${colorData.base}">
298
366
  `;
299
- container.appendChild(div);
300
- document.getElementById(`scoop-color-${i}`).addEventListener('input', render);
301
- }
302
- }
303
-
304
- function renderLogo(container, count, params) {
305
- const { offset, scaleDecay, wobble, angularWobble, conePush, colorMode, midrangeScale, midrangeY } = params;
306
-
307
- // Linear interpolation based on scoop count (0-10)
308
- // For 0 scoops: baseY = -175, scale = 1.25
309
- // For 10 scoops: baseY = 512, scale = 1.1
310
- const t = count / 10;
311
- let baseY = -175 + t * (512 - (-175)); // -175 to 512
312
- let overallScale = 1.25 + t * (1.1 - 1.25); // 1.25 to 1.1
313
-
314
- // Apply mid-range adjustments for 1-9 scoops using calibrated curves
315
- if (count >= 1 && count <= 9) {
316
- // Scale factor: interpolate from 0.9 (at 1-2) to 1.0 (at 7-9)
317
- // Using piecewise linear: 0.9 at 1-2, 0.95 at 5, 1.0 at 7+
318
- let scaleFactor;
319
- if (count <= 2) {
320
- scaleFactor = 0.9;
321
- } else if (count <= 5) {
322
- // Linear from 0.9 to 0.95 between scoops 2 and 5
323
- scaleFactor = 0.9 + (count - 2) * (0.95 - 0.9) / (5 - 2);
324
- } else {
325
- // Linear from 0.95 to 1.0 between scoops 5 and 7
326
- scaleFactor = 0.95 + Math.min(count - 5, 2) * (1.0 - 0.95) / (7 - 5);
327
- }
328
-
329
- // Y offset: peaks at 120 (scoops 2-5), then decreases to 50 (scoop 9)
330
- // 1: 100, 2-5: 120, 7: 110, 8: 80, 9: 50
331
- let yOffset;
332
- if (count === 1) {
333
- yOffset = 100;
334
- } else if (count <= 5) {
335
- yOffset = 120;
336
- } else if (count === 6) {
337
- yOffset = 115; // interpolate between 120 and 110
338
- } else if (count === 7) {
339
- yOffset = 110;
340
- } else if (count === 8) {
341
- yOffset = 80;
342
- } else { // count === 9
343
- yOffset = 50;
344
- }
345
-
346
- overallScale *= scaleFactor * midrangeScale;
347
- baseY += yOffset + midrangeY;
348
- }
349
-
350
- // Ensure we have enough wobble seeds
351
- while (wobbleSeeds.length < count) {
352
- wobbleSeeds.push({
353
- x: Math.random() * 2 - 1,
354
- angle: Math.random() * 2 - 1
355
- });
356
- }
357
-
358
- const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
359
- // Use square viewBox (1024x1024) and center the content horizontally
360
- const viewBoxWidth = 1024;
361
- const viewBoxHeight = 1024;
362
- const contentWidth = 576.75;
363
- const xOffset = (viewBoxWidth - contentWidth) / 2;
364
- svg.setAttribute('viewBox', `0 0 ${viewBoxWidth} ${viewBoxHeight}`);
365
- svg.setAttribute('width', '100%');
366
- svg.setAttribute('height', '100%');
367
-
368
- const mainGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
369
- const conePushTotal = (count - 1) * conePush;
370
- const totalHeight = 887.3 + (count - 1) * offset + conePushTotal;
371
- const scaleFactor = Math.min(1, 900 / totalHeight) * overallScale;
372
- const translateY = (viewBoxHeight - totalHeight * scaleFactor) / 2 + baseY;
373
- const translateX = xOffset + (contentWidth - contentWidth * scaleFactor) / 2;
374
- mainGroup.setAttribute('transform', `translate(${translateX}, ${translateY}) scale(${scaleFactor})`);
375
-
376
- // Add cone (pushed down by additional scoops)
377
- const coneGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
378
- coneGroup.setAttribute('transform', `translate(0, ${conePushTotal})`);
379
- const cone = coneTemplate.cloneNode(true);
380
- applyConeColors(cone, colorMode);
381
- coneGroup.appendChild(cone);
382
-
383
- // If no scoops, add eyes to the cone
384
- if (count === 0) {
385
- const rightEye = scoopTemplate.rightEye.cloneNode(true);
386
- const leftEye = scoopTemplate.leftEye.cloneNode(true);
387
- applyEyeColors(rightEye, leftEye, colorMode);
388
- applyEyeTransforms(rightEye, leftEye, params);
389
- coneGroup.appendChild(rightEye);
390
- coneGroup.appendChild(leftEye);
391
- }
392
- mainGroup.appendChild(coneGroup);
393
-
394
- // Add scoops from bottom to top
395
- for (let i = 0; i < count; i++) {
396
- const scoopGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
397
- // Each scoop is pushed down by the weight of scoops above it
398
- const scoopsAbove = (count - 1) - i;
399
- const pushDown = scoopsAbove * conePush;
400
- const scoopY = -i * offset + pushDown;
401
- // Random wobble (bottom scoop stays centered)
402
- const scoopX = i > 0 ? wobbleSeeds[i].x * wobble : 0;
403
- const scoopAngle = i > 0 ? wobbleSeeds[i].angle * angularWobble : 0;
404
- const scoopScale = Math.pow(scaleDecay, i);
405
- const centerX = 288;
406
- const centerY = 250;
407
-
408
- scoopGroup.setAttribute('transform',
409
- `translate(${centerX + scoopX}, ${centerY + scoopY}) rotate(${scoopAngle}) scale(${scoopScale}) translate(${-centerX}, ${-centerY})`);
410
-
411
- const outline = scoopTemplate.outline.cloneNode(true);
412
- applyScoopColors(outline, i, colorMode, svg);
413
- scoopGroup.appendChild(outline);
414
-
415
- // Only add eyes to top scoop
416
- if (i === count - 1) {
417
- const rightEye = scoopTemplate.rightEye.cloneNode(true);
418
- const leftEye = scoopTemplate.leftEye.cloneNode(true);
419
- applyEyeColors(rightEye, leftEye, colorMode);
420
- applyEyeTransforms(rightEye, leftEye, params);
421
- scoopGroup.appendChild(rightEye);
422
- scoopGroup.appendChild(leftEye);
423
- }
424
-
425
- mainGroup.appendChild(scoopGroup);
426
- }
427
-
428
- svg.appendChild(mainGroup);
429
- container.innerHTML = '';
430
- container.appendChild(svg);
431
- }
432
-
433
- function applyConeColors(cone, colorMode) {
434
- if (colorMode === 'mono-light') {
435
- // White cone with black outlines
436
- cone.querySelectorAll('[style*="fill"]').forEach(e => {
437
- if (e.id === 'Cone_Bottom' || e.id === 'Cone_Top') {
438
- e.style.fill = '#fff';
439
- } else {
440
- // Waffle cross-hatch
441
- e.style.fill = '#000';
442
- }
443
- });
444
- cone.querySelectorAll('path:not([style*="fill"]), rect:not([style*="fill"])').forEach(e => {
445
- e.style.fill = '#000';
446
- });
447
- cone.querySelectorAll('[style*="stroke"]').forEach(e => {
448
- e.style.stroke = '#000';
449
- });
450
- } else if (colorMode === 'mono-dark') {
451
- // Dark gray cone with white outlines
452
- cone.querySelectorAll('[style*="fill"]').forEach(e => {
453
- if (e.id === 'Cone_Bottom' || e.id === 'Cone_Top') {
454
- e.style.fill = '#222';
455
- } else {
456
- // Waffle cross-hatch
457
- e.style.fill = '#fff';
458
- }
459
- });
460
- cone.querySelectorAll('path:not([style*="fill"]), rect:not([style*="fill"])').forEach(e => {
461
- e.style.fill = '#fff';
462
- });
463
- cone.querySelectorAll('[style*="stroke"]').forEach(e => {
464
- e.style.stroke = '#fff';
465
- });
466
- } else {
467
- // Color mode
468
- cone.querySelectorAll('[style*="fill"]').forEach(e => {
469
- if (e.id === 'Cone_Bottom' || e.id === 'Cone_Top') {
470
- e.style.fill = coneColors.base;
471
- } else {
472
- // Waffle cross-hatch
473
- e.style.fill = coneColors.crosshatch;
474
- }
475
- });
476
- cone.querySelectorAll('path:not([style*="fill"]), rect:not([style*="fill"])').forEach(e => {
477
- e.style.fill = coneColors.crosshatch;
478
- });
479
- cone.querySelectorAll('[style*="stroke"]').forEach(e => {
480
- e.style.stroke = coneColors.outline;
481
- });
482
- }
483
- }
484
-
485
- function darkenColor(hex, percent = 20) {
486
- // Convert hex to RGB
487
- const r = parseInt(hex.slice(1, 3), 16);
488
- const g = parseInt(hex.slice(3, 5), 16);
489
- const b = parseInt(hex.slice(5, 7), 16);
490
-
491
- // Darken by percentage
492
- const newR = Math.max(0, Math.floor(r * (1 - percent / 100)));
493
- const newG = Math.max(0, Math.floor(g * (1 - percent / 100)));
494
- const newB = Math.max(0, Math.floor(b * (1 - percent / 100)));
495
-
496
- // Convert back to hex
497
- return `#${newR.toString(16).padStart(2, '0')}${newG.toString(16).padStart(2, '0')}${newB.toString(16).padStart(2, '0')}`;
498
- }
499
-
500
- function applyScoopColors(outline, scoopIndex, colorMode, svgRoot) {
501
- if (colorMode === 'mono-light') {
502
- outline.style.fill = '#fff';
503
- outline.style.stroke = '#000';
504
- } else if (colorMode === 'mono-dark') {
505
- outline.style.fill = '#222';
506
- outline.style.stroke = '#fff';
507
- } else {
508
- // Color mode - use custom color or default palette
509
- const colorInput = document.getElementById(`scoop-color-${scoopIndex}`);
510
- const baseColor = colorInput ? colorInput.value : scoopColors[scoopIndex % scoopColors.length].base;
511
- const darkColor = scoopColors[scoopIndex % scoopColors.length].dark;
512
-
513
- // Create outline color - darker than the dark gradient stop
514
- const outlineColor = darkenColor(darkColor, 20);
515
-
516
- // Create gradient in the SVG defs
517
- const gradientId = `scoop-gradient-${scoopIndex}`;
518
-
519
- let defs = svgRoot.querySelector('defs');
520
- if (!defs) {
521
- defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
522
- svgRoot.insertBefore(defs, svgRoot.firstChild);
523
- }
524
-
525
- // Remove old gradient if it exists
526
- const oldGradient = defs.querySelector(`#${gradientId}`);
527
- if (oldGradient) {
528
- oldGradient.remove();
529
- }
530
-
531
- const gradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
532
- gradient.setAttribute('id', gradientId);
533
- gradient.setAttribute('x1', '0%');
534
- gradient.setAttribute('y1', '0%');
535
- gradient.setAttribute('x2', '100%');
536
- gradient.setAttribute('y2', '100%');
537
-
538
- const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
539
- stop1.setAttribute('offset', '0%');
540
- stop1.setAttribute('style', `stop-color:${baseColor};stop-opacity:1`);
541
-
542
- const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
543
- stop2.setAttribute('offset', '100%');
544
- stop2.setAttribute('style', `stop-color:${darkColor};stop-opacity:1`);
545
-
546
- gradient.appendChild(stop1);
547
- gradient.appendChild(stop2);
548
- defs.appendChild(gradient);
549
-
550
- outline.style.fill = `url(#${gradientId})`;
551
- outline.style.stroke = outlineColor;
552
- }
553
- }
554
-
555
- function applyEyeColors(rightEye, leftEye, colorMode) {
556
- [rightEye, leftEye].forEach(eye => {
557
- const outline = eye.querySelector('circle');
558
- const pupil = eye.querySelector('path');
559
-
560
- if (colorMode === 'mono-light') {
561
- if (outline) {
562
- outline.style.fill = '#fff';
563
- outline.style.stroke = '#000';
564
- }
565
- if (pupil) pupil.style.fill = '#000';
566
- } else if (colorMode === 'mono-dark') {
567
- if (outline) {
568
- outline.style.fill = '#000';
569
- outline.style.stroke = '#fff';
570
- }
571
- if (pupil) pupil.style.fill = '#fff';
572
- } else {
573
- // Color mode
574
- if (outline) {
575
- outline.style.fill = '#fff';
576
- outline.style.stroke = '#000';
577
- }
578
- if (pupil) pupil.style.fill = '#000';
579
- }
580
- });
581
- }
582
-
583
- function applyEyeTransforms(rightEye, leftEye, params) {
584
- const { eyeAnimate, eyerollSpeed, eyeDistance, eyeSize, eyeAngle } = params;
585
-
586
- // Get original eye positions from SVG
587
- // Right eye center: 383.4, 216.12
588
- // Left eye center: 175.1, 250.34
589
- const rightEyeCenterX = 383.4;
590
- const rightEyeCenterY = 216.12;
591
- const leftEyeCenterX = 175.1;
592
- const leftEyeCenterY = 250.34;
593
- const eyeCenterX = (rightEyeCenterX + leftEyeCenterX) / 2;
594
- const eyeCenterY = (rightEyeCenterY + leftEyeCenterY) / 2;
595
-
596
- // Apply transforms to both eyes
597
- [rightEye, leftEye].forEach((eye, idx) => {
598
- const isRight = idx === 0;
599
- const centerX = isRight ? rightEyeCenterX : leftEyeCenterX;
600
- const centerY = isRight ? rightEyeCenterY : leftEyeCenterY;
601
-
602
- // Build transform string for the whole eye group
603
- let transforms = [];
604
-
605
- // Eye distance: move eyes apart/together from center point
606
- if (eyeDistance !== 1.0) {
607
- const dx = (centerX - eyeCenterX) * (eyeDistance - 1);
608
- const dy = (centerY - eyeCenterY) * (eyeDistance - 1);
609
- transforms.push(`translate(${dx}, ${dy})`);
610
- }
611
-
612
- // Eye size: scale from eye center
613
- if (eyeSize !== 1.0) {
614
- transforms.push(`translate(${centerX}, ${centerY}) scale(${eyeSize}) translate(${-centerX}, ${-centerY})`);
615
- }
616
-
617
- // Eye angle: rotate from eye center
618
- if (eyeAngle !== 0) {
619
- transforms.push(`translate(${centerX}, ${centerY}) rotate(${eyeAngle}) translate(${-centerX}, ${-centerY})`);
620
- }
621
-
622
- if (transforms.length > 0) {
623
- eye.setAttribute('transform', transforms.join(' '));
624
- }
625
-
626
- // Apply animation to the pupil only using the correct ID
627
- const pupilId = isRight ? 'Right_Pupil' : 'Left_Pupil';
628
- const pupil = eye.querySelector(`#${pupilId}`);
629
- if (pupil && eyeAnimate) {
630
- pupil.classList.add('pupil-animated');
631
- pupil.style.animation = `eyeroll ${eyerollSpeed}s ease-in-out infinite`;
632
- } else if (pupil) {
633
- pupil.classList.remove('pupil-animated');
634
- pupil.style.animation = '';
635
- }
636
- });
637
- }
638
-
639
- function render() {
640
- const userCount = parseInt(document.getElementById('scoop-count').value);
641
- const colorMode = document.getElementById('color-mode').value;
642
-
643
- // Update body class based on color mode
644
- document.body.classList.remove('light', 'dark');
645
- if (colorMode === 'mono-dark') {
646
- document.body.classList.add('dark');
647
- } else {
648
- document.body.classList.add('light');
649
- }
650
-
651
- const params = {
652
- offset: parseInt(document.getElementById('scoop-offset').value),
653
- scaleDecay: parseFloat(document.getElementById('scoop-scale').value),
654
- wobble: parseInt(document.getElementById('scoop-wobble').value),
655
- angularWobble: parseInt(document.getElementById('angular-wobble').value),
656
- conePush: parseInt(document.getElementById('cone-push').value),
657
- midrangeScale: parseFloat(document.getElementById('midrange-scale').value),
658
- midrangeY: parseInt(document.getElementById('midrange-y').value),
659
- eyeAnimate: document.getElementById('eye-animate').checked,
660
- eyerollSpeed: parseFloat(document.getElementById('eyeroll-speed').value),
661
- eyeDistance: parseFloat(document.getElementById('eye-distance').value),
662
- eyeSize: parseFloat(document.getElementById('eye-size').value),
663
- eyeAngle: parseFloat(document.getElementById('eye-angle').value),
664
- colorMode: colorMode
665
- };
666
-
667
- // Update label
668
- document.getElementById('label-user').textContent = `${userCount} Scoop${userCount !== 1 ? 's' : ''}`;
669
-
670
- // Render all three variants
671
- renderLogo(document.getElementById('logo-container-0'), 0, params);
672
- renderLogo(document.getElementById('logo-container-user'), userCount, params);
673
- renderLogo(document.getElementById('logo-container-10'), 10, params);
674
- }
675
-
676
-
677
-
678
- // Generate SVG string from container
679
- function getSVGString(container) {
680
- const svg = container.querySelector('svg');
681
- if (!svg) return null;
682
-
683
- const serializer = new XMLSerializer();
684
- let svgString = serializer.serializeToString(svg);
685
-
686
- // Add XML declaration
687
- svgString = '<?xml version="1.0" encoding="UTF-8"?>\n' + svgString;
688
-
689
- return svgString;
690
- }
691
-
692
- // Convert SVG to PNG (square)
693
- function svgToPNG(svgString, size) {
694
- return new Promise((resolve, reject) => {
695
- const img = new Image();
696
- const canvas = document.createElement('canvas');
697
- canvas.width = size;
698
- canvas.height = size;
699
- const ctx = canvas.getContext('2d');
700
-
701
- img.onload = () => {
702
- ctx.drawImage(img, 0, 0, size, size);
703
- canvas.toBlob(blob => {
704
- resolve(blob);
705
- }, 'image/png');
706
- };
707
-
708
- img.onerror = reject;
709
-
710
- const blob = new Blob([svgString], { type: 'image/svg+xml' });
711
- const url = URL.createObjectURL(blob);
712
- img.src = url;
713
- });
714
- }
715
-
716
- // Download a file
717
- function downloadFile(blob, filename) {
718
- const url = URL.createObjectURL(blob);
719
- const a = document.createElement('a');
720
- a.href = url;
721
- a.download = filename;
722
- document.body.appendChild(a);
723
- a.click();
724
- document.body.removeChild(a);
725
- URL.revokeObjectURL(url);
726
- }
727
-
728
- // Generate all variants
729
- async function generateAllVariants() {
730
- const statusEl = document.getElementById('export-status');
731
- statusEl.textContent = 'Generating variants...';
732
-
733
- const modes = ['mono-light', 'mono-dark', 'color'];
734
- const scoopCounts = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
735
- const pngSizes = [16, 32, 48, 128];
736
-
737
- // Save current state
738
- const originalMode = document.getElementById('color-mode').value;
739
- const originalCount = parseInt(document.getElementById('scoop-count').value);
740
-
741
- // Create temporary container for rendering
742
- const tempContainer = document.createElement('div');
743
- tempContainer.style.position = 'absolute';
744
- tempContainer.style.left = '-9999px';
745
- tempContainer.style.width = '300px';
746
- tempContainer.style.height = '300px';
747
- document.body.appendChild(tempContainer);
748
-
749
- let totalFiles = 0;
750
-
751
- try {
752
- // Generate SVG variants (3 modes × 11 scoop counts = 33 files)
753
- for (const mode of modes) {
754
- for (const count of scoopCounts) {
755
- statusEl.textContent = `Generating SVG: ${mode}, ${count} scoops...`;
756
-
757
- // Set mode and count
758
- document.getElementById('color-mode').value = mode;
759
- document.getElementById('scoop-count').value = count;
760
- generateColorControls();
367
+ container.appendChild(div);
368
+ document.getElementById(`scoop-color-${i}`).addEventListener('input', render);
369
+ }
370
+ }
761
371
 
762
- // Get current params
763
- const params = {
764
- offset: parseInt(document.getElementById('scoop-offset').value),
765
- scaleDecay: parseFloat(document.getElementById('scoop-scale').value),
766
- wobble: parseInt(document.getElementById('scoop-wobble').value),
767
- angularWobble: parseInt(document.getElementById('angular-wobble').value),
768
- conePush: parseInt(document.getElementById('cone-push').value),
769
- midrangeScale: parseFloat(document.getElementById('midrange-scale').value),
770
- midrangeY: parseInt(document.getElementById('midrange-y').value),
771
- eyeAnimate: false, // No animation in exports
772
- eyerollSpeed: parseFloat(document.getElementById('eyeroll-speed').value),
773
- eyeDistance: parseFloat(document.getElementById('eye-distance').value),
774
- eyeSize: parseFloat(document.getElementById('eye-size').value),
775
- eyeAngle: parseFloat(document.getElementById('eye-angle').value),
776
- colorMode: mode
777
- };
372
+ function renderLogo(container, count, params) {
373
+ const {
374
+ offset,
375
+ scaleDecay,
376
+ wobble,
377
+ angularWobble,
378
+ conePush,
379
+ colorMode,
380
+ midrangeScale,
381
+ midrangeY,
382
+ } = params;
383
+
384
+ // Linear interpolation based on scoop count (0-10)
385
+ // For 0 scoops: baseY = -175, scale = 1.25
386
+ // For 10 scoops: baseY = 512, scale = 1.1
387
+ const t = count / 10;
388
+ let baseY = -175 + t * (512 - -175); // -175 to 512
389
+ let overallScale = 1.25 + t * (1.1 - 1.25); // 1.25 to 1.1
390
+
391
+ // Apply mid-range adjustments for 1-9 scoops using calibrated curves
392
+ if (count >= 1 && count <= 9) {
393
+ // Scale factor: interpolate from 0.9 (at 1-2) to 1.0 (at 7-9)
394
+ // Using piecewise linear: 0.9 at 1-2, 0.95 at 5, 1.0 at 7+
395
+ let scaleFactor;
396
+ if (count <= 2) {
397
+ scaleFactor = 0.9;
398
+ } else if (count <= 5) {
399
+ // Linear from 0.9 to 0.95 between scoops 2 and 5
400
+ scaleFactor = 0.9 + ((count - 2) * (0.95 - 0.9)) / (5 - 2);
401
+ } else {
402
+ // Linear from 0.95 to 1.0 between scoops 5 and 7
403
+ scaleFactor = 0.95 + (Math.min(count - 5, 2) * (1.0 - 0.95)) / (7 - 5);
404
+ }
405
+
406
+ // Y offset: peaks at 120 (scoops 2-5), then decreases to 50 (scoop 9)
407
+ // 1: 100, 2-5: 120, 7: 110, 8: 80, 9: 50
408
+ let yOffset;
409
+ if (count === 1) {
410
+ yOffset = 100;
411
+ } else if (count <= 5) {
412
+ yOffset = 120;
413
+ } else if (count === 6) {
414
+ yOffset = 115; // interpolate between 120 and 110
415
+ } else if (count === 7) {
416
+ yOffset = 110;
417
+ } else if (count === 8) {
418
+ yOffset = 80;
419
+ } else {
420
+ // count === 9
421
+ yOffset = 50;
422
+ }
423
+
424
+ overallScale *= scaleFactor * midrangeScale;
425
+ baseY += yOffset + midrangeY;
426
+ }
778
427
 
779
- // Render to temp container
780
- renderLogo(tempContainer, count, params);
428
+ // Ensure we have enough wobble seeds
429
+ while (wobbleSeeds.length < count) {
430
+ wobbleSeeds.push({
431
+ x: Math.random() * 2 - 1,
432
+ angle: Math.random() * 2 - 1,
433
+ });
434
+ }
781
435
 
782
- // Get SVG string
783
- const svgString = getSVGString(tempContainer);
784
- if (svgString) {
785
- const blob = new Blob([svgString], { type: 'image/svg+xml' });
786
- const filename = `sliccy-${mode}-${count}scoops.svg`;
787
- downloadFile(blob, filename);
788
- totalFiles++;
436
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
437
+ // Use square viewBox (1024x1024) and center the content horizontally
438
+ const viewBoxWidth = 1024;
439
+ const viewBoxHeight = 1024;
440
+ const contentWidth = 576.75;
441
+ const xOffset = (viewBoxWidth - contentWidth) / 2;
442
+ svg.setAttribute('viewBox', `0 0 ${viewBoxWidth} ${viewBoxHeight}`);
443
+ svg.setAttribute('width', '100%');
444
+ svg.setAttribute('height', '100%');
445
+
446
+ const mainGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
447
+ const conePushTotal = (count - 1) * conePush;
448
+ const totalHeight = 887.3 + (count - 1) * offset + conePushTotal;
449
+ const scaleFactor = Math.min(1, 900 / totalHeight) * overallScale;
450
+ const translateY = (viewBoxHeight - totalHeight * scaleFactor) / 2 + baseY;
451
+ const translateX = xOffset + (contentWidth - contentWidth * scaleFactor) / 2;
452
+ mainGroup.setAttribute(
453
+ 'transform',
454
+ `translate(${translateX}, ${translateY}) scale(${scaleFactor})`
455
+ );
456
+
457
+ // Add cone (pushed down by additional scoops)
458
+ const coneGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
459
+ coneGroup.setAttribute('transform', `translate(0, ${conePushTotal})`);
460
+ const cone = coneTemplate.cloneNode(true);
461
+ applyConeColors(cone, colorMode);
462
+ coneGroup.appendChild(cone);
463
+
464
+ // If no scoops, add eyes to the cone
465
+ if (count === 0) {
466
+ const rightEye = scoopTemplate.rightEye.cloneNode(true);
467
+ const leftEye = scoopTemplate.leftEye.cloneNode(true);
468
+ applyEyeColors(rightEye, leftEye, colorMode);
469
+ applyEyeTransforms(rightEye, leftEye, params);
470
+ coneGroup.appendChild(rightEye);
471
+ coneGroup.appendChild(leftEye);
472
+ }
473
+ mainGroup.appendChild(coneGroup);
474
+
475
+ // Add scoops from bottom to top
476
+ for (let i = 0; i < count; i++) {
477
+ const scoopGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
478
+ // Each scoop is pushed down by the weight of scoops above it
479
+ const scoopsAbove = count - 1 - i;
480
+ const pushDown = scoopsAbove * conePush;
481
+ const scoopY = -i * offset + pushDown;
482
+ // Random wobble (bottom scoop stays centered)
483
+ const scoopX = i > 0 ? wobbleSeeds[i].x * wobble : 0;
484
+ const scoopAngle = i > 0 ? wobbleSeeds[i].angle * angularWobble : 0;
485
+ const scoopScale = Math.pow(scaleDecay, i);
486
+ const centerX = 288;
487
+ const centerY = 250;
488
+
489
+ scoopGroup.setAttribute(
490
+ 'transform',
491
+ `translate(${centerX + scoopX}, ${centerY + scoopY}) rotate(${scoopAngle}) scale(${scoopScale}) translate(${-centerX}, ${-centerY})`
492
+ );
493
+
494
+ const outline = scoopTemplate.outline.cloneNode(true);
495
+ applyScoopColors(outline, i, colorMode, svg);
496
+ scoopGroup.appendChild(outline);
497
+
498
+ // Only add eyes to top scoop
499
+ if (i === count - 1) {
500
+ const rightEye = scoopTemplate.rightEye.cloneNode(true);
501
+ const leftEye = scoopTemplate.leftEye.cloneNode(true);
502
+ applyEyeColors(rightEye, leftEye, colorMode);
503
+ applyEyeTransforms(rightEye, leftEye, params);
504
+ scoopGroup.appendChild(rightEye);
505
+ scoopGroup.appendChild(leftEye);
506
+ }
507
+
508
+ mainGroup.appendChild(scoopGroup);
509
+ }
510
+
511
+ svg.appendChild(mainGroup);
512
+ container.innerHTML = '';
513
+ container.appendChild(svg);
514
+ }
515
+
516
+ function applyConeColors(cone, colorMode) {
517
+ if (colorMode === 'mono-light') {
518
+ // White cone with black outlines
519
+ cone.querySelectorAll('[style*="fill"]').forEach((e) => {
520
+ if (e.id === 'Cone_Bottom' || e.id === 'Cone_Top') {
521
+ e.style.fill = '#fff';
522
+ } else {
523
+ // Waffle cross-hatch
524
+ e.style.fill = '#000';
525
+ }
526
+ });
527
+ cone
528
+ .querySelectorAll('path:not([style*="fill"]), rect:not([style*="fill"])')
529
+ .forEach((e) => {
530
+ e.style.fill = '#000';
531
+ });
532
+ cone.querySelectorAll('[style*="stroke"]').forEach((e) => {
533
+ e.style.stroke = '#000';
534
+ });
535
+ } else if (colorMode === 'mono-dark') {
536
+ // Dark gray cone with white outlines
537
+ cone.querySelectorAll('[style*="fill"]').forEach((e) => {
538
+ if (e.id === 'Cone_Bottom' || e.id === 'Cone_Top') {
539
+ e.style.fill = '#222';
540
+ } else {
541
+ // Waffle cross-hatch
542
+ e.style.fill = '#fff';
543
+ }
544
+ });
545
+ cone
546
+ .querySelectorAll('path:not([style*="fill"]), rect:not([style*="fill"])')
547
+ .forEach((e) => {
548
+ e.style.fill = '#fff';
549
+ });
550
+ cone.querySelectorAll('[style*="stroke"]').forEach((e) => {
551
+ e.style.stroke = '#fff';
552
+ });
553
+ } else {
554
+ // Color mode
555
+ cone.querySelectorAll('[style*="fill"]').forEach((e) => {
556
+ if (e.id === 'Cone_Bottom' || e.id === 'Cone_Top') {
557
+ e.style.fill = coneColors.base;
558
+ } else {
559
+ // Waffle cross-hatch
560
+ e.style.fill = coneColors.crosshatch;
561
+ }
562
+ });
563
+ cone
564
+ .querySelectorAll('path:not([style*="fill"]), rect:not([style*="fill"])')
565
+ .forEach((e) => {
566
+ e.style.fill = coneColors.crosshatch;
567
+ });
568
+ cone.querySelectorAll('[style*="stroke"]').forEach((e) => {
569
+ e.style.stroke = coneColors.outline;
570
+ });
571
+ }
572
+ }
789
573
 
790
- // Small delay to avoid overwhelming the browser
791
- await new Promise(resolve => setTimeout(resolve, 100));
574
+ function darkenColor(hex, percent = 20) {
575
+ // Convert hex to RGB
576
+ const r = parseInt(hex.slice(1, 3), 16);
577
+ const g = parseInt(hex.slice(3, 5), 16);
578
+ const b = parseInt(hex.slice(5, 7), 16);
579
+
580
+ // Darken by percentage
581
+ const newR = Math.max(0, Math.floor(r * (1 - percent / 100)));
582
+ const newG = Math.max(0, Math.floor(g * (1 - percent / 100)));
583
+ const newB = Math.max(0, Math.floor(b * (1 - percent / 100)));
584
+
585
+ // Convert back to hex
586
+ return `#${newR.toString(16).padStart(2, '0')}${newG.toString(16).padStart(2, '0')}${newB.toString(16).padStart(2, '0')}`;
587
+ }
588
+
589
+ function applyScoopColors(outline, scoopIndex, colorMode, svgRoot) {
590
+ if (colorMode === 'mono-light') {
591
+ outline.style.fill = '#fff';
592
+ outline.style.stroke = '#000';
593
+ } else if (colorMode === 'mono-dark') {
594
+ outline.style.fill = '#222';
595
+ outline.style.stroke = '#fff';
596
+ } else {
597
+ // Color mode - use custom color or default palette
598
+ const colorInput = document.getElementById(`scoop-color-${scoopIndex}`);
599
+ const baseColor = colorInput
600
+ ? colorInput.value
601
+ : scoopColors[scoopIndex % scoopColors.length].base;
602
+ const darkColor = scoopColors[scoopIndex % scoopColors.length].dark;
603
+
604
+ // Create outline color - darker than the dark gradient stop
605
+ const outlineColor = darkenColor(darkColor, 20);
606
+
607
+ // Create gradient in the SVG defs
608
+ const gradientId = `scoop-gradient-${scoopIndex}`;
609
+
610
+ let defs = svgRoot.querySelector('defs');
611
+ if (!defs) {
612
+ defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
613
+ svgRoot.insertBefore(defs, svgRoot.firstChild);
614
+ }
615
+
616
+ // Remove old gradient if it exists
617
+ const oldGradient = defs.querySelector(`#${gradientId}`);
618
+ if (oldGradient) {
619
+ oldGradient.remove();
620
+ }
621
+
622
+ const gradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
623
+ gradient.setAttribute('id', gradientId);
624
+ gradient.setAttribute('x1', '0%');
625
+ gradient.setAttribute('y1', '0%');
626
+ gradient.setAttribute('x2', '100%');
627
+ gradient.setAttribute('y2', '100%');
628
+
629
+ const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
630
+ stop1.setAttribute('offset', '0%');
631
+ stop1.setAttribute('style', `stop-color:${baseColor};stop-opacity:1`);
632
+
633
+ const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
634
+ stop2.setAttribute('offset', '100%');
635
+ stop2.setAttribute('style', `stop-color:${darkColor};stop-opacity:1`);
636
+
637
+ gradient.appendChild(stop1);
638
+ gradient.appendChild(stop2);
639
+ defs.appendChild(gradient);
640
+
641
+ outline.style.fill = `url(#${gradientId})`;
642
+ outline.style.stroke = outlineColor;
792
643
  }
793
644
  }
794
- }
795
645
 
796
- // Generate PNG variants (only from color mode, 11 counts × 4 sizes = 44 files)
797
- document.getElementById('color-mode').value = 'color';
798
- generateColorControls();
646
+ function applyEyeColors(rightEye, leftEye, colorMode) {
647
+ [rightEye, leftEye].forEach((eye) => {
648
+ const outline = eye.querySelector('circle');
649
+ const pupil = eye.querySelector('path');
650
+
651
+ if (colorMode === 'mono-light') {
652
+ if (outline) {
653
+ outline.style.fill = '#fff';
654
+ outline.style.stroke = '#000';
655
+ }
656
+ if (pupil) pupil.style.fill = '#000';
657
+ } else if (colorMode === 'mono-dark') {
658
+ if (outline) {
659
+ outline.style.fill = '#000';
660
+ outline.style.stroke = '#fff';
661
+ }
662
+ if (pupil) pupil.style.fill = '#fff';
663
+ } else {
664
+ // Color mode
665
+ if (outline) {
666
+ outline.style.fill = '#fff';
667
+ outline.style.stroke = '#000';
668
+ }
669
+ if (pupil) pupil.style.fill = '#000';
670
+ }
671
+ });
672
+ }
799
673
 
800
- for (const count of scoopCounts) {
801
- for (const size of pngSizes) {
802
- statusEl.textContent = `Generating PNG: ${count} scoops, ${size}x${size}...`;
674
+ function applyEyeTransforms(rightEye, leftEye, params) {
675
+ const { eyeAnimate, eyerollSpeed, eyeDistance, eyeSize, eyeAngle } = params;
676
+
677
+ // Get original eye positions from SVG
678
+ // Right eye center: 383.4, 216.12
679
+ // Left eye center: 175.1, 250.34
680
+ const rightEyeCenterX = 383.4;
681
+ const rightEyeCenterY = 216.12;
682
+ const leftEyeCenterX = 175.1;
683
+ const leftEyeCenterY = 250.34;
684
+ const eyeCenterX = (rightEyeCenterX + leftEyeCenterX) / 2;
685
+ const eyeCenterY = (rightEyeCenterY + leftEyeCenterY) / 2;
686
+
687
+ // Apply transforms to both eyes
688
+ [rightEye, leftEye].forEach((eye, idx) => {
689
+ const isRight = idx === 0;
690
+ const centerX = isRight ? rightEyeCenterX : leftEyeCenterX;
691
+ const centerY = isRight ? rightEyeCenterY : leftEyeCenterY;
692
+
693
+ // Build transform string for the whole eye group
694
+ let transforms = [];
695
+
696
+ // Eye distance: move eyes apart/together from center point
697
+ if (eyeDistance !== 1.0) {
698
+ const dx = (centerX - eyeCenterX) * (eyeDistance - 1);
699
+ const dy = (centerY - eyeCenterY) * (eyeDistance - 1);
700
+ transforms.push(`translate(${dx}, ${dy})`);
701
+ }
702
+
703
+ // Eye size: scale from eye center
704
+ if (eyeSize !== 1.0) {
705
+ transforms.push(
706
+ `translate(${centerX}, ${centerY}) scale(${eyeSize}) translate(${-centerX}, ${-centerY})`
707
+ );
708
+ }
709
+
710
+ // Eye angle: rotate from eye center
711
+ if (eyeAngle !== 0) {
712
+ transforms.push(
713
+ `translate(${centerX}, ${centerY}) rotate(${eyeAngle}) translate(${-centerX}, ${-centerY})`
714
+ );
715
+ }
716
+
717
+ if (transforms.length > 0) {
718
+ eye.setAttribute('transform', transforms.join(' '));
719
+ }
720
+
721
+ // Apply animation to the pupil only using the correct ID
722
+ const pupilId = isRight ? 'Right_Pupil' : 'Left_Pupil';
723
+ const pupil = eye.querySelector(`#${pupilId}`);
724
+ if (pupil && eyeAnimate) {
725
+ pupil.classList.add('pupil-animated');
726
+ pupil.style.animation = `eyeroll ${eyerollSpeed}s ease-in-out infinite`;
727
+ } else if (pupil) {
728
+ pupil.classList.remove('pupil-animated');
729
+ pupil.style.animation = '';
730
+ }
731
+ });
732
+ }
733
+
734
+ function render() {
735
+ const userCount = parseInt(document.getElementById('scoop-count').value);
736
+ const colorMode = document.getElementById('color-mode').value;
737
+
738
+ // Update body class based on color mode
739
+ document.body.classList.remove('light', 'dark');
740
+ if (colorMode === 'mono-dark') {
741
+ document.body.classList.add('dark');
742
+ } else {
743
+ document.body.classList.add('light');
744
+ }
803
745
 
804
746
  const params = {
805
747
  offset: parseInt(document.getElementById('scoop-offset').value),
@@ -809,71 +751,234 @@ async function generateAllVariants() {
809
751
  conePush: parseInt(document.getElementById('cone-push').value),
810
752
  midrangeScale: parseFloat(document.getElementById('midrange-scale').value),
811
753
  midrangeY: parseInt(document.getElementById('midrange-y').value),
812
- eyeAnimate: false,
754
+ eyeAnimate: document.getElementById('eye-animate').checked,
813
755
  eyerollSpeed: parseFloat(document.getElementById('eyeroll-speed').value),
814
756
  eyeDistance: parseFloat(document.getElementById('eye-distance').value),
815
757
  eyeSize: parseFloat(document.getElementById('eye-size').value),
816
758
  eyeAngle: parseFloat(document.getElementById('eye-angle').value),
817
- colorMode: 'color'
759
+ colorMode: colorMode,
818
760
  };
819
761
 
820
- renderLogo(tempContainer, count, params);
762
+ // Update label
763
+ document.getElementById('label-user').textContent =
764
+ `${userCount} Scoop${userCount !== 1 ? 's' : ''}`;
821
765
 
822
- const svgString = getSVGString(tempContainer);
823
- if (svgString) {
824
- const pngBlob = await svgToPNG(svgString, size);
825
- const filename = `sliccy-color-${count}scoops-${size}x${size}.png`;
826
- downloadFile(pngBlob, filename);
827
- totalFiles++;
766
+ // Render all three variants
767
+ renderLogo(document.getElementById('logo-container-0'), 0, params);
768
+ renderLogo(document.getElementById('logo-container-user'), userCount, params);
769
+ renderLogo(document.getElementById('logo-container-10'), 10, params);
770
+ }
771
+
772
+ // Generate SVG string from container
773
+ function getSVGString(container) {
774
+ const svg = container.querySelector('svg');
775
+ if (!svg) return null;
776
+
777
+ const serializer = new XMLSerializer();
778
+ let svgString = serializer.serializeToString(svg);
779
+
780
+ // Add XML declaration
781
+ svgString = '<?xml version="1.0" encoding="UTF-8"?>\n' + svgString;
782
+
783
+ return svgString;
784
+ }
785
+
786
+ // Convert SVG to PNG (square)
787
+ function svgToPNG(svgString, size) {
788
+ return new Promise((resolve, reject) => {
789
+ const img = new Image();
790
+ const canvas = document.createElement('canvas');
791
+ canvas.width = size;
792
+ canvas.height = size;
793
+ const ctx = canvas.getContext('2d');
794
+
795
+ img.onload = () => {
796
+ ctx.drawImage(img, 0, 0, size, size);
797
+ canvas.toBlob((blob) => {
798
+ resolve(blob);
799
+ }, 'image/png');
800
+ };
801
+
802
+ img.onerror = reject;
828
803
 
829
- await new Promise(resolve => setTimeout(resolve, 100));
804
+ const blob = new Blob([svgString], { type: 'image/svg+xml' });
805
+ const url = URL.createObjectURL(blob);
806
+ img.src = url;
807
+ });
808
+ }
809
+
810
+ // Download a file
811
+ function downloadFile(blob, filename) {
812
+ const url = URL.createObjectURL(blob);
813
+ const a = document.createElement('a');
814
+ a.href = url;
815
+ a.download = filename;
816
+ document.body.appendChild(a);
817
+ a.click();
818
+ document.body.removeChild(a);
819
+ URL.revokeObjectURL(url);
820
+ }
821
+
822
+ // Generate all variants
823
+ async function generateAllVariants() {
824
+ const statusEl = document.getElementById('export-status');
825
+ statusEl.textContent = 'Generating variants...';
826
+
827
+ const modes = ['mono-light', 'mono-dark', 'color'];
828
+ const scoopCounts = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
829
+ const pngSizes = [16, 32, 48, 128];
830
+
831
+ // Save current state
832
+ const originalMode = document.getElementById('color-mode').value;
833
+ const originalCount = parseInt(document.getElementById('scoop-count').value);
834
+
835
+ // Create temporary container for rendering
836
+ const tempContainer = document.createElement('div');
837
+ tempContainer.style.position = 'absolute';
838
+ tempContainer.style.left = '-9999px';
839
+ tempContainer.style.width = '300px';
840
+ tempContainer.style.height = '300px';
841
+ document.body.appendChild(tempContainer);
842
+
843
+ let totalFiles = 0;
844
+
845
+ try {
846
+ // Generate SVG variants (3 modes × 11 scoop counts = 33 files)
847
+ for (const mode of modes) {
848
+ for (const count of scoopCounts) {
849
+ statusEl.textContent = `Generating SVG: ${mode}, ${count} scoops...`;
850
+
851
+ // Set mode and count
852
+ document.getElementById('color-mode').value = mode;
853
+ document.getElementById('scoop-count').value = count;
854
+ generateColorControls();
855
+
856
+ // Get current params
857
+ const params = {
858
+ offset: parseInt(document.getElementById('scoop-offset').value),
859
+ scaleDecay: parseFloat(document.getElementById('scoop-scale').value),
860
+ wobble: parseInt(document.getElementById('scoop-wobble').value),
861
+ angularWobble: parseInt(document.getElementById('angular-wobble').value),
862
+ conePush: parseInt(document.getElementById('cone-push').value),
863
+ midrangeScale: parseFloat(document.getElementById('midrange-scale').value),
864
+ midrangeY: parseInt(document.getElementById('midrange-y').value),
865
+ eyeAnimate: false, // No animation in exports
866
+ eyerollSpeed: parseFloat(document.getElementById('eyeroll-speed').value),
867
+ eyeDistance: parseFloat(document.getElementById('eye-distance').value),
868
+ eyeSize: parseFloat(document.getElementById('eye-size').value),
869
+ eyeAngle: parseFloat(document.getElementById('eye-angle').value),
870
+ colorMode: mode,
871
+ };
872
+
873
+ // Render to temp container
874
+ renderLogo(tempContainer, count, params);
875
+
876
+ // Get SVG string
877
+ const svgString = getSVGString(tempContainer);
878
+ if (svgString) {
879
+ const blob = new Blob([svgString], { type: 'image/svg+xml' });
880
+ const filename = `sliccy-${mode}-${count}scoops.svg`;
881
+ downloadFile(blob, filename);
882
+ totalFiles++;
883
+
884
+ // Small delay to avoid overwhelming the browser
885
+ await new Promise((resolve) => setTimeout(resolve, 100));
886
+ }
887
+ }
888
+ }
889
+
890
+ // Generate PNG variants (only from color mode, 11 counts × 4 sizes = 44 files)
891
+ document.getElementById('color-mode').value = 'color';
892
+ generateColorControls();
893
+
894
+ for (const count of scoopCounts) {
895
+ for (const size of pngSizes) {
896
+ statusEl.textContent = `Generating PNG: ${count} scoops, ${size}x${size}...`;
897
+
898
+ const params = {
899
+ offset: parseInt(document.getElementById('scoop-offset').value),
900
+ scaleDecay: parseFloat(document.getElementById('scoop-scale').value),
901
+ wobble: parseInt(document.getElementById('scoop-wobble').value),
902
+ angularWobble: parseInt(document.getElementById('angular-wobble').value),
903
+ conePush: parseInt(document.getElementById('cone-push').value),
904
+ midrangeScale: parseFloat(document.getElementById('midrange-scale').value),
905
+ midrangeY: parseInt(document.getElementById('midrange-y').value),
906
+ eyeAnimate: false,
907
+ eyerollSpeed: parseFloat(document.getElementById('eyeroll-speed').value),
908
+ eyeDistance: parseFloat(document.getElementById('eye-distance').value),
909
+ eyeSize: parseFloat(document.getElementById('eye-size').value),
910
+ eyeAngle: parseFloat(document.getElementById('eye-angle').value),
911
+ colorMode: 'color',
912
+ };
913
+
914
+ renderLogo(tempContainer, count, params);
915
+
916
+ const svgString = getSVGString(tempContainer);
917
+ if (svgString) {
918
+ const pngBlob = await svgToPNG(svgString, size);
919
+ const filename = `sliccy-color-${count}scoops-${size}x${size}.png`;
920
+ downloadFile(pngBlob, filename);
921
+ totalFiles++;
922
+
923
+ await new Promise((resolve) => setTimeout(resolve, 100));
924
+ }
925
+ }
926
+ }
927
+
928
+ statusEl.textContent = `✅ Generated ${totalFiles} files! (33 SVGs + 44 PNGs)`;
929
+ } catch (error) {
930
+ statusEl.textContent = `❌ Error: ${error.message}`;
931
+ console.error(error);
932
+ } finally {
933
+ // Restore original state
934
+ document.getElementById('color-mode').value = originalMode;
935
+ document.getElementById('scoop-count').value = originalCount;
936
+ generateColorControls();
937
+ render();
938
+
939
+ // Remove temp container
940
+ document.body.removeChild(tempContainer);
830
941
  }
831
942
  }
832
- }
833
-
834
- statusEl.textContent = `✅ Generated ${totalFiles} files! (33 SVGs + 44 PNGs)`;
835
-
836
- } catch (error) {
837
- statusEl.textContent = `❌ Error: ${error.message}`;
838
- console.error(error);
839
- } finally {
840
- // Restore original state
841
- document.getElementById('color-mode').value = originalMode;
842
- document.getElementById('scoop-count').value = originalCount;
843
- generateColorControls();
844
- render();
845
-
846
- // Remove temp container
847
- document.body.removeChild(tempContainer);
848
- }
849
- }
850
-
851
- // Event listeners
852
- document.getElementById('color-mode').addEventListener('change', () => {
853
- generateColorControls();
854
- render();
855
- });
856
-
857
- ['scoop-count', 'scoop-offset', 'scoop-scale', 'scoop-wobble', 'angular-wobble', 'cone-push', 'midrange-scale', 'midrange-y', 'eyeroll-speed', 'eye-distance', 'eye-size', 'eye-angle'].forEach(id => {
858
- const el = document.getElementById(id);
859
- el.addEventListener('input', () => {
860
- document.getElementById(id + '-val').textContent = el.value;
861
- if (id === 'scoop-count') generateColorControls();
862
- render();
863
- });
864
- });
865
-
866
- document.getElementById('eye-animate').addEventListener('change', render);
867
-
868
- document.getElementById('randomize-wobble').addEventListener('click', () => {
869
- wobbleSeeds = [];
870
- render();
871
- });
872
-
873
- document.getElementById('generate-variants').addEventListener('click', generateAllVariants);
874
-
875
- loadSVG();
876
- </script>
877
- </body>
878
- </html>
879
943
 
944
+ // Event listeners
945
+ document.getElementById('color-mode').addEventListener('change', () => {
946
+ generateColorControls();
947
+ render();
948
+ });
949
+
950
+ [
951
+ 'scoop-count',
952
+ 'scoop-offset',
953
+ 'scoop-scale',
954
+ 'scoop-wobble',
955
+ 'angular-wobble',
956
+ 'cone-push',
957
+ 'midrange-scale',
958
+ 'midrange-y',
959
+ 'eyeroll-speed',
960
+ 'eye-distance',
961
+ 'eye-size',
962
+ 'eye-angle',
963
+ ].forEach((id) => {
964
+ const el = document.getElementById(id);
965
+ el.addEventListener('input', () => {
966
+ document.getElementById(id + '-val').textContent = el.value;
967
+ if (id === 'scoop-count') generateColorControls();
968
+ render();
969
+ });
970
+ });
971
+
972
+ document.getElementById('eye-animate').addEventListener('change', render);
973
+
974
+ document.getElementById('randomize-wobble').addEventListener('click', () => {
975
+ wobbleSeeds = [];
976
+ render();
977
+ });
978
+
979
+ document.getElementById('generate-variants').addEventListener('click', generateAllVariants);
980
+
981
+ loadSVG();
982
+ </script>
983
+ </body>
984
+ </html>