arirajuns commited on
Commit
d6aa7b4
·
verified ·
1 Parent(s): bb13535

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +995 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Math Animation1
3
- emoji: 🌖
4
- colorFrom: red
5
- colorTo: indigo
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: math-animation1
3
+ emoji: 🐳
4
+ colorFrom: gray
5
+ colorTo: gray
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,995 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Math Animation Generator</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/ccapture.js/1.1.0/CCapture.all.min.js"></script>
10
+ <style>
11
+ @import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');
12
+
13
+ body {
14
+ font-family: 'Space Mono', monospace;
15
+ background-color: #0f172a;
16
+ color: #e2e8f0;
17
+ overflow-x: hidden;
18
+ }
19
+
20
+ .canvas-container {
21
+ position: relative;
22
+ width: 100%;
23
+ height: 70vh;
24
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
25
+ border-radius: 1rem;
26
+ overflow: hidden;
27
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
28
+ }
29
+
30
+ canvas {
31
+ position: absolute;
32
+ top: 0;
33
+ left: 0;
34
+ width: 100%;
35
+ height: 100%;
36
+ }
37
+
38
+ .control-panel {
39
+ background-color: #1e293b;
40
+ border-radius: 1rem;
41
+ box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
42
+ }
43
+
44
+ .recording-indicator {
45
+ animation: pulse 1.5s infinite;
46
+ }
47
+
48
+ @keyframes pulse {
49
+ 0% { opacity: 1; }
50
+ 50% { opacity: 0.5; }
51
+ 100% { opacity: 1; }
52
+ }
53
+
54
+ .gradient-text {
55
+ background: linear-gradient(90deg, #3b82f6, #8b5cf6);
56
+ -webkit-background-clip: text;
57
+ background-clip: text;
58
+ color: transparent;
59
+ }
60
+
61
+ .tab-button {
62
+ transition: all 0.3s ease;
63
+ }
64
+
65
+ .tab-button.active {
66
+ background-color: #3b82f6;
67
+ color: white;
68
+ }
69
+
70
+ input[type="range"] {
71
+ -webkit-appearance: none;
72
+ height: 6px;
73
+ background: #334155;
74
+ border-radius: 3px;
75
+ }
76
+
77
+ input[type="range"]::-webkit-slider-thumb {
78
+ -webkit-appearance: none;
79
+ width: 18px;
80
+ height: 18px;
81
+ background: #3b82f6;
82
+ border-radius: 50%;
83
+ cursor: pointer;
84
+ }
85
+
86
+ .color-picker {
87
+ -webkit-appearance: none;
88
+ -moz-appearance: none;
89
+ appearance: none;
90
+ width: 40px;
91
+ height: 40px;
92
+ border: none;
93
+ border-radius: 50%;
94
+ cursor: pointer;
95
+ background-color: transparent;
96
+ }
97
+
98
+ .color-picker::-webkit-color-swatch {
99
+ border-radius: 50%;
100
+ border: 2px solid #334155;
101
+ }
102
+
103
+ .color-picker::-moz-color-swatch {
104
+ border-radius: 50%;
105
+ border: 2px solid #334155;
106
+ }
107
+ </style>
108
+ </head>
109
+ <body class="min-h-screen p-4 md:p-8">
110
+ <div class="max-w-7xl mx-auto">
111
+ <header class="mb-8 text-center">
112
+ <h1 class="text-4xl md:text-5xl font-bold mb-2 gradient-text">Math Animation Generator</h1>
113
+ <p class="text-lg text-slate-400">Create beautiful mathematical animations and export them as MP4</p>
114
+ </header>
115
+
116
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
117
+ <div class="lg:col-span-2">
118
+ <div class="canvas-container">
119
+ <canvas id="animationCanvas"></canvas>
120
+ <div id="recordingOverlay" class="absolute top-4 right-4 hidden items-center bg-red-600 text-white px-3 py-1 rounded-full">
121
+ <span class="recording-indicator mr-2">●</span>
122
+ <span id="recordingTimer">00:60</span>
123
+ </div>
124
+ </div>
125
+ </div>
126
+
127
+ <div class="control-panel p-6">
128
+ <div class="flex space-x-2 mb-6">
129
+ <button class="tab-button active px-4 py-2 rounded-md" data-tab="presets">Presets</button>
130
+ <button class="tab-button px-4 py-2 rounded-md" data-tab="parameters">Parameters</button>
131
+ <button class="tab-button px-4 py-2 rounded-md" data-tab="export">Export</button>
132
+ </div>
133
+
134
+ <div id="presetsTab" class="tab-content">
135
+ <h3 class="text-xl font-semibold mb-4">Animation Types</h3>
136
+ <div class="grid grid-cols-2 gap-4 mb-6">
137
+ <button class="animation-preset bg-slate-700 hover:bg-slate-600 p-4 rounded-lg transition" data-preset="lissajous">
138
+ <div class="text-blue-400 font-bold">Lissajous Curves</div>
139
+ <div class="text-sm text-slate-400">Parametric equations</div>
140
+ </button>
141
+ <button class="animation-preset bg-slate-700 hover:bg-slate-600 p-4 rounded-lg transition" data-preset="fractal">
142
+ <div class="text-purple-400 font-bold">Mandelbrot Set</div>
143
+ <div class="text-sm text-slate-400">Fractal visualization</div>
144
+ </button>
145
+ <button class="animation-preset bg-slate-700 hover:bg-slate-600 p-4 rounded-lg transition" data-preset="particles">
146
+ <div class="text-green-400 font-bold">Particle System</div>
147
+ <div class="text-sm text-slate-400">Mathematical attractors</div>
148
+ </button>
149
+ <button class="animation-preset bg-slate-700 hover:bg-slate-600 p-4 rounded-lg transition" data-preset="fourier">
150
+ <div class="text-yellow-400 font-bold">Fourier Series</div>
151
+ <div class="text-sm text-slate-400">Wave decomposition</div>
152
+ </button>
153
+ </div>
154
+
155
+ <div class="mb-6">
156
+ <label class="block text-sm font-medium mb-2">Background Color</label>
157
+ <div class="flex items-center">
158
+ <input type="color" class="color-picker" id="bgColor" value="#0f172a">
159
+ <span class="ml-2 text-sm" id="bgColorValue">#0f172a</span>
160
+ </div>
161
+ </div>
162
+
163
+ <div class="mb-6">
164
+ <label class="block text-sm font-medium mb-2">Foreground Color</label>
165
+ <div class="flex items-center">
166
+ <input type="color" class="color-picker" id="fgColor" value="#3b82f6">
167
+ <span class="ml-2 text-sm" id="fgColorValue">#3b82f6</span>
168
+ </div>
169
+ </div>
170
+ </div>
171
+
172
+ <div id="parametersTab" class="tab-content hidden">
173
+ <h3 class="text-xl font-semibold mb-4">Animation Parameters</h3>
174
+
175
+ <div id="lissajousParams" class="space-y-4">
176
+ <div>
177
+ <label class="block text-sm font-medium mb-1">Frequency X</label>
178
+ <input type="range" min="1" max="10" step="0.1" value="3" class="w-full" id="freqX">
179
+ <div class="flex justify-between text-xs text-slate-400">
180
+ <span>1</span>
181
+ <span id="freqXValue">3</span>
182
+ <span>10</span>
183
+ </div>
184
+ </div>
185
+
186
+ <div>
187
+ <label class="block text-sm font-medium mb-1">Frequency Y</label>
188
+ <input type="range" min="1" max="10" step="0.1" value="2" class="w-full" id="freqY">
189
+ <div class="flex justify-between text-xs text-slate-400">
190
+ <span>1</span>
191
+ <span id="freqYValue">2</span>
192
+ <span>10</span>
193
+ </div>
194
+ </div>
195
+
196
+ <div>
197
+ <label class="block text-sm font-medium mb-1">Phase Shift</label>
198
+ <input type="range" min="0" max="6.28" step="0.01" value="0" class="w-full" id="phaseShift">
199
+ <div class="flex justify-between text-xs text-slate-400">
200
+ <span>0</span>
201
+ <span id="phaseShiftValue">0</span>
202
+ <span>6.28</span>
203
+ </div>
204
+ </div>
205
+ </div>
206
+
207
+ <div id="fractalParams" class="space-y-4 hidden">
208
+ <div>
209
+ <label class="block text-sm font-medium mb-1">Iterations</label>
210
+ <input type="range" min="10" max="200" step="1" value="100" class="w-full" id="iterations">
211
+ <div class="flex justify-between text-xs text-slate-400">
212
+ <span>10</span>
213
+ <span id="iterationsValue">100</span>
214
+ <span>200</span>
215
+ </div>
216
+ </div>
217
+
218
+ <div>
219
+ <label class="block text-sm font-medium mb-1">Zoom</label>
220
+ <input type="range" min="0.1" max="5" step="0.1" value="1" class="w-full" id="zoom">
221
+ <div class="flex justify-between text-xs text-slate-400">
222
+ <span>0.1</span>
223
+ <span id="zoomValue">1</span>
224
+ <span>5</span>
225
+ </div>
226
+ </div>
227
+
228
+ <div>
229
+ <label class="block text-sm font-medium mb-1">Offset X</label>
230
+ <input type="range" min="-2" max="2" step="0.01" value="0" class="w-full" id="offsetX">
231
+ <div class="flex justify-between text-xs text-slate-400">
232
+ <span>-2</span>
233
+ <span id="offsetXValue">0</span>
234
+ <span>2</span>
235
+ </div>
236
+ </div>
237
+ </div>
238
+
239
+ <div id="particlesParams" class="space-y-4 hidden">
240
+ <div>
241
+ <label class="block text-sm font-medium mb-1">Particle Count</label>
242
+ <input type="range" min="10" max="500" step="10" value="100" class="w-full" id="particleCount">
243
+ <div class="flex justify-between text-xs text-slate-400">
244
+ <span>10</span>
245
+ <span id="particleCountValue">100</span>
246
+ <span>500</span>
247
+ </div>
248
+ </div>
249
+
250
+ <div>
251
+ <label class="block text-sm font-medium mb-1">Attractor Strength</label>
252
+ <input type="range" min="0.1" max="2" step="0.1" value="0.5" class="w-full" id="attractorStrength">
253
+ <div class="flex justify-between text-xs text-slate-400">
254
+ <span>0.1</span>
255
+ <span id="attractorStrengthValue">0.5</span>
256
+ <span>2</span>
257
+ </div>
258
+ </div>
259
+
260
+ <div>
261
+ <label class="block text-sm font-medium mb-1">Particle Size</label>
262
+ <input type="range" min="1" max="10" step="0.5" value="3" class="w-full" id="particleSize">
263
+ <div class="flex justify-between text-xs text-slate-400">
264
+ <span>1</span>
265
+ <span id="particleSizeValue">3</span>
266
+ <span>10</span>
267
+ </div>
268
+ </div>
269
+ </div>
270
+
271
+ <div id="fourierParams" class="space-y-4 hidden">
272
+ <div>
273
+ <label class="block text-sm font-medium mb-1">Harmonics</label>
274
+ <input type="range" min="1" max="20" step="1" value="5" class="w-full" id="harmonics">
275
+ <div class="flex justify-between text-xs text-slate-400">
276
+ <span>1</span>
277
+ <span id="harmonicsValue">5</span>
278
+ <span>20</span>
279
+ </div>
280
+ </div>
281
+
282
+ <div>
283
+ <label class="block text-sm font-medium mb-1">Wave Type</label>
284
+ <select class="w-full bg-slate-700 border border-slate-600 rounded-md px-3 py-2 text-sm" id="waveType">
285
+ <option value="sine">Sine</option>
286
+ <option value="square">Square</option>
287
+ <option value="sawtooth">Sawtooth</option>
288
+ <option value="triangle">Triangle</option>
289
+ </select>
290
+ </div>
291
+
292
+ <div>
293
+ <label class="block text-sm font-medium mb-1">Animation Speed</label>
294
+ <input type="range" min="0.1" max="2" step="0.1" value="1" class="w-full" id="fourierSpeed">
295
+ <div class="flex justify-between text-xs text-slate-400">
296
+ <span>0.1</span>
297
+ <span id="fourierSpeedValue">1</span>
298
+ <span>2</span>
299
+ </div>
300
+ </div>
301
+ </div>
302
+ </div>
303
+
304
+ <div id="exportTab" class="tab-content hidden">
305
+ <h3 class="text-xl font-semibold mb-4">Export Settings</h3>
306
+
307
+ <div class="mb-6">
308
+ <label class="block text-sm font-medium mb-2">Duration (seconds)</label>
309
+ <input type="number" min="1" max="120" value="60" class="w-full bg-slate-700 border border-slate-600 rounded-md px-3 py-2" id="exportDuration">
310
+ </div>
311
+
312
+ <div class="mb-6">
313
+ <label class="block text-sm font-medium mb-2">Resolution</label>
314
+ <select class="w-full bg-slate-700 border border-slate-600 rounded-md px-3 py-2 text-sm" id="exportResolution">
315
+ <option value="720">720p (HD)</option>
316
+ <option value="1080" selected>1080p (Full HD)</option>
317
+ <option value="1440">1440p (QHD)</option>
318
+ <option value="2160">2160p (4K)</option>
319
+ </select>
320
+ </div>
321
+
322
+ <div class="mb-6">
323
+ <label class="block text-sm font-medium mb-2">Frame Rate</label>
324
+ <select class="w-full bg-slate-700 border border-slate-600 rounded-md px-3 py-2 text-sm" id="exportFramerate">
325
+ <option value="24">24 FPS (Cinematic)</option>
326
+ <option value="30" selected>30 FPS (Standard)</option>
327
+ <option value="60">60 FPS (Smooth)</option>
328
+ </select>
329
+ </div>
330
+
331
+ <button id="startExport" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded-md transition">
332
+ Export Animation as MP4
333
+ </button>
334
+
335
+ <div id="exportProgress" class="mt-4 hidden">
336
+ <div class="flex justify-between text-sm mb-1">
337
+ <span>Export Progress</span>
338
+ <span id="exportPercent">0%</span>
339
+ </div>
340
+ <div class="w-full bg-slate-700 rounded-full h-2.5">
341
+ <div id="exportProgressBar" class="bg-blue-600 h-2.5 rounded-full" style="width: 0%"></div>
342
+ </div>
343
+ </div>
344
+ </div>
345
+ </div>
346
+ </div>
347
+
348
+ <footer class="mt-12 text-center text-sm text-slate-500">
349
+ <p>Math Animation Generator | Created with Anime.js and CCapture.js</p>
350
+ </footer>
351
+ </div>
352
+
353
+ <script>
354
+ // DOM Elements
355
+ const canvas = document.getElementById('animationCanvas');
356
+ const ctx = canvas.getContext('2d');
357
+ const recordingOverlay = document.getElementById('recordingOverlay');
358
+ const recordingTimer = document.getElementById('recordingTimer');
359
+
360
+ // Animation state
361
+ let currentAnimation = 'lissajous';
362
+ let animationRunning = false;
363
+ let animationId = null;
364
+ let particles = [];
365
+ let bgColor = '#0f172a';
366
+ let fgColor = '#3b82f6';
367
+
368
+ // Export state
369
+ let capturer = null;
370
+ let exportStartTime = 0;
371
+ let exportDuration = 60;
372
+ let isExporting = false;
373
+
374
+ // Initialize canvas size
375
+ function initCanvas() {
376
+ const container = document.querySelector('.canvas-container');
377
+ canvas.width = container.clientWidth;
378
+ canvas.height = container.clientHeight;
379
+ }
380
+
381
+ // Animation presets
382
+ const animations = {
383
+ lissajous: {
384
+ freqX: 3,
385
+ freqY: 2,
386
+ phaseShift: 0,
387
+ points: [],
388
+ history: [],
389
+ maxHistory: 100
390
+ },
391
+ fractal: {
392
+ iterations: 100,
393
+ zoom: 1,
394
+ offsetX: 0,
395
+ offsetY: 0
396
+ },
397
+ particles: {
398
+ count: 100,
399
+ size: 3,
400
+ attractorStrength: 0.5,
401
+ particles: []
402
+ },
403
+ fourier: {
404
+ harmonics: 5,
405
+ waveType: 'sine',
406
+ speed: 1,
407
+ circles: [],
408
+ path: [],
409
+ maxPath: 500
410
+ }
411
+ };
412
+
413
+ // Initialize animation
414
+ function initAnimation() {
415
+ stopAnimation();
416
+
417
+ // Clear canvas
418
+ ctx.fillStyle = bgColor;
419
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
420
+
421
+ // Initialize based on current animation type
422
+ switch(currentAnimation) {
423
+ case 'lissajous':
424
+ animations.lissajous.points = [];
425
+ animations.lissajous.history = [];
426
+ break;
427
+
428
+ case 'fractal':
429
+ // Nothing to initialize
430
+ break;
431
+
432
+ case 'particles':
433
+ initParticles();
434
+ break;
435
+
436
+ case 'fourier':
437
+ initFourier();
438
+ break;
439
+ }
440
+
441
+ startAnimation();
442
+ }
443
+
444
+ // Start animation loop
445
+ function startAnimation() {
446
+ if (animationRunning) return;
447
+
448
+ animationRunning = true;
449
+ let lastTime = performance.now();
450
+
451
+ function animate(currentTime) {
452
+ if (!animationRunning) return;
453
+
454
+ const deltaTime = (currentTime - lastTime) / 1000;
455
+ lastTime = currentTime;
456
+
457
+ // Clear canvas
458
+ ctx.fillStyle = bgColor;
459
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
460
+
461
+ // Update and draw based on current animation
462
+ switch(currentAnimation) {
463
+ case 'lissajous':
464
+ updateLissajous(deltaTime);
465
+ break;
466
+
467
+ case 'fractal':
468
+ drawFractal();
469
+ break;
470
+
471
+ case 'particles':
472
+ updateParticles(deltaTime);
473
+ break;
474
+
475
+ case 'fourier':
476
+ updateFourier(deltaTime);
477
+ break;
478
+ }
479
+
480
+ // If exporting, capture frame
481
+ if (isExporting) {
482
+ capturer.capture(canvas);
483
+
484
+ const elapsed = (currentTime - exportStartTime) / 1000;
485
+ const remaining = Math.max(0, exportDuration - elapsed);
486
+
487
+ // Update timer
488
+ const minutes = Math.floor(remaining / 60);
489
+ const seconds = Math.floor(remaining % 60);
490
+ recordingTimer.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
491
+
492
+ // Update progress
493
+ const progress = Math.min(100, (elapsed / exportDuration) * 100);
494
+ document.getElementById('exportProgressBar').style.width = `${progress}%`;
495
+ document.getElementById('exportPercent').textContent = `${Math.round(progress)}%`;
496
+
497
+ // Check if export is complete
498
+ if (elapsed >= exportDuration) {
499
+ stopExport();
500
+ }
501
+ }
502
+
503
+ animationId = requestAnimationFrame(animate);
504
+ }
505
+
506
+ animate(lastTime);
507
+ }
508
+
509
+ // Stop animation loop
510
+ function stopAnimation() {
511
+ animationRunning = false;
512
+ if (animationId) {
513
+ cancelAnimationFrame(animationId);
514
+ animationId = null;
515
+ }
516
+ }
517
+
518
+ // Lissajous curve animation
519
+ function updateLissajous(deltaTime) {
520
+ const { freqX, freqY, phaseShift, maxHistory } = animations.lissajous;
521
+ const centerX = canvas.width / 2;
522
+ const centerY = canvas.height / 2;
523
+ const radius = Math.min(canvas.width, canvas.height) * 0.4;
524
+
525
+ // Update time for animation
526
+ animations.lissajous.time = (animations.lissajous.time || 0) + deltaTime;
527
+
528
+ // Calculate current point
529
+ const t = animations.lissajous.time;
530
+ const x = centerX + Math.sin(t * freqX + phaseShift) * radius;
531
+ const y = centerY + Math.sin(t * freqY) * radius;
532
+
533
+ // Add to history
534
+ animations.lissajous.history.push({ x, y });
535
+ if (animations.lissajous.history.length > maxHistory) {
536
+ animations.lissajous.history.shift();
537
+ }
538
+
539
+ // Draw the curve
540
+ ctx.strokeStyle = fgColor;
541
+ ctx.lineWidth = 2;
542
+ ctx.beginPath();
543
+
544
+ for (let i = 0; i < animations.lissajous.history.length; i++) {
545
+ const point = animations.lissajous.history[i];
546
+ if (i === 0) {
547
+ ctx.moveTo(point.x, point.y);
548
+ } else {
549
+ ctx.lineTo(point.x, point.y);
550
+ }
551
+ }
552
+
553
+ ctx.stroke();
554
+
555
+ // Draw current point
556
+ ctx.fillStyle = fgColor;
557
+ ctx.beginPath();
558
+ ctx.arc(x, y, 5, 0, Math.PI * 2);
559
+ ctx.fill();
560
+ }
561
+
562
+ // Mandelbrot fractal animation
563
+ function drawFractal() {
564
+ const { iterations, zoom, offsetX, offsetY } = animations.fractal;
565
+ const width = canvas.width;
566
+ const height = canvas.height;
567
+
568
+ const imageData = ctx.createImageData(width, height);
569
+ const data = imageData.data;
570
+
571
+ for (let x = 0; x < width; x++) {
572
+ for (let y = 0; y < height; y++) {
573
+ // Convert pixel coordinates to complex plane coordinates
574
+ const zx = (x - width / 2) / (0.25 * zoom * width) + offsetX;
575
+ const zy = (y - height / 2) / (0.25 * zoom * height) + offsetY;
576
+
577
+ let cX = zx;
578
+ let cY = zy;
579
+ let iter = 0;
580
+
581
+ // Mandelbrot iteration
582
+ while (iter < iterations) {
583
+ const x2 = cX * cX;
584
+ const y2 = cY * cY;
585
+
586
+ if (x2 + y2 > 4) break;
587
+
588
+ const temp = x2 - y2 + zx;
589
+ cY = 2 * cX * cY + zy;
590
+ cX = temp;
591
+ iter++;
592
+ }
593
+
594
+ // Color based on iteration count
595
+ const idx = (x + y * width) * 4;
596
+ if (iter === iterations) {
597
+ // Inside the set - black
598
+ data[idx] = 0;
599
+ data[idx + 1] = 0;
600
+ data[idx + 2] = 0;
601
+ } else {
602
+ // Outside the set - color based on iterations
603
+ const norm = iter / iterations;
604
+ const r = Math.floor(norm * 255);
605
+ const g = Math.floor(norm * 120);
606
+ const b = Math.floor(norm * 255);
607
+
608
+ data[idx] = r;
609
+ data[idx + 1] = g;
610
+ data[idx + 2] = b;
611
+ }
612
+ data[idx + 3] = 255; // Alpha
613
+ }
614
+ }
615
+
616
+ ctx.putImageData(imageData, 0, 0);
617
+ }
618
+
619
+ // Particle system initialization
620
+ function initParticles() {
621
+ const { count } = animations.particles;
622
+ particles = [];
623
+
624
+ for (let i = 0; i < count; i++) {
625
+ particles.push({
626
+ x: Math.random() * canvas.width,
627
+ y: Math.random() * canvas.height,
628
+ vx: (Math.random() - 0.5) * 2,
629
+ vy: (Math.random() - 0.5) * 2,
630
+ size: animations.particles.size
631
+ });
632
+ }
633
+ }
634
+
635
+ // Particle system update
636
+ function updateParticles(deltaTime) {
637
+ const { attractorStrength } = animations.particles;
638
+ const centerX = canvas.width / 2;
639
+ const centerY = canvas.height / 2;
640
+
641
+ // Update particles
642
+ for (let i = 0; i < particles.length; i++) {
643
+ const p = particles[i];
644
+
645
+ // Calculate direction to center
646
+ const dx = centerX - p.x;
647
+ const dy = centerY - p.y;
648
+ const dist = Math.sqrt(dx * dx + dy * dy);
649
+
650
+ // Apply attractor force
651
+ if (dist > 0) {
652
+ const force = attractorStrength / dist;
653
+ p.vx += dx * force * deltaTime;
654
+ p.vy += dy * force * deltaTime;
655
+ }
656
+
657
+ // Apply velocity
658
+ p.x += p.vx;
659
+ p.y += p.vy;
660
+
661
+ // Bounce off edges
662
+ if (p.x < 0 || p.x > canvas.width) p.vx *= -0.8;
663
+ if (p.y < 0 || p.y > canvas.height) p.vy *= -0.8;
664
+
665
+ // Keep within bounds
666
+ p.x = Math.max(0, Math.min(canvas.width, p.x));
667
+ p.y = Math.max(0, Math.min(canvas.height, p.y));
668
+ }
669
+
670
+ // Draw particles
671
+ ctx.fillStyle = fgColor;
672
+ for (let i = 0; i < particles.length; i++) {
673
+ const p = particles[i];
674
+ ctx.beginPath();
675
+ ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
676
+ ctx.fill();
677
+ }
678
+ }
679
+
680
+ // Fourier series initialization
681
+ function initFourier() {
682
+ animations.fourier.circles = [];
683
+ animations.fourier.path = [];
684
+
685
+ // Create circles for each harmonic
686
+ for (let i = 0; i < animations.fourier.harmonics; i++) {
687
+ animations.fourier.circles.push({
688
+ radius: 50 / (i * 2 + 1),
689
+ frequency: (i * 2 + 1) * animations.fourier.speed,
690
+ phase: 0
691
+ });
692
+ }
693
+ }
694
+
695
+ // Fourier series update
696
+ function updateFourier(deltaTime) {
697
+ const { circles, path, maxPath, waveType } = animations.fourier;
698
+ const centerX = canvas.width / 2;
699
+ const centerY = canvas.height / 2;
700
+
701
+ // Update time
702
+ animations.fourier.time = (animations.fourier.time || 0) + deltaTime;
703
+
704
+ let x = centerX;
705
+ let y = centerY;
706
+
707
+ // Draw and update each circle
708
+ ctx.strokeStyle = fgColor;
709
+ ctx.lineWidth = 1;
710
+ ctx.fillStyle = 'transparent';
711
+
712
+ for (let i = 0; i < circles.length; i++) {
713
+ const circle = circles[i];
714
+
715
+ // Calculate position on this circle
716
+ circle.phase = animations.fourier.time * circle.frequency;
717
+
718
+ const prevX = x;
719
+ const prevY = y;
720
+
721
+ // For different wave types
722
+ let amplitude = 1;
723
+ if (waveType === 'square') {
724
+ amplitude = (i % 2 === 0) ? 1 : -1;
725
+ } else if (waveType === 'sawtooth') {
726
+ amplitude = (i % 2 === 0) ? 1/(i+1) : -1/(i+1);
727
+ } else if (waveType === 'triangle') {
728
+ amplitude = (i % 2 === 0) ? 1/((i+1)*(i+1)) : -1/((i+1)*(i+1));
729
+ }
730
+
731
+ x += Math.cos(circle.phase) * circle.radius * amplitude;
732
+ y += Math.sin(circle.phase) * circle.radius * amplitude;
733
+
734
+ // Draw circle
735
+ ctx.beginPath();
736
+ ctx.arc(prevX, prevY, circle.radius, 0, Math.PI * 2);
737
+ ctx.stroke();
738
+
739
+ // Draw line to next circle
740
+ ctx.beginPath();
741
+ ctx.moveTo(prevX, prevY);
742
+ ctx.lineTo(x, y);
743
+ ctx.stroke();
744
+ }
745
+
746
+ // Add current point to path
747
+ path.push({ x, y });
748
+ if (path.length > maxPath) {
749
+ path.shift();
750
+ }
751
+
752
+ // Draw the path
753
+ ctx.strokeStyle = fgColor;
754
+ ctx.lineWidth = 2;
755
+ ctx.beginPath();
756
+
757
+ for (let i = 0; i < path.length; i++) {
758
+ if (i === 0) {
759
+ ctx.moveTo(path[i].x, path[i].y);
760
+ } else {
761
+ ctx.lineTo(path[i].x, path[i].y);
762
+ }
763
+ }
764
+
765
+ ctx.stroke();
766
+
767
+ // Draw current point
768
+ ctx.fillStyle = fgColor;
769
+ ctx.beginPath();
770
+ ctx.arc(x, y, 5, 0, Math.PI * 2);
771
+ ctx.fill();
772
+ }
773
+
774
+ // Start export process
775
+ function startExport() {
776
+ if (isExporting) return;
777
+
778
+ exportDuration = parseInt(document.getElementById('exportDuration').value);
779
+ const resolution = parseInt(document.getElementById('exportResolution').value);
780
+ const framerate = parseInt(document.getElementById('exportFramerate').value);
781
+
782
+ // Calculate dimensions while maintaining aspect ratio
783
+ const aspectRatio = canvas.width / canvas.height;
784
+ let exportWidth, exportHeight;
785
+
786
+ if (aspectRatio > 1) {
787
+ exportWidth = resolution;
788
+ exportHeight = Math.round(resolution / aspectRatio);
789
+ } else {
790
+ exportHeight = resolution;
791
+ exportWidth = Math.round(resolution * aspectRatio);
792
+ }
793
+
794
+ // Create capturer
795
+ capturer = new CCapture({
796
+ format: 'ffmpegserver',
797
+ framerate: framerate,
798
+ verbose: true,
799
+ display: true,
800
+ timeLimit: exportDuration,
801
+ frameLimit: 0,
802
+ autoSaveTime: 0,
803
+ name: `math_animation_${currentAnimation}`,
804
+ extension: '.mp4',
805
+ codec: 'libx264',
806
+ quality: 100,
807
+ workersPath: ''
808
+ });
809
+
810
+ // Start capturing
811
+ capturer.start();
812
+ isExporting = true;
813
+ exportStartTime = performance.now();
814
+
815
+ // Show recording UI
816
+ recordingOverlay.classList.remove('hidden');
817
+
818
+ // Update progress UI
819
+ document.getElementById('exportProgress').classList.remove('hidden');
820
+ document.getElementById('exportProgressBar').style.width = '0%';
821
+ document.getElementById('exportPercent').textContent = '0%';
822
+
823
+ // Disable export button
824
+ document.getElementById('startExport').disabled = true;
825
+ document.getElementById('startExport').classList.add('opacity-50');
826
+ }
827
+
828
+ // Stop export process
829
+ function stopExport() {
830
+ if (!isExporting) return;
831
+
832
+ capturer.stop();
833
+ capturer.save();
834
+ isExporting = false;
835
+
836
+ // Hide recording UI
837
+ recordingOverlay.classList.add('hidden');
838
+
839
+ // Re-enable export button
840
+ document.getElementById('startExport').disabled = false;
841
+ document.getElementById('startExport').classList.remove('opacity-50');
842
+ }
843
+
844
+ // Event listeners for UI controls
845
+ function setupEventListeners() {
846
+ // Window resize
847
+ window.addEventListener('resize', () => {
848
+ initCanvas();
849
+ initAnimation();
850
+ });
851
+
852
+ // Tab switching
853
+ document.querySelectorAll('.tab-button').forEach(button => {
854
+ button.addEventListener('click', () => {
855
+ const tab = button.dataset.tab;
856
+
857
+ // Update active tab button
858
+ document.querySelectorAll('.tab-button').forEach(btn => {
859
+ btn.classList.remove('active');
860
+ });
861
+ button.classList.add('active');
862
+
863
+ // Show corresponding tab content
864
+ document.querySelectorAll('.tab-content').forEach(content => {
865
+ content.classList.add('hidden');
866
+ });
867
+ document.getElementById(`${tab}Tab`).classList.remove('hidden');
868
+ });
869
+ });
870
+
871
+ // Animation preset buttons
872
+ document.querySelectorAll('.animation-preset').forEach(button => {
873
+ button.addEventListener('click', () => {
874
+ currentAnimation = button.dataset.preset;
875
+ initAnimation();
876
+
877
+ // Update active preset button
878
+ document.querySelectorAll('.animation-preset').forEach(btn => {
879
+ btn.classList.remove('bg-blue-600');
880
+ btn.classList.add('bg-slate-700');
881
+ });
882
+
883
+ button.classList.remove('bg-slate-700');
884
+ button.classList.add('bg-blue-600');
885
+ });
886
+ });
887
+
888
+ // Color pickers
889
+ document.getElementById('bgColor').addEventListener('input', (e) => {
890
+ bgColor = e.target.value;
891
+ document.getElementById('bgColorValue').textContent = bgColor;
892
+ initAnimation();
893
+ });
894
+
895
+ document.getElementById('fgColor').addEventListener('input', (e) => {
896
+ fgColor = e.target.value;
897
+ document.getElementById('fgColorValue').textContent = fgColor;
898
+ initAnimation();
899
+ });
900
+
901
+ // Lissajous controls
902
+ document.getElementById('freqX').addEventListener('input', (e) => {
903
+ animations.lissajous.freqX = parseFloat(e.target.value);
904
+ document.getElementById('freqXValue').textContent = animations.lissajous.freqX;
905
+ });
906
+
907
+ document.getElementById('freqY').addEventListener('input', (e) => {
908
+ animations.lissajous.freqY = parseFloat(e.target.value);
909
+ document.getElementById('freqYValue').textContent = animations.lissajous.freqY;
910
+ });
911
+
912
+ document.getElementById('phaseShift').addEventListener('input', (e) => {
913
+ animations.lissajous.phaseShift = parseFloat(e.target.value);
914
+ document.getElementById('phaseShiftValue').textContent = animations.lissajous.phaseShift.toFixed(2);
915
+ });
916
+
917
+ // Fractal controls
918
+ document.getElementById('iterations').addEventListener('input', (e) => {
919
+ animations.fractal.iterations = parseInt(e.target.value);
920
+ document.getElementById('iterationsValue').textContent = animations.fractal.iterations;
921
+ });
922
+
923
+ document.getElementById('zoom').addEventListener('input', (e) => {
924
+ animations.fractal.zoom = parseFloat(e.target.value);
925
+ document.getElementById('zoomValue').textContent = animations.fractal.zoom.toFixed(1);
926
+ });
927
+
928
+ document.getElementById('offsetX').addEventListener('input', (e) => {
929
+ animations.fractal.offsetX = parseFloat(e.target.value);
930
+ document.getElementById('offsetXValue').textContent = animations.fractal.offsetX.toFixed(2);
931
+ });
932
+
933
+ // Particle controls
934
+ document.getElementById('particleCount').addEventListener('input', (e) => {
935
+ animations.particles.count = parseInt(e.target.value);
936
+ document.getElementById('particleCountValue').textContent = animations.particles.count;
937
+ initParticles();
938
+ });
939
+
940
+ document.getElementById('attractorStrength').addEventListener('input', (e) => {
941
+ animations.particles.attractorStrength = parseFloat(e.target.value);
942
+ document.getElementById('attractorStrengthValue').textContent = animations.particles.attractorStrength.toFixed(1);
943
+ });
944
+
945
+ document.getElementById('particleSize').addEventListener('input', (e) => {
946
+ animations.particles.size = parseFloat(e.target.value);
947
+ document.getElementById('particleSizeValue').textContent = animations.particles.size.toFixed(1);
948
+
949
+ // Update existing particles
950
+ particles.forEach(p => {
951
+ p.size = animations.particles.size;
952
+ });
953
+ });
954
+
955
+ // Fourier controls
956
+ document.getElementById('harmonics').addEventListener('input', (e) => {
957
+ animations.fourier.harmonics = parseInt(e.target.value);
958
+ document.getElementById('harmonicsValue').textContent = animations.fourier.harmonics;
959
+ initFourier();
960
+ });
961
+
962
+ document.getElementById('waveType').addEventListener('change', (e) => {
963
+ animations.fourier.waveType = e.target.value;
964
+ initFourier();
965
+ });
966
+
967
+ document.getElementById('fourierSpeed').addEventListener('input', (e) => {
968
+ animations.fourier.speed = parseFloat(e.target.value);
969
+ document.getElementById('fourierSpeedValue').textContent = animations.fourier.speed.toFixed(1);
970
+
971
+ // Update circle frequencies
972
+ animations.fourier.circles.forEach((circle, i) => {
973
+ circle.frequency = (i * 2 + 1) * animations.fourier.speed;
974
+ });
975
+ });
976
+
977
+ // Export button
978
+ document.getElementById('startExport').addEventListener('click', startExport);
979
+ }
980
+
981
+ // Initialize everything
982
+ function init() {
983
+ initCanvas();
984
+ setupEventListeners();
985
+ initAnimation();
986
+
987
+ // Set initial active preset
988
+ document.querySelector('.animation-preset[data-preset="lissajous"]').click();
989
+ }
990
+
991
+ // Start the app
992
+ window.addEventListener('load', init);
993
+ </script>
994
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=arirajuns/math-animation1" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
995
+ </html>