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.
- package/dist/node-server/electron-controller.js +15 -6
- package/dist/node-server/electron-runtime.js +9 -10
- package/dist/node-server/index.js +6 -4
- package/dist/ui/assets/{___vite-browser-external_commonjs-proxy-BaP7fEvT.js → ___vite-browser-external_commonjs-proxy-CdTWn1k_.js} +1 -1
- package/dist/ui/assets/{bsh-watchdog-Cm3Aj_S6.js → bsh-watchdog-D42k4Edm.js} +1 -1
- package/dist/ui/assets/{index-BnDoetcE.js → index-B0xTpQYL.js} +1 -1
- package/dist/ui/assets/index-BfNzQpoL.js +131 -0
- package/dist/ui/assets/index-BlSQnyL2.css +1 -0
- package/dist/ui/assets/{index-B5Pp2r00.js → index-C0eFsJDV.js} +1 -1
- package/dist/ui/assets/{index-Xfgq0sxD.js → index-CCybN7e8.js} +1033 -718
- package/dist/ui/assets/{index-DlCxPcMj.js → index-CbC7FGzC.js} +1 -1
- package/dist/ui/assets/{index-i3ABK3vk.js → index-CsOERWYO.js} +1 -1
- package/dist/ui/assets/{index-tzzI-w2Q.js → index-D54P7vsh.js} +1 -1
- package/dist/ui/assets/{index-CTwqCjQD.js → index-DjKjcXeW.js} +1 -1
- package/dist/ui/assets/{index-DzGPKzGT.js → index-pQ08_oTm.js} +9 -9
- package/dist/ui/assets/{offscreen-client-CO5fJLap.js → offscreen-client-DmhRES2I.js} +1 -1
- package/dist/ui/assets/{sql-wasm-CgIJhlRQ.js → sql-wasm-Vb3Bc41A.js} +1 -1
- package/dist/ui/index.html +2 -2
- package/dist/ui/logos/logo-editor.html +944 -839
- package/dist/ui/logos/macos-icon.icon/icon.json +19 -24
- package/dist/ui/packages/webapp/index.html +2 -2
- package/package.json +20 -25
- package/dist/ui/assets/index-Cj6gFnz_.js +0 -131
- package/dist/ui/assets/index-D2mLc9tI.css +0 -1
|
@@ -1,805 +1,747 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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="
|
|
215
|
-
<
|
|
216
|
-
|
|
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="
|
|
219
|
-
<
|
|
220
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
<
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
300
|
-
|
|
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
|
-
|
|
763
|
-
const
|
|
764
|
-
offset
|
|
765
|
-
scaleDecay
|
|
766
|
-
wobble
|
|
767
|
-
angularWobble
|
|
768
|
-
conePush
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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
|
-
//
|
|
780
|
-
|
|
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
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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
|
-
|
|
791
|
-
|
|
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
|
-
|
|
797
|
-
|
|
798
|
-
|
|
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
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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:
|
|
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:
|
|
759
|
+
colorMode: colorMode,
|
|
818
760
|
};
|
|
819
761
|
|
|
820
|
-
|
|
762
|
+
// Update label
|
|
763
|
+
document.getElementById('label-user').textContent =
|
|
764
|
+
`${userCount} Scoop${userCount !== 1 ? 's' : ''}`;
|
|
821
765
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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
|
-
|
|
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>
|