Unknown456 commited on
Commit
d6a2142
·
verified ·
1 Parent(s): 9addc4d

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +767 -19
index.html CHANGED
@@ -1,19 +1,767 @@
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>Paper Physics Simulator</title>
7
+
8
+ <!-- Import FontAwesome for Icons -->
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
+
11
+ <!-- Import Matter.js for Physics -->
12
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
13
+
14
+ <style>
15
+ :root {
16
+ --bg-color: #121214;
17
+ --panel-bg: rgba(32, 33, 36, 0.7);
18
+ --accent-color: #8257e6;
19
+ --accent-hover: #9466ff;
20
+ --text-main: #e1e1e6;
21
+ --text-muted: #a8a8b3;
22
+ --border-color: rgba(255, 255, 255, 0.1);
23
+ --font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
24
+ }
25
+
26
+ * {
27
+ box-sizing: border-box;
28
+ margin: 0;
29
+ padding: 0;
30
+ -webkit-font-smoothing: antialiased;
31
+ }
32
+
33
+ body {
34
+ font-family: var(--font-family);
35
+ background-color: var(--bg-color);
36
+ color: var(--text-main);
37
+ overflow: hidden; /* Prevent scroll on the main page */
38
+ height: 100vh;
39
+ width: 100vw;
40
+ display: flex;
41
+ flex-direction: column;
42
+ }
43
+
44
+ /* --- Header --- */
45
+ header {
46
+ height: 60px;
47
+ display: flex;
48
+ align-items: center;
49
+ justify-content: space-between;
50
+ padding: 0 24px;
51
+ background: rgba(18, 18, 20, 0.9);
52
+ border-bottom: 1px solid var(--border-color);
53
+ z-index: 10;
54
+ backdrop-filter: blur(10px);
55
+ }
56
+
57
+ .logo {
58
+ font-weight: 700;
59
+ font-size: 1.2rem;
60
+ display: flex;
61
+ align-items: center;
62
+ gap: 10px;
63
+ }
64
+
65
+ .logo i {
66
+ color: var(--accent-color);
67
+ }
68
+
69
+ .built-with {
70
+ font-size: 0.85rem;
71
+ color: var(--text-muted);
72
+ text-decoration: none;
73
+ transition: color 0.3s;
74
+ }
75
+
76
+ .built-with:hover {
77
+ color: var(--accent-color);
78
+ }
79
+
80
+ /* --- Main Layout --- */
81
+ main {
82
+ flex: 1;
83
+ position: relative;
84
+ display: flex;
85
+ height: calc(100vh - 60px);
86
+ }
87
+
88
+ /* --- Canvas Container --- */
89
+ #canvas-container {
90
+ flex: 1;
91
+ position: relative;
92
+ background: radial-gradient(circle at center, #1e1e24 0%, #121214 100%);
93
+ cursor: crosshair;
94
+ overflow: hidden;
95
+ }
96
+
97
+ canvas {
98
+ display: block;
99
+ width: 100%;
100
+ height: 100%;
101
+ }
102
+
103
+ /* --- Controls Sidebar --- */
104
+ aside {
105
+ width: 320px;
106
+ background: var(--panel-bg);
107
+ border-left: 1px solid var(--border-color);
108
+ backdrop-filter: blur(20px);
109
+ display: flex;
110
+ flex-direction: column;
111
+ overflow-y: auto;
112
+ padding: 20px;
113
+ gap: 24px;
114
+ transition: transform 0.3s ease;
115
+ }
116
+
117
+ .control-group {
118
+ display: flex;
119
+ flex-direction: column;
120
+ gap: 12px;
121
+ }
122
+
123
+ .group-header {
124
+ display: flex;
125
+ justify-content: space-between;
126
+ align-items: center;
127
+ font-size: 0.9rem;
128
+ font-weight: 600;
129
+ color: var(--text-muted);
130
+ text-transform: uppercase;
131
+ letter-spacing: 0.5px;
132
+ border-bottom: 1px solid var(--border-color);
133
+ padding-bottom: 8px;
134
+ }
135
+
136
+ /* --- UI Elements --- */
137
+ label {
138
+ font-size: 0.85rem;
139
+ display: flex;
140
+ justify-content: space-between;
141
+ }
142
+
143
+ .value-display {
144
+ color: var(--accent-color);
145
+ font-family: monospace;
146
+ }
147
+
148
+ input[type="range"] {
149
+ -webkit-appearance: none;
150
+ width: 100%;
151
+ height: 6px;
152
+ background: rgba(255,255,255,0.1);
153
+ border-radius: 3px;
154
+ outline: none;
155
+ }
156
+
157
+ input[type="range"]::-webkit-slider-thumb {
158
+ -webkit-appearance: none;
159
+ width: 16px;
160
+ height: 16px;
161
+ border-radius: 50%;
162
+ background: var(--accent-color);
163
+ cursor: pointer;
164
+ transition: background 0.2s;
165
+ }
166
+
167
+ input[type="range"]::-webkit-slider-thumb:hover {
168
+ background: var(--accent-hover);
169
+ }
170
+
171
+ .btn {
172
+ background: var(--accent-color);
173
+ color: white;
174
+ border: none;
175
+ padding: 12px;
176
+ border-radius: 8px;
177
+ font-weight: 600;
178
+ cursor: pointer;
179
+ transition: all 0.2s;
180
+ display: flex;
181
+ align-items: center;
182
+ justify-content: center;
183
+ gap: 8px;
184
+ }
185
+
186
+ .btn:hover {
187
+ background: var(--accent-hover);
188
+ transform: translateY(-1px);
189
+ }
190
+
191
+ .btn:active {
192
+ transform: translateY(1px);
193
+ }
194
+
195
+ .btn-outline {
196
+ background: transparent;
197
+ border: 1px solid var(--border-color);
198
+ color: var(--text-main);
199
+ }
200
+
201
+ .btn-outline:hover {
202
+ border-color: var(--accent-color);
203
+ color: var(--accent-color);
204
+ }
205
+
206
+ .btn-danger {
207
+ background: rgba(220, 38, 38, 0.2);
208
+ color: #ff6b6b;
209
+ border: 1px solid rgba(220, 38, 38, 0.3);
210
+ }
211
+
212
+ .btn-danger:hover {
213
+ background: rgba(220, 38, 38, 0.4);
214
+ color: #fff;
215
+ }
216
+
217
+ /* --- Toggle Switch --- */
218
+ .toggle-row {
219
+ display: flex;
220
+ justify-content: space-between;
221
+ align-items: center;
222
+ font-size: 0.9rem;
223
+ }
224
+
225
+ .switch {
226
+ position: relative;
227
+ display: inline-block;
228
+ width: 40px;
229
+ height: 20px;
230
+ }
231
+
232
+ .switch input {
233
+ opacity: 0;
234
+ width: 0;
235
+ height: 0;
236
+ }
237
+
238
+ .slider {
239
+ position: absolute;
240
+ cursor: pointer;
241
+ top: 0;
242
+ left: 0;
243
+ right: 0;
244
+ bottom: 0;
245
+ background-color: rgba(255,255,255,0.1);
246
+ transition: .4s;
247
+ border-radius: 20px;
248
+ }
249
+
250
+ .slider:before {
251
+ position: absolute;
252
+ content: "";
253
+ height: 14px;
254
+ width: 14px;
255
+ left: 3px;
256
+ bottom: 3px;
257
+ background-color: white;
258
+ transition: .4s;
259
+ border-radius: 50%;
260
+ }
261
+
262
+ input:checked + .slider {
263
+ background-color: var(--accent-color);
264
+ }
265
+
266
+ input:checked + .slider:before {
267
+ transform: translateX(20px);
268
+ }
269
+
270
+ /* --- Instructions Overlay --- */
271
+ .instructions {
272
+ position: absolute;
273
+ bottom: 20px;
274
+ left: 50%;
275
+ transform: translateX(-50%);
276
+ background: rgba(0, 0, 0, 0.6);
277
+ padding: 8px 16px;
278
+ border-radius: 20px;
279
+ font-size: 0.85rem;
280
+ color: var(--text-muted);
281
+ pointer-events: none;
282
+ user-select: none;
283
+ backdrop-filter: blur(4px);
284
+ border: 1px solid var(--border-color);
285
+ }
286
+
287
+ /* --- Mobile Responsive --- */
288
+ @media (max-width: 768px) {
289
+ main {
290
+ flex-direction: column;
291
+ }
292
+ aside {
293
+ width: 100%;
294
+ height: 40%;
295
+ border-left: none;
296
+ border-top: 1px solid var(--border-color);
297
+ }
298
+ #canvas-container {
299
+ height: 60%;
300
+ }
301
+ }
302
+ </style>
303
+ </head>
304
+ <body>
305
+
306
+ <header>
307
+ <div class="logo">
308
+ <i class="fa-solid fa-layer-group"></i>
309
+ <span>PaperLab</span>
310
+ </div>
311
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">Built with anycoder</a>
312
+ </header>
313
+
314
+ <main>
315
+ <div id="canvas-container">
316
+ <canvas id="world"></canvas>
317
+ <div class="instructions">
318
+ <i class="fa-regular fa-hand-pointer"></i> Tap or Drag anywhere to tear paper
319
+ </div>
320
+ </div>
321
+
322
+ <aside>
323
+ <!-- Physics Controls -->
324
+ <div class="control-group">
325
+ <div class="group-header">
326
+ <span>Physics Engine</span>
327
+ <i class="fa-solid fa-atom"></i>
328
+ </div>
329
+
330
+ <div class="toggle-row">
331
+ <span>Gravity</span>
332
+ <label class="switch">
333
+ <input type="checkbox" id="gravity-toggle" checked>
334
+ <span class="slider"></span>
335
+ </label>
336
+ </div>
337
+
338
+ <div class="toggle-row">
339
+ <span>Wind Force</span>
340
+ <label class="switch">
341
+ <input type="checkbox" id="wind-toggle">
342
+ <span class="slider"></span>
343
+ </label>
344
+ </div>
345
+ </div>
346
+
347
+ <!-- Tear Settings -->
348
+ <div class="control-group">
349
+ <div class="group-header">
350
+ <span>Tear Settings</span>
351
+ <i class="fa-solid fa-scissors"></i>
352
+ </div>
353
+
354
+ <label>
355
+ Rip Amount (Complexity)
356
+ <span id="val-rip" class="value-display">3</span>
357
+ </label>
358
+ <input type="range" id="rip-amount" min="1" max="10" value="3" step="1">
359
+
360
+ <label>
361
+ Extra Pieces (Chaos)
362
+ <span id="val-pieces" class="value-display">0</span>
363
+ </label>
364
+ <input type="range" id="extra-pieces" min="0" max="5" value="0" step="1">
365
+ </div>
366
+
367
+ <!-- Material Properties -->
368
+ <div class="control-group">
369
+ <div class="group-header">
370
+ <span>Material</span>
371
+ <i class="fa-solid fa-cube"></i>
372
+ </div>
373
+
374
+ <label>
375
+ Roughness (Friction)
376
+ <span id="val-friction" class="value-display">0.5</span>
377
+ </label>
378
+ <input type="range" id="friction" min="0" max="1" value="0.5" step="0.05">
379
+
380
+ <label>
381
+ Restitution (Bounciness)
382
+ <span id="val-restitution" class="value-display">0.2</span>
383
+ </label>
384
+ <input type="range" id="restitution" min="0" max="1" value="0.2" step="0.05">
385
+ </div>
386
+
387
+ <!-- Speed Control -->
388
+ <div class="control-group">
389
+ <div class="group-header">
390
+ <span>Simulation Speed</span>
391
+ <i class="fa-solid fa-gauge-high"></i>
392
+ </div>
393
+
394
+ <label>
395
+ Time Scale
396
+ <span id="val-speed" class="value-display">1.0x</span>
397
+ </label>
398
+ <input type="range" id="time-scale" min="0.1" max="2.0" value="1.0" step="0.1">
399
+ </div>
400
+
401
+ <!-- Actions -->
402
+ <div class="control-group" style="margin-top: auto;">
403
+ <button id="btn-reset" class="btn btn-danger">
404
+ <i class="fa-solid fa-trash-can"></i> Reset Scene
405
+ </button>
406
+ <button id="btn-explode" class="btn btn-outline">
407
+ <i class="fa-solid fa-wind"></i> Explode
408
+ </button>
409
+ </div>
410
+ </aside>
411
+ </main>
412
+
413
+ <script>
414
+ // --- 1. Initialization ---
415
+ const canvas = document.getElementById('world');
416
+ const container = document.getElementById('canvas-container');
417
+
418
+ // Module Aliases
419
+ const Engine = Matter.Engine,
420
+ Render = Matter.Render,
421
+ Runner = Matter.Runner,
422
+ Bodies = Matter.Bodies,
423
+ Composite = Matter.Composite,
424
+ Events = Matter.Events,
425
+ Mouse = Matter.Mouse,
426
+ MouseConstraint = Matter.MouseConstraint,
427
+ Vector = Matter.Vector,
428
+ Body = Matter.Body;
429
+
430
+ // Create Engine
431
+ const engine = Engine.create();
432
+ const world = engine.world;
433
+
434
+ // Handle High DPI Screens
435
+ const dpr = window.devicePixelRatio || 1;
436
+
437
+ function resizeCanvas() {
438
+ const width = container.clientWidth;
439
+ const height = container.clientHeight;
440
+
441
+ canvas.width = width * dpr;
442
+ canvas.height = height * dpr;
443
+
444
+ canvas.style.width = `${width}px`;
445
+ canvas.style.height = `${height}px`;
446
+
447
+ const ctx = canvas.getContext('2d');
448
+ ctx.scale(dpr, dpr);
449
+
450
+ // Keep walls on resize
451
+ updateWalls(width, height);
452
+ }
453
+
454
+ // --- 2. Scene Setup ---
455
+
456
+ let paperBody = null;
457
+ let walls = [];
458
+ let particles = []; // For explosion effects
459
+
460
+ function createPaper(width, height) {
461
+ // Remove existing paper
462
+ if (paperBody) Composite.remove(world, paperBody);
463
+
464
+ const thickness = 10;
465
+ const halfW = width / 2;
466
+ const halfH = height / 2;
467
+
468
+ // Create a static rectangle for the main sheet
469
+ paperBody = Bodies.rectangle(halfW, halfH, width, thickness, {
470
+ isStatic: true,
471
+ chamfer: { radius: 5 }, // Rounded corners
472
+ render: { fillStyle: '#ffffff' },
473
+ friction: parseFloat(document.getElementById('friction').value),
474
+ restitution: parseFloat(document.getElementById('restitution').value),
475
+ label: 'paper'
476
+ });
477
+
478
+ Composite.add(world, paperBody);
479
+ }
480
+
481
+ function updateWalls(width, height) {
482
+ // Remove old walls
483
+ if (walls.length > 0) Composite.remove(world, walls);
484
+
485
+ const wallOptions = {
486
+ isStatic: true,
487
+ render: { visible: false } // Invisible walls
488
+ };
489
+
490
+ walls = [
491
+ Bodies.rectangle(width/2, -50, width, 100, wallOptions), // Top
492
+ Bodies.rectangle(width/2, height + 50, width, 100, wallOptions), // Bottom
493
+ Bodies.rectangle(width + 50, height/2, 100, height, wallOptions), // Right
494
+ Bodies.rectangle(-50, height/2, 100, height, wallOptions) // Left
495
+ ];
496
+
497
+ Composite.add(world, walls);
498
+ }
499
+
500
+ // --- 3. Interaction (Tearing) ---
501
+
502
+ Events.on(engine, 'collisionStart', function(event) {
503
+ const pairs = event.pairs;
504
+ const ripAmount = parseInt(document.getElementById('rip-amount').value);
505
+ const extraPieces = parseInt(document.getElementById('extra-pieces').value);
506
+
507
+ // Check if any collision involves the paper
508
+ const paperCollisions = pairs.filter(pair =>
509
+ pair.bodyA.label === 'paper' || pair.bodyB.label === 'paper'
510
+ );
511
+
512
+ if (paperCollisions.length > 0) {
513
+ tearPaper(ripAmount, extraPieces);
514
+ }
515
+ });
516
+
517
+ function tearPaper(ripAmount, extraPieces) {
518
+ if (!paperBody) return;
519
+
520
+ // Get vertices
521
+ const vertices = paperBody.vertices;
522
+ const center = paperBody.position;
523
+
524
+ // Calculate area of the original paper
525
+ const area = Matter.Vertices.area(vertices);
526
+
527
+ // We want to cut out a chunk based on ripAmount
528
+ // Higher ripAmount = smaller chunk cut out
529
+ const chunkSize = area / (ripAmount + 1);
530
+
531
+ // Pick a random starting vertex
532
+ const startIndex = Math.floor(Math.random() * vertices.length);
533
+ const startVertex = vertices[startIndex];
534
+
535
+ // Find the next vertex
536
+ let nextIndex = (startIndex + 1) % vertices.length;
537
+ let nextVertex = vertices[nextIndex];
538
+
539
+ // Create a new polygon representing the "chunk" to remove
540
+ // We construct a polygon starting from startVertex, going around to nextVertex
541
+ // and adding a point slightly offset from the center to make it a chunk
542
+ const chunkVertices = [startVertex, nextVertex];
543
+
544
+ // Add a point in the middle of the paper
545
+ chunkVertices.push({ x: center.x, y: center.y });
546
+
547
+ // Create a new body from these vertices
548
+ const chunkBody = Bodies.fromVertices(center.x, center.y, [chunkVertices], {
549
+ isStatic: false,
550
+ density: 0.002,
551
+ friction: parseFloat(document.getElementById('friction').value),
552
+ restitution: parseFloat(document.getElementById('restitution').value),
553
+ chamfer: { radius: 2 }
554
+ });
555
+
556
+ if (chunkBody) {
557
+ // Add some random velocity to the chunk so it falls
558
+ Body.setVelocity(chunkBody, {
559
+ x: (Math.random() - 0.5) * 10,
560
+ y: (Math.random() - 0.5) * 10
561
+ });
562
+
563
+ // Add some extra random pieces if requested
564
+ for(let i=0; i<extraPieces; i++) {
565
+ const smallSize = 20 + Math.random() * 30;
566
+ const smallBody = Bodies.circle(
567
+ center.x + (Math.random()-0.5)*100,
568
+ center.y + (Math.random()-0.5)*100,
569
+ smallSize/2,
570
+ {
571
+ isStatic: false,
572
+ density: 0.005,
573
+ friction: parseFloat(document.getElementById('friction').value),
574
+ restitution: parseFloat(document.getElementById('restitution').value)
575
+ }
576
+ );
577
+ Body.setVelocity(smallBody, {
578
+ x: (Math.random() - 0.5) * 20,
579
+ y: (Math.random() - 0.5) * 20
580
+ });
581
+ Composite.add(world, smallBody);
582
+ }
583
+
584
+ Composite.add(world, chunkBody);
585
+
586
+ // Remove the original paper
587
+ Composite.remove(world, paperBody);
588
+ paperBody = null;
589
+ }
590
+ }
591
+
592
+ // --- 4. Rendering ---
593
+
594
+ // Custom renderer loop
595
+ function renderLoop() {
596
+ const width = container.clientWidth;
597
+ const height = container.clientHeight;
598
+
599
+ // Clear
600
+ const ctx = canvas.getContext('2d');
601
+ ctx.clearRect(0, 0, width, height);
602
+
603
+ // Draw Particles (Confetti)
604
+ particles.forEach((p, index) => {
605
+ p.x += p.vx;
606
+ p.y += p.vy;
607
+ p.life -= 0.02;
608
+
609
+ if (p.life <= 0) {
610
+ particles.splice(index, 1);
611
+ } else {
612
+ ctx.globalAlpha = p.life;
613
+ ctx.fillStyle = p.color;
614
+ ctx.beginPath();
615
+ ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
616
+ ctx.fill();
617
+ }
618
+ });
619
+ ctx.globalAlpha = 1;
620
+
621
+ // Draw Bodies
622
+ const bodies = Composite.allBodies(world);
623
+
624
+ ctx.lineWidth = 1;
625
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
626
+ ctx.fillStyle = '#ffffff';
627
+
628
+ bodies.forEach(body => {
629
+ if (body.render.visible === false) return;
630
+
631
+ ctx.beginPath();
632
+ const vertices = body.vertices;
633
+ ctx.moveTo(vertices[0].x, vertices[0].y);
634
+ for (let j = 1; j < vertices.length; j += 1) {
635
+ ctx.lineTo(vertices[j].x, vertices[j].y);
636
+ }
637
+ ctx.lineTo(vertices[0].x, vertices[0].y);
638
+ ctx.closePath();
639
+
640
+ // Fill
641
+ ctx.fill();
642
+
643
+ // Stroke (for the paper look)
644
+ ctx.stroke();
645
+ });
646
+
647
+ requestAnimationFrame(renderLoop);
648
+ }
649
+
650
+ // --- 5. Event Listeners & Controls ---
651
+
652
+ // Resize
653
+ window.addEventListener('resize', () => {
654
+ resizeCanvas();
655
+ createPaper(container.clientWidth, container.clientHeight);
656
+ });
657
+
658
+ // Initialize
659
+ resizeCanvas();
660
+ createPaper(container.clientWidth, container.clientHeight);
661
+ renderLoop();
662
+
663
+ // Controls Logic
664
+ document.getElementById('gravity-toggle').addEventListener('change', (e) => {
665
+ world.gravity.y = e.target.checked ? 1 : 0;
666
+ });
667
+
668
+ document.getElementById('wind-toggle').addEventListener('change', (e) => {
669
+ // Simple wind effect: apply force to all dynamic bodies
670
+ const windStrength = 0.05;
671
+ const bodies = Composite.allBodies(world).filter(b => !b.isStatic);
672
+
673
+ if (e.target.checked) {
674
+ const windInterval = setInterval(() => {
675
+ if (!e.target.checked) {
676
+ clearInterval(windInterval);
677
+ return;
678
+ }
679
+ bodies.forEach(b => {
680
+ Body.applyForce(b, b.position, { x: windStrength, y: 0 });
681
+ });
682
+ }, 1000);
683
+ }
684
+ });
685
+
686
+ document.getElementById('friction').addEventListener('input', (e) => {
687
+ document.getElementById('val-friction').textContent = e.target.value;
688
+ if(paperBody) paperBody.friction = parseFloat(e.target.value);
689
+ });
690
+
691
+ document.getElementById('restitution').addEventListener('input', (e) => {
692
+ document.getElementById('val-restitution').textContent = e.target.value;
693
+ if(paperBody) paperBody.restitution = parseFloat(e.target.value);
694
+ });
695
+
696
+ document.getElementById('time-scale').addEventListener('input', (e) => {
697
+ const val = parseFloat(e.target.value);
698
+ engine.timing.timeScale = val;
699
+ document.getElementById('val-speed').textContent = val.toFixed(1) + 'x';
700
+ });
701
+
702
+ document.getElementById('rip-amount').addEventListener('input', (e) => {
703
+ document.getElementById('val-rip').textContent = e.target.value;
704
+ });
705
+
706
+ document.getElementById('extra-pieces').addEventListener('input', (e) => {
707
+ document.getElementById('val-pieces').textContent = e.target.value;
708
+ });
709
+
710
+ // Buttons
711
+ document.getElementById('btn-reset').addEventListener('click', () => {
712
+ createPaper(container.clientWidth, container.clientHeight);
713
+ });
714
+
715
+ document.getElementById('btn-explode').addEventListener('click', () => {
716
+ const bodies = Composite.allBodies(world).filter(b => !b.isStatic);
717
+ bodies.forEach(b => {
718
+ const forceMagnitude = 0.05 * b.mass;
719
+ Body.applyForce(b, b.position, {
720
+ x: (b.position.x - container.clientWidth/2) * 0.001 * forceMagnitude,
721
+ y: (b.position.y - container.clientHeight/2) * 0.001 * forceMagnitude
722
+ });
723
+
724
+ // Add confetti
725
+ for(let i=0; i<5; i++) {
726
+ particles.push({
727
+ x: b.position.x,
728
+ y: b.position.y,
729
+ vx: (Math.random() - 0.5) * 10,
730
+ vy: (Math.random() - 0.5) * 10,
731
+ life: 1.0,
732
+ color: `hsl(${Math.random()*360}, 70%, 50%)`,
733
+ size: Math.random() * 3 + 1
734
+ });
735
+ }
736
+ });
737
+ });
738
+
739
+ // Mouse Control (Tear on drag)
740
+ const mouse = Mouse.create(canvas);
741
+ const mouseConstraint = MouseConstraint.create(engine, {
742
+ mouse: mouse,
743
+ constraint: {
744
+ stiffness: 0.2,
745
+ render: { visible: false }
746
+ }
747
+ });
748
+
749
+ // Allow tearing on drag by checking velocity
750
+ Events.on(mouseConstraint, 'mousedown', function(event) {
751
+ // Check if we are clicking on paper
752
+ const mousePosition = event.mouse.position;
753
+ const bodies = Composite.allBodies(world);
754
+
755
+ // Simple raycast check or point query
756
+ const found = Matter.Query.point(bodies, mousePosition);
757
+
758
+ if (found.length > 0 && found[0].label === 'paper') {
759
+ tearPaper(10, 2); // High rip on click
760
+ }
761
+ });
762
+
763
+ Composite.add(world, mouseConstraint);
764
+
765
+ </script>
766
+ </body>
767
+ </html>