Sylvain Filoni commited on
Commit
c2a5d87
1 Parent(s): 7bf4dfe

added anim sys files

Browse files
public/AnimSys.js ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class AnimSys {
2
+
3
+ constructor(fps, frame_limit, onions_tint, initial_frame_nb){
4
+ this.fps = fps;
5
+ this.frame_limit = frame_limit;
6
+ this.onions_tint = onions_tint;
7
+ this.initial_frame_nb = initial_frame_nb;
8
+ this.framesList = [];
9
+
10
+ this.frameGraphics = createGraphics(width, height);
11
+ this.onionGraphics = createGraphics(width, height);
12
+
13
+ this.UI = createDiv('');
14
+ this.UI.id('ui-container');
15
+
16
+ this.createFrame_btn = createButton('<i class="fa-solid fa-circle-plus"></i>');
17
+ this.createFrame_btn.mousePressed(this.create_new_frame.bind(this));
18
+ //this.createFrame_btn.parent(this.UI);
19
+ this.createFrame_btn.parent('right-panel');
20
+
21
+ this.show_onion_btn = createButton('<span class="spec-onions"><i class="fa-regular fa-circle"></i><i class="fa-solid fa-circle"></i><span>');
22
+ this.show_onion_btn.mousePressed(this.switch_onions.bind(this));
23
+ //this.show_onion_btn.parent(this.UI);
24
+ this.show_onion_btn.parent('right-panel');
25
+
26
+ this.hide_onion_btn = createButton('<span class="spec-onions"><i class="fa-regular fa-circle"></i><i class="fa-solid fa-circle"></i><span>').hide();
27
+ this.hide_onion_btn.id('hide-onion-btn');
28
+ this.hide_onion_btn.mousePressed(this.switch_onions.bind(this));
29
+ //this.hide_onion_btn.parent(this.UI);
30
+ this.hide_onion_btn.parent('right-panel');
31
+
32
+ this.clear_frame_btn = createButton('<i class="fa-solid fa-hand-sparkles"></i>');
33
+ this.clear_frame_btn.mousePressed(this.clear_frame.bind(this));
34
+ //this.clear_frame_btn.parent(this.UI);
35
+ this.clear_frame_btn.parent('left-panel');
36
+
37
+ this.play_btn = createButton('<i class="fa-solid fa-play"></i>');
38
+ this.play_btn.id('play-btn');
39
+ this.play_btn.mousePressed(this.play_anim.bind(this));
40
+ //this.play_btn.parent(this.UI);
41
+ this.play_btn.parent('right-panel');
42
+
43
+ this.stop_btn = createButton('<i class="fa-solid fa-pause"></i>').hide();
44
+ this.stop_btn.id('stop-btn');
45
+ this.stop_btn.mousePressed(this.play_anim.bind(this));
46
+ //this.stop_btn.parent(this.UI);
47
+ this.stop_btn.parent('right-panel');
48
+
49
+ this.timeline = createDiv('timeline');
50
+ this.timeline.id('timeline');
51
+ this.timeline.parent('timeline-ctn');
52
+
53
+ this.frame_displayed = 0;
54
+
55
+ this.isPlaying = false;
56
+ this.play_interval = null;
57
+
58
+ this.showOnions = false;
59
+
60
+ }
61
+
62
+ // -----------------------------------------
63
+ // -----------------------------------------
64
+
65
+
66
+ update_frame_list(){
67
+ //flush timeline elements
68
+ this.timeline.html('');
69
+ //insert frames + new one in timeline
70
+ for (let [i,frame] of this.framesList.entries()){
71
+ let frameDiv = createDiv(i+1);
72
+ frameDiv.id('frame-number-' + i);
73
+ frameDiv.class('aframe');
74
+ frameDiv.parent(this.timeline);
75
+ frameDiv.mousePressed( () =>{
76
+ this.display_frame(i);
77
+ })
78
+ redraw();
79
+ }
80
+ }
81
+
82
+ // -----------------------------------------
83
+ // -----------------------------------------
84
+
85
+
86
+ create_new_frame(data){
87
+
88
+ if(this.framesList.length == this.frame_limit){
89
+ console.warn('you cannot create more than ' + this.frame_limit + ' frames for this project')
90
+ } else {
91
+ let image64;
92
+ let diff64;
93
+ if(data){
94
+ image64 = data.image64;
95
+ } else {
96
+ this.frameGraphics.clear();
97
+ this.frameGraphics.loadPixels();
98
+ image64 = this.frameGraphics.canvas.toDataURL('image/png');
99
+ }
100
+
101
+ let new_frame = {
102
+ "img_data": image64,
103
+ "diffused_data": diff64
104
+ };
105
+ this.framesList.push(new_frame);
106
+
107
+ this.update_frame_list();
108
+
109
+ if(this.framesList.length > 0){
110
+ this.frame_displayed = this.framesList.length -1;
111
+ }
112
+
113
+ this.display_frame(this.frame_displayed);
114
+ }
115
+
116
+ }
117
+
118
+ // -----------------------------------------
119
+ // -----------------------------------------
120
+
121
+
122
+ display_frame(frame_index){
123
+
124
+ if(this.isPlaying == true){
125
+
126
+ if(frame_index == this.framesList.length - 1){
127
+ frame_index = 0;
128
+ } else {
129
+ frame_index++;
130
+ }
131
+
132
+ }
133
+
134
+ this.frame_displayed = frame_index;
135
+
136
+ let getAllDiv = document.querySelectorAll('.aframe');
137
+ getAllDiv.forEach(aframe => {
138
+ aframe.classList.remove('current-frame');
139
+ });
140
+
141
+ let getDiv = select('#frame-number-' + this.frame_displayed);
142
+ getDiv.addClass('current-frame');
143
+
144
+ if(this.framesList[frame_index].img_data !== undefined){
145
+
146
+ let display = loadImage(this.framesList[frame_index].img_data, function(){
147
+
148
+ this.frameGraphics.clear();
149
+ this.frameGraphics.image(display, 0, 0);
150
+
151
+ }.bind(this));
152
+
153
+ }
154
+
155
+
156
+ if(grAPI.show_diffused == true){
157
+
158
+ grAPI.diffusedGraphics.clear();
159
+
160
+ if(this.framesList[frame_index].diffused_data !== undefined ){
161
+
162
+ let diff_data = this.framesList[this.frame_displayed].diffused_data;
163
+
164
+ grAPI.diffusedGraphics.image(diff_data, 0, 0);
165
+
166
+ }
167
+
168
+ }
169
+
170
+
171
+ //redraw();
172
+ // ONIONS À TRAITER DANS UNE FUNCTION À PART POUR + D'EFFICACITÉ
173
+
174
+
175
+
176
+ if( (this.isPlaying == false && this.showOnions == true) || (this.isPlaying == true && this.showOnions == true) ){
177
+
178
+ let onion_index;
179
+
180
+ if (frame_index == 0){
181
+
182
+ onion_index = this.framesList.length - 1;
183
+
184
+ } else {
185
+
186
+ onion_index = frame_index - 1;
187
+
188
+ }
189
+
190
+ let displayOnions = loadImage(this.framesList[onion_index].img_data, function(){
191
+
192
+ this.onionGraphics.clear();
193
+ this.onionGraphics.tint(255, this.onions_tint);
194
+ this.onionGraphics.image(displayOnions, 0, 0);
195
+
196
+ }.bind(this));
197
+ }
198
+
199
+
200
+
201
+
202
+ setTimeout(redraw, 10)
203
+
204
+ }
205
+
206
+ // -----------------------------------------
207
+ // -----------------------------------------
208
+
209
+ update_frame(){
210
+
211
+ this.frameGraphics.loadPixels();
212
+ let image64 = this.frameGraphics.canvas.toDataURL('image/png');
213
+
214
+ let data = {"image64": image64}
215
+
216
+ if(this.framesList.length == 0){
217
+
218
+ this.create_new_frame(data);
219
+
220
+ } else {
221
+
222
+ this.framesList[this.frame_displayed].img_data = image64;
223
+
224
+
225
+ }
226
+
227
+ setTimeout(redraw, 10)
228
+ }
229
+
230
+ // -----------------------------------------
231
+ // -----------------------------------------
232
+
233
+ clear_frame(){
234
+ // add here all the graphics layer you need to clear
235
+ grAPI.diffusedGraphics.clear();
236
+ this.framesList[this.frame_displayed].diffused_data = undefined;
237
+ this.frameGraphics.clear();
238
+ this.update_frame();
239
+ //redraw();
240
+ }
241
+
242
+ // -----------------------------------------
243
+ // -----------------------------------------
244
+
245
+ switch_onions(){
246
+ if(this.showOnions == false){
247
+
248
+ this.showOnions = true;
249
+ this.show_onion_btn.hide();
250
+ this.hide_onion_btn.show();
251
+
252
+ } else {
253
+
254
+ this.showOnions = false;
255
+ this.hide_onion_btn.hide();
256
+ this.show_onion_btn.show();
257
+
258
+ }
259
+ if(this.framesList.length != 0){
260
+ setTimeout(function(){
261
+ this.display_frame(this.frame_displayed)
262
+ }.bind(this), 10);
263
+ }
264
+
265
+
266
+ }
267
+
268
+ // -----------------------------------------
269
+ // -----------------------------------------
270
+
271
+ play_anim(){
272
+ if(this.framesList.length > 0){
273
+ if (this.isPlaying == false){
274
+ if(this.showOnions == true){
275
+ this.switch_onions();
276
+ }
277
+ this.isPlaying = true;
278
+ this.play_interval = setInterval(function(){
279
+ this.display_frame(this.frame_displayed)
280
+ }.bind(this), 1000/this.fps);
281
+ this.play_btn.hide();
282
+ this.stop_btn.show();
283
+ } else {
284
+ clearInterval(this.play_interval);
285
+ this.isPlaying = false;
286
+ this.stop_btn.hide();
287
+ this.play_btn.show();
288
+
289
+ }
290
+ } else {
291
+ console.log("Create a first capture before playing")
292
+ }
293
+ }
294
+
295
+ }
public/DrawHandler.js ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class DrawHandler {
2
+
3
+ constructor(){
4
+
5
+ // create a layer that will handle drawing phase
6
+ this.drawGraphic = createGraphics(width, height);
7
+
8
+ // create an array to store all paths
9
+ this.drawings = [];
10
+
11
+ // create an array to store path's points being produced
12
+ this.currentPath = [];
13
+
14
+ // bool to check if user is drawing, pencil touching and dragged on the canvas
15
+ this.isDrawing = false;
16
+
17
+ // bool to check if user is using eraser instead
18
+ this.isErasing = false;
19
+
20
+ // set the eraser radius
21
+ this.eraserRadius = 10;
22
+
23
+ this.pencil_btn = createButton('<i class="fa-solid fa-pencil"></i>');
24
+ this.pencil_btn.mousePressed(this.pickPencil.bind(this));
25
+ this.pencil_btn.parent('left-panel');
26
+ this.pencil_active = true;
27
+ this.pencil_btn.addClass('tool-active');
28
+
29
+ this.eraser_btn = createButton('<i class="fa-solid fa-eraser"></i>');
30
+ this.eraser_btn.mousePressed(this.pickEraser.bind(this));
31
+ this.eraser_btn.parent('left-panel');
32
+ this.eraser_active = false;
33
+ }
34
+
35
+ // -----------------------------------------
36
+ // -----------------------------------------
37
+
38
+ pickPencil(){
39
+ this.isErasing = false;
40
+ if(this.pencil_active == false){
41
+ this.pencil_active = true;
42
+ this.pencil_btn.addClass('tool-active');
43
+ this.eraser_active = false;
44
+ this.eraser_btn.removeClass('tool-active');
45
+ }
46
+ }
47
+
48
+ pickEraser(){
49
+ this.isErasing = true;
50
+ if(this.eraser_active == false){
51
+ this.eraser_active = true;
52
+ this.eraser_btn.addClass('tool-active');
53
+ this.pencil_active = false;
54
+ this.pencil_btn.removeClass('tool-active');
55
+
56
+ }
57
+
58
+ }
59
+
60
+ // -----------------------------------------
61
+ // -----------------------------------------
62
+
63
+ // this function calls the trueErase() method applied on the targeted layer
64
+ mouseDragged(target_graphics){
65
+
66
+ if(Draws.isDrawing && Draws.isErasing){
67
+
68
+ this.trueErase(this.eraserRadius, target_graphics);
69
+
70
+ }
71
+
72
+ redraw();
73
+ }
74
+
75
+ // -----------------------------------------
76
+ // -----------------------------------------
77
+
78
+ // this function checks if a key is down,
79
+ // if so do corresponding task
80
+ keydown_check(){
81
+
82
+ // checks if the "E" key is down,
83
+ // if so set isErasing bool to true while "E" key is down
84
+ if (keyIsDown(69)){ // KEY E
85
+ this.pickEraser();
86
+ this.isErasing = true;
87
+
88
+ } else {
89
+ this.pickPencil();
90
+ this.isErasing = false;
91
+
92
+ }
93
+ }
94
+
95
+ // -----------------------------------------
96
+ // -----------------------------------------
97
+
98
+ startPath() {
99
+
100
+ Cursor.calculateAngle();
101
+
102
+ for (let i = 0; i < brushesPoints.length; i++) {
103
+
104
+ brushesPoints[i]
105
+ .calcPointCoordinates(mouseX,
106
+ mouseY,
107
+ Cursor.targeAngle,
108
+ Cursor.diameter
109
+ );
110
+
111
+ }
112
+
113
+
114
+ for (let i = 0; i < brushesPoints.length; i++) {
115
+ brushesPoints[i].resetPointOrigin();
116
+ }
117
+
118
+ this.isDrawing = true;
119
+ this.currentPath = [];
120
+
121
+ //console.log("——");
122
+ //console.log("You started a new path!");
123
+ this.drawings.push(this.currentPath);
124
+ //console.log("A new array of points is pushed in 'drawings'");
125
+
126
+ }
127
+
128
+ // -----------------------------------------
129
+ // -----------------------------------------
130
+
131
+ endPath(source_graphics, target_graphics) {
132
+ this.isDrawing = false;
133
+
134
+ // on affiche le nouveau drawings sur la target
135
+ target_graphics.image(source_graphics, 0, 0);
136
+ // on vide le drawings array
137
+ //this.drawings = [];
138
+ // on clear le drawGraphic
139
+ source_graphics.clear();
140
+ //on redraw
141
+ redraw();
142
+ }
143
+
144
+ trueErase(r, target){
145
+ // target is the graphics you want to erase on | e.g: className.frameGraphics
146
+ target.loadPixels();
147
+
148
+ for (let x = mouseX - r; x < mouseX + r; x++) {
149
+ for (let y = mouseY - r; y < mouseY + r; y++) {
150
+ if ((dist(x,y, mouseX, mouseY) < r) && x > 0 && x <= width) {
151
+
152
+ target.set(x,y,color(0,0));
153
+
154
+ }
155
+ }
156
+ }
157
+
158
+ target.updatePixels();
159
+
160
+ }
161
+
162
+ // -----------------------------------------
163
+ // -----------------------------------------
164
+
165
+ get_new_current_path(){
166
+ if (this.isDrawing == true && this.isErasing == false) {
167
+
168
+ this.drawGraphic.clear();
169
+
170
+ let point = {
171
+ x1: [],
172
+ y1: [],
173
+ x2: [],
174
+ y2: [],
175
+ x3: [],
176
+ y3: [],
177
+ x4: [],
178
+ y4: []
179
+ }
180
+ this.currentPath.push(point);
181
+
182
+ for (let i = 0; i < brushesPoints.length; i++) {
183
+ brushesPoints[i].shiftPointVertex()
184
+ brushesPoints[i].pushPoints(point);
185
+ }
186
+
187
+ }
188
+
189
+ }
190
+
191
+ // -----------------------------------------
192
+ // -----------------------------------------
193
+
194
+ drawLines(index, color, size, graphics){
195
+ //Shows the current drawing if there any data in drawing array
196
+
197
+ if(this.isDrawing == true && this.isErasing == false){
198
+
199
+ for (let i = 0; i < this.drawings.length; i++) {
200
+
201
+ let path = this.drawings[i];
202
+
203
+ if (path.length != 0) {
204
+ noFill();
205
+
206
+ for (let j = 0; j < path.length; j++) {
207
+
208
+
209
+ push();
210
+
211
+ graphics.beginShape();
212
+ graphics.strokeWeight(size);
213
+ graphics.noFill();
214
+ graphics.stroke(color); // A
215
+
216
+ graphics.curveVertex(path[j].x1[index], path[j].y1[index]);
217
+ graphics.curveVertex(path[j].x2[index], path[j].y2[index]);
218
+ graphics.curveVertex(path[j].x3[index], path[j].y3[index]);
219
+ graphics.curveVertex(path[j].x4[index], path[j].y4[index]);
220
+
221
+ graphics.endShape();
222
+
223
+ pop();
224
+
225
+
226
+ }
227
+ }
228
+ }
229
+ }
230
+ }
231
+
232
+ }
public/OrientedCursor.js ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class OrientedCursor{
2
+
3
+ constructor(elementID){
4
+
5
+ this.elementID = elementID;
6
+ this.tiltX = 0;
7
+ this.tiltY = 0;
8
+ this.pressure = 0;
9
+ this.diameter = 0;
10
+
11
+ this.targetAngle = 0;
12
+
13
+ this.isOnCanvas = false;
14
+ }
15
+
16
+ // -----------------------------------------
17
+ // -----------------------------------------
18
+
19
+
20
+ catchCursor(){
21
+ let getCanvas = document.getElementById(this.elementID);
22
+
23
+ getCanvas.addEventListener("pointermove", (e) => {
24
+ //console.log("pointerMove");
25
+
26
+ if (this.isOnCanvas) {
27
+ this.tiltX = e.tiltX;
28
+ this.tiltY = e.tiltY;
29
+ this.pressure = e.pressure;
30
+
31
+ //console.log(inclinationX + ' ' + inclinationY + ' ' + pressure);
32
+ }
33
+ }, false);
34
+
35
+ getCanvas.addEventListener("pointerdown", (e) => {
36
+ //console.log("pointerDown");
37
+ getCanvas.setPointerCapture(e.pointerId);
38
+ this.isOnCanvas = true;
39
+
40
+ this.tiltX = e.tiltX;
41
+ this.tiltY = e.tiltY;
42
+ this.pressure = e.pressure;
43
+
44
+ }, false);
45
+
46
+ getCanvas.addEventListener("pointerup", (e) => {
47
+ //console.log("pointerUp");
48
+
49
+ if (this.isOnCanvas) {
50
+ getCanvas.releasePointerCapture(e.pointerId);
51
+ this.isOnCanvas = false;
52
+
53
+ this.tiltX = e.tiltX;
54
+ this.tiltY = e.tiltY;
55
+ this.pressure = e.pressure;
56
+
57
+ //console.log(inclinationX + ' ' + inclinationY + ' ' + pressure);
58
+
59
+ }
60
+ }, false);
61
+ }
62
+
63
+
64
+ // -----------------------------------------
65
+ // -----------------------------------------
66
+
67
+
68
+ calculateAngle(){
69
+ this.targetAngle = atan2(this.tiltY, this.tiltX);
70
+ }
71
+
72
+
73
+ // -----------------------------------------
74
+ // -----------------------------------------
75
+
76
+
77
+ showData(){
78
+ // LIVE COORDINATES
79
+ push();
80
+ noFill();
81
+ stroke('#fff')
82
+ text('pressure: ' + this.pressure, 10, 30);
83
+ text('tilt_X: ' + this.tiltX, 10, 50);
84
+ text('tilt_Y: ' + this.tiltY, 10, 70);
85
+ text('angle arctan: ' + this.targetAngle, 10, 90);
86
+ pop();
87
+ }
88
+
89
+ // -----------------------------------------
90
+ // -----------------------------------------
91
+
92
+
93
+ mapPressure(){
94
+ this.diameter = map(this.pressure, 0, 1, 1, 3);
95
+ }
96
+
97
+ // -----------------------------------------
98
+ // -----------------------------------------
99
+
100
+
101
+ process_rotate(){
102
+ translate(mouseX, mouseY); //mouseX & mouseY
103
+ rotate(this.targetAngle);
104
+ translate(-mouseX, -mouseY); // -mouseX & -mouseY
105
+ }
106
+
107
+ // -----------------------------------------
108
+ // -----------------------------------------
109
+
110
+
111
+ showCursor(mouseX, mouseY){
112
+ // POINTER CENTER
113
+ push();
114
+ noStroke();
115
+ fill(0, 0, 0);
116
+ circle(mouseX, mouseY, 20);
117
+ pop();
118
+
119
+ // RECTANGLE SHAPE
120
+ push();
121
+ this.process_rotate()
122
+
123
+ noFill();
124
+ stroke(2)
125
+ rectMode(CENTER)
126
+ rect(mouseX, mouseY, this.diameter, 30); // reacts to pen pressure value
127
+
128
+ noStroke();
129
+ fill('yellow');
130
+ circle(mouseX, mouseY, 10); // shows the pivot point
131
+ pop();
132
+
133
+ // POINTS FROM STYLUS AT GOOD INCLINATION & PRESSURE VALUE
134
+ push();
135
+ this.process_rotate();
136
+ noFill();
137
+ stroke(1);
138
+ ellipseMode(CENTER);
139
+ circle(mouseX, mouseY + this.diameter, 10); // LEFT || WEST
140
+ circle(mouseX + this.diameter, mouseY, 10);// DOWN || SOUTH
141
+ circle(mouseX, mouseY - this.diameter, 10); // RIGHT || EAST
142
+ circle(mouseX - this.diameter, mouseY, 10); // UP || NORTH
143
+
144
+
145
+ pop();
146
+
147
+ circle(mouseX + this.diameter/4 * cos(this.targetAngle), mouseY + this.diameter/4 * sin(this.targetAngle), 1)
148
+ circle(mouseX + this.diameter/4 * cos(this.targetAngle + PI), mouseY + this.diameter/4 * sin(this.targetAngle+ PI), 1)
149
+
150
+
151
+ // TILT AXIS & LENGTH
152
+ push();
153
+ fill('red');
154
+ circle(mouseX + this.tiltX, mouseY + this.tiltY, 10);
155
+
156
+ pop();
157
+
158
+ push();
159
+ fill('blue');
160
+ circle(mouseX - this.tiltX, mouseY - this.tiltY, 10);
161
+ pop();
162
+ }
163
+
164
+ }
public/apiCall.js ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class GRAPI {
2
+
3
+ constructor(URI, token){
4
+ this.URI_endpoint = URI;
5
+ this.token = token;
6
+ this.prompt_value = '';
7
+ this.prompt_input = select('#prompt-inp')
8
+ .attribute('placeholder', 'PROMPT')
9
+ .input(this.set_prompt_value.bind(this));
10
+
11
+ this.neg_prompt_value = '';
12
+ this.neg_prompt_input = select('#neg-prompt-inp')
13
+ .attribute('placeholder', 'NEGATIVE PROMPT')
14
+ .input(this.set_neg_prompt_value.bind(this));
15
+
16
+ this.call_API_btn = createButton('<i class="fa-solid fa-wand-magic-sparkles"></i>');
17
+ this.call_API_btn.mousePressed(this.call_API.bind(this));
18
+ this.call_API_btn.id('api-btn');
19
+ this.call_API_btn.parent('#inner-right')
20
+
21
+ this.running_API_btn = createButton('<i class="fa-solid fa-spinner"></i>');
22
+ this.running_API_btn.id('running-api-btn');
23
+ this.running_API_btn.parent('#inner-right');
24
+ this.running_API_btn.addClass('hide');
25
+
26
+ this.show_diffused = false;
27
+ this.showHide_diff_btn = createButton('<i class="fa-solid fa-eye-slash"></i>');
28
+ this.showHide_diff_btn.mousePressed(this.showHide_diff.bind(this));
29
+ this.showHide_diff_btn.id('show-hide-diff-btn');
30
+ this.showHide_diff_btn.parent('#inner-left');
31
+
32
+ this.API_log_txt = createDiv('logs')
33
+ this.API_log_txt.id('api-logs')
34
+
35
+ this.hiddenScribbleGraphics = createGraphics(width, height);
36
+ this.diffusedGraphics = createGraphics(width, height);
37
+ }
38
+
39
+ set_prompt_value(){
40
+ this.prompt_value = this.prompt_input.value();
41
+ }
42
+
43
+ set_neg_prompt_value(){
44
+ this.neg_prompt_value = this.neg_prompt_input.value();
45
+ }
46
+
47
+ fakeCall(){
48
+ this.running_API_btn.removeClass('hide');
49
+ this.call_API_btn.addClass('hide');
50
+ setTimeout(function(){
51
+ this.call_API_btn.removeClass('hide');
52
+ this.running_API_btn.addClass('hide');
53
+ }.bind(this), 3000)
54
+
55
+ }
56
+
57
+ call_API(){
58
+
59
+ this.running_API_btn.removeClass('hide');
60
+ this.call_API_btn.addClass('hide');
61
+
62
+ if(AS.framesList.length != 0) {
63
+
64
+ console.log(AS.frame_displayed)
65
+
66
+ this.hiddenScribbleGraphics.loadPixels();
67
+ let frame1_data = this.hiddenScribbleGraphics.canvas.toDataURL('image/png');
68
+
69
+ let inputs = [
70
+ this.prompt_value,
71
+ frame1_data
72
+ ];
73
+
74
+ this.query(inputs);
75
+
76
+ console.log("API CALLED • Waiting for a response ... ")
77
+ console.log("PROMPT: " + this.prompt_value)
78
+ //this.API_log_txt.html('API CALLED • Waiting for a response ... ')
79
+ }
80
+
81
+ }
82
+
83
+ async query(data) {
84
+
85
+ const response = await fetch(this.URI_endpoint, {
86
+ method: "POST",
87
+ body: JSON.stringify(
88
+ { "data": data } // data images to send
89
+ ),
90
+ headers: { "Content-Type": "application/json",
91
+ "Authorization": "Bearer " + this.token + "" }
92
+ })
93
+
94
+ .then(function(response) { return response.json(); })
95
+
96
+ .then(function(json_response){
97
+
98
+ console.log("got results");
99
+
100
+ //this.API_log_txt.html('got results • hit Play Anim button !')
101
+ //console.log(json_response.data);
102
+
103
+ setTimeout(function(){AS.display_frame(AS.frame_displayed)}, 100);
104
+
105
+ loadImage(json_response.data[0], function(diff_img){
106
+ AS.framesList[AS.frame_displayed].diffused_data = diff_img;
107
+ });
108
+
109
+ console.log('stored');
110
+ this.call_API_btn.removeClass('hide');
111
+ this.running_API_btn.addClass('hide');
112
+ if(this.show_diffused == false){
113
+ this.showHide_diff()
114
+ }
115
+
116
+ }.bind(this));
117
+ }
118
+
119
+ showHide_diff(){
120
+
121
+ if(this.show_diffused == false){
122
+
123
+ this.show_diffused = true;
124
+ this.showHide_diff_btn.html('<i class="fa-solid fa-eye"></i>');
125
+
126
+ } else if(this.show_diffused == true){
127
+
128
+ this.show_diffused = false;
129
+ this.showHide_diff_btn.html('<i class="fa-solid fa-eye-slash"></i>');
130
+
131
+ }
132
+
133
+ setTimeout(AS.display_frame(AS.frame_displayed), 1000);
134
+
135
+ }
136
+
137
+ }
public/brushpoint.js ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class BrushPoint{
2
+
3
+ constructor(name, dist, piPosition) {
4
+ this.name = name;
5
+ this.dist = dist;
6
+ this.piPosition = piPosition
7
+
8
+ this.px;
9
+ this.py;
10
+ this.ppx;
11
+ this.ppy;
12
+ this.sx;
13
+ this.sy;
14
+ this.pointerX;
15
+ this.pointerY;
16
+ }
17
+
18
+ // -----------------------------------------
19
+ // -----------------------------------------
20
+
21
+ calcPointCoordinates(mouseX, mouseY, angle, pressure){
22
+ this.pointerX = mouseX + (this.dist * pressure) * cos(angle + this.piPosition);
23
+ this.pointerY = mouseY + (this.dist * pressure) * sin(angle + this.piPosition);
24
+ //console.log('class: ' + this.pointerX + ' ' + this.pointerY)
25
+ }
26
+
27
+ // -----------------------------------------
28
+ // -----------------------------------------
29
+
30
+ resetPointOrigin(){
31
+ this.sx = this.pointerX;
32
+ this.sy = this.pointerY;
33
+ this.px = this.pointerX;
34
+ this.py = this.pointerY;
35
+ this.ppx = this.pointerX;
36
+ this.ppy = this.pointerY;
37
+ //console.log(this.sx, this.sy, this.px, this.py, this.ppx, this.ppy)
38
+ }
39
+
40
+ // -----------------------------------------
41
+ // -----------------------------------------
42
+
43
+ shiftPointVertex(){
44
+ this.sx = this.ppx;
45
+ this.sy = this.ppy;
46
+ this.ppx = this.px;
47
+ this.ppy = this.py;
48
+ this.px = this.pointerX;
49
+ this.py = this.pointerY;
50
+ }
51
+
52
+ // -----------------------------------------
53
+ // -----------------------------------------
54
+
55
+ pushPoints(point){
56
+ point.x1.push(this.sx)
57
+ point.y1.push(this.sy)
58
+ point.x2.push(this.ppx)
59
+ point.y2.push(this.ppy)
60
+ point.x3.push(this.px)
61
+ point.y3.push(this.py)
62
+ point.x4.push(this.pointerX)
63
+ point.y4.push(this.pointerY)
64
+ }
65
+
66
+ }
public/index.html CHANGED
@@ -1,14 +1,39 @@
1
  <!DOCTYPE html>
2
- <html>
 
 
 
 
 
 
3
 
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
7
- <meta name="viewport" content="width=device-width, initial-scale=1">
8
- <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.js"></script>
9
- <script src="https://cdn.socket.io/4.5.4/socket.io.min.js" integrity="sha384-/KNQL8Nu5gCHLqwqfQjA689Hhoqgi2S84SNUxC3roTe4EhJ9AfLkp8QiQcU8AMzI" crossorigin="anonymous"></script>
10
- </head>
11
- <body>
12
- Hello World !
13
- <script src="sketch.js"></script>
14
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
5
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/addons/p5.sound.min.js"></script>
6
+ <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css" crossorigin="anonymous">
7
+ <link rel="stylesheet" type="text/css" href="style.css">
8
+ <meta charset="utf-8" />
9
 
10
+ </head>
11
+ <body>
12
+ <div class="main-container">
13
+ <div id="left-panel"></div>
14
+ <div id="center-panel">
15
+ <h1>Simple Animation Doodle</h1>
16
+ <div id="canvas-ctn"></div>
17
+ <div id="timeline-ctn"></div>
18
+ </div>
19
+ <div id="right-panel"></div>
20
+ </div>
21
+ <div class="main-container">
22
+ <div id="ml-config-ctn">
23
+ <div id="inner-left"></div>
24
+ <div id="prompts-ctn">
25
+ <textarea id="prompt-inp" rows="2"></textarea>
26
+ <textarea id="neg-prompt-inp" rows="2"></textarea>
27
+ </div>
28
+ <div id="inner-right"></div>
29
+ </div>
30
+ </div>
31
+ <script src="apiCall.js"></script>
32
+ <script src="OrientedCursor.js"></script>
33
+ <script src="brushpoint.js"></script>
34
+ <script src="DrawHandler.js"></script>
35
+ <script src="AnimSys.js"></script>
36
+
37
+ <script src="sketch.js"></script>
38
+ </body>
39
+ </html>
public/sketch.js CHANGED
@@ -1,4 +1,20 @@
1
- let secret;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  function preload() {
3
  const socket = io();
4
  // client-side
@@ -6,19 +22,133 @@ function preload() {
6
  console.log(socket.id); // x8WIv7-mJelg7on_ALbx
7
  socket.on("hello", (arg) => {
8
  //console.log(arg)
9
- secret = arg;
10
- console.log(secret)
11
  });
12
  });
13
  }
14
 
15
  function setup() {
16
- createCanvas(400, 400);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  }
18
 
 
 
 
 
 
19
  function draw() {
20
- background(220);
21
- textSize(32);
22
- text(secret, 10, 30);
23
 
24
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // WORKING VERSION OF ANIMATION SYSTEM
2
+ // ERASER is binded to the frameGraphics, and all the drawings operations works on the drawGraphic
3
+ // Each path is displayed on the frameGraphics before being deleted to make the eraser working properly
4
+ //Systeme à reproduire pour les autres projet, c'est le plus efficace, et le plus maniable ;)
5
+
6
+ let canvas;
7
+ let Cursor;
8
+ let AS;
9
+ let Draws;
10
+ let Pencil;
11
+ let brushesPoints = [];
12
+ let show_diffused = false;
13
+
14
+
15
+ //-----------------------------------------------------
16
+ //-----------------------------------------------------
17
+
18
  function preload() {
19
  const socket = io();
20
  // client-side
 
22
  console.log(socket.id); // x8WIv7-mJelg7on_ALbx
23
  socket.on("hello", (arg) => {
24
  //console.log(arg)
25
+ const space_uri = arg[0];
26
+ const hf_tkn = arg[1];
27
  });
28
  });
29
  }
30
 
31
  function setup() {
32
+
33
+ canvas = createCanvas(512, 512);
34
+ canvas.id('canvas');
35
+ canvas.parent('canvas-ctn');
36
+ noLoop();
37
+ pixelDensity(1);
38
+ background('white');
39
+ grAPI = new GRAPI(space_uri, hf_token)
40
+ Draws = new DrawHandler();
41
+ AS = new AnimSys(12, 12, 30, 8);
42
+
43
+ for (let i = 0; i < AS.initial_frame_nb; i++) {
44
+ AS.create_new_frame();
45
+ AS.display_frame(0);
46
+ }
47
+
48
+ Cursor = new OrientedCursor('canvas');
49
+ Cursor.catchCursor();
50
+
51
+ Pencil = new BrushPoint('pencil', 0, 0);
52
+ brushesPoints.push(Pencil);
53
+
54
+
55
+
56
+ canvas.mousePressed(function(){
57
+ Draws.startPath();
58
+ });
59
+ canvas.mouseReleased(function(){
60
+ Draws.endPath(Draws.drawGraphic, AS.frameGraphics);
61
+ Draws.drawings = [];
62
+ AS.update_frame();
63
+ });
64
+
65
+ } // END SETUP
66
+
67
+
68
+ //-----------------------------------------------------
69
+ //-----------------------------------------------------
70
+
71
+
72
+ function mouseDragged(){
73
+
74
+ Draws.mouseDragged(AS.frameGraphics);
75
+
76
  }
77
 
78
+
79
+ //-----------------------------------------------------
80
+ //-----------------------------------------------------
81
+
82
+
83
  function draw() {
 
 
 
84
 
85
+ //Draws.keydown_check();
86
+
87
+ clear(); // clear the whole canvas
88
+ background('white'); // set main canvas background to 'white'
89
+
90
+ if(Cursor.isOnCanvas == false){
91
+ Cursor.tiltX = 0;
92
+ Cursor.tiltY = 0;
93
+ }
94
+
95
+ //MAP THE PRESSURE VALUE TO VISIBLE ONES
96
+ Cursor.mapPressure();
97
+
98
+ Draws.get_new_current_path();
99
+
100
+
101
+ // ANGLE WE ARE LOOKING FOR TO KEEP TRACK OF THE STYLUS TILT
102
+ Cursor.calculateAngle()
103
+
104
+
105
+ for (let i = 0; i < brushesPoints.length; i++) {
106
+ brushesPoints[i]
107
+ .calcPointCoordinates(mouseX,
108
+ mouseY,
109
+ Cursor.targetAngle,
110
+ Cursor.diameter
111
+ );
112
+ }
113
+
114
+ // DRAWS CURRENT PATH
115
+ Draws.drawLines(0, 'black', 4, Draws.drawGraphic); // pencil
116
+
117
+ //------------
118
+
119
+ if(AS.isPlaying == false){
120
+
121
+ if(AS.showOnions == true){
122
+
123
+ image(AS.onionGraphics, 0, 0);
124
+
125
+ }
126
+
127
+ }
128
+
129
+ image(AS.frameGraphics, 0, 0)
130
+ image(Draws.drawGraphic, 0, 0)
131
+
132
+ push();
133
+ grAPI.hiddenScribbleGraphics.clear();
134
+ grAPI.hiddenScribbleGraphics.background('white');
135
+ grAPI.hiddenScribbleGraphics.image(AS.frameGraphics, 0, 0);
136
+ grAPI.hiddenScribbleGraphics.filter(INVERT)
137
+ pop();
138
+
139
+ if(grAPI.show_diffused == true){
140
+
141
+ image(grAPI.diffusedGraphics, 0, 0);
142
+ }
143
+
144
+ if(Draws.isDrawing){
145
+
146
+ //Cursor.showCursor(mouseX, mouseY);
147
+
148
+ }
149
+
150
+ // DISPLAY TECHNICAL DATA ABOUT THE CURSOR
151
+ //Cursor.showData();
152
+
153
+
154
+ } // END DRAW
public/style.css ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html, body {
2
+ margin: 0;
3
+ padding: 0;
4
+ color: white;
5
+ font-family: monospace;
6
+ }
7
+ h1{
8
+ color: white;
9
+ font-family: monospace;
10
+ text-align: center;
11
+ }
12
+
13
+ textarea{
14
+ resize: none;
15
+ outline: none;
16
+ overflow: auto;
17
+ border: none;
18
+ padding: 10px;
19
+ width: 408px;
20
+ }
21
+ textarea#prompt-inp {
22
+ /*border-top-left-radius: 6px;*/
23
+ /*border-top-right-radius: 6px;*/
24
+ }
25
+
26
+ textarea#neg-prompt-inp {
27
+ /*border-bottom-left-radius: 6px;*/
28
+ /*border-bottom-right-radius: 6px;*/
29
+ }
30
+ button#show-hide-diff-btn {
31
+ border: 4px solid white;
32
+ color: white;
33
+ cursor: pointer;
34
+ background: none;
35
+ border-top-left-radius: 10px;
36
+ border-bottom-left-radius: 10px;
37
+ text-align: center;
38
+ height: 105px;
39
+ width: 105px;
40
+ margin-right: 3px;
41
+ }
42
+ button#api-btn, button#running-api-btn {
43
+ border: 4px solid white;
44
+ color: white;
45
+ cursor: pointer;
46
+ background: none;
47
+ border-top-right-radius: 10px;
48
+ border-bottom-right-radius: 10px;
49
+ text-align: center;
50
+ height: 105px;
51
+ width: 105px;
52
+ margin-left: 3px;
53
+ }
54
+ div.main-container{
55
+ display: flex;
56
+ justify-content: center;
57
+ }
58
+ div#left-panel, div#right-panel{
59
+ /*border: 1px solid white;*/
60
+ border-radius: 10px;
61
+ display: flex;
62
+ flex-direction: column;
63
+ justify-content: center;
64
+ align-items: center;
65
+ height: auto;
66
+ margin: 20px;
67
+ width: 100px;
68
+ }
69
+ i{
70
+ font-size: 30px;
71
+ }
72
+ span.spec-onions {
73
+ display: flex;
74
+ justify-content: center;
75
+ align-items: center;
76
+ }
77
+
78
+ span.spec-onions > i.fa-solid.fa-circle {
79
+ margin-left: -14px;
80
+ }
81
+ button#hide-onion-btn {
82
+ border-color: #ef70ff !important;
83
+ color: #ef70ff !important;
84
+ }
85
+ button.tool-active {
86
+ border-color: #70c9ff !important;
87
+ color: #70c9ff !important;
88
+ }
89
+
90
+ div#right-panel > button, div#left-panel > button{
91
+ background: none;
92
+ border: 4px solid rgb(181,181,181);
93
+ border-radius: 10px;
94
+ color: rgb(181,181,181);
95
+ cursor: pointer;
96
+ height: 70px;
97
+ margin-bottom: 10px;
98
+ text-align: center;
99
+ width: 70px;
100
+ }
101
+
102
+ div#right-panel > button:hover, div#left-panel > button:hover{
103
+ color:white;
104
+ border-color:white;
105
+ }
106
+
107
+ button#play-btn{
108
+ border: 4px solid #6bff6f !important;
109
+ color: #6bff6f !important;
110
+ }
111
+ button#stop-btn {
112
+ border: 4px solid #ff303b !important;
113
+ color: #ff303b !important;
114
+ }
115
+ div#timeline-ctn{
116
+ align-items: center;
117
+ /*border: 1px dashed white;*/
118
+ border-radius: 10px;
119
+ display: flex;
120
+ height: 60px;
121
+ justify-content: center;
122
+ margin: 4px 0;
123
+ }
124
+ div#canvas-ctn{
125
+ /*background: white;*/
126
+ /*border: 1px solid white;*/
127
+ /*border-radius : 10px;*/
128
+ /*padding: 20px;*/
129
+ /*width: 512px;*/
130
+ }
131
+
132
+ canvas {
133
+ cursor: crosshair;
134
+ display: block;
135
+ border: 1px solid #dbdbdb;
136
+ border-radius: 6px;
137
+ }
138
+
139
+ .aframe{
140
+ display: flex;
141
+ align-items: center;
142
+ color: black;
143
+ font-family: monospace;
144
+ justify-content: center;
145
+ width: 30px;
146
+ height: 30px;
147
+ background: white;
148
+ border: 2px solid #ffffff;
149
+ border-radius: 20px;
150
+ cursor: pointer;
151
+ margin: 0 2px;
152
+ }
153
+
154
+ .current-frame{
155
+ background: #ffcf1e;
156
+ border: 2px solid #ff9d0c;
157
+ color: #422700;
158
+ font-weight: bold;
159
+ }
160
+
161
+ #timeline, #ui-container{
162
+ display: flex;
163
+ }
164
+
165
+ div#ml-config-ctn{
166
+ display: flex;
167
+ width: 512px;
168
+ justify-content: center;
169
+ margin-top: 20px;
170
+ }
171
+
172
+ button#show-hide-diff-btn {
173
+ /*display: none;*/
174
+ }
175
+
176
+ div#prompts-ctn {
177
+ display: flex;
178
+ flex-direction: column;
179
+ gap: 3px;
180
+ }
181
+
182
+ .hide{
183
+ display: none;
184
+ }
185
+
186
+ i.fa-solid.fa-spinner{
187
+ animation: rotation .8s infinite linear;
188
+ }
189
+
190
+ @keyframes rotation {
191
+ from {
192
+ transform: rotate(0deg);
193
+ }
194
+ to {
195
+ transform: rotate(359deg);
196
+ }
197
+ }