@@ -13,6 +13,15 @@ function loadVideo(src) {
1313const physicsData = [ ] ;
1414const main = document . querySelector ( 'main' ) ;
1515
16+ // Drag and drop state management
17+ const dragState = {
18+ isDragging : false ,
19+ draggedBallIndex : - 1 ,
20+ dragOffset : { x : 0 , y : 0 } ,
21+ lastMousePos : { x : 0 , y : 0 } ,
22+ dragStartTime : 0
23+ } ;
24+
1625// Get actual viewport dimensions accounting for mobile quirks
1726function getViewportDimensions ( ) {
1827 const rect = main . getBoundingClientRect ( ) ;
@@ -22,23 +31,38 @@ function getViewportDimensions() {
2231 } ;
2332}
2433
34+ // Helper function to find which ball is at a given position
35+ function findBallAtPosition ( x , y ) {
36+ for ( let i = 0 ; i < physicsData . length ; i ++ ) {
37+ const data = physicsData [ i ] ;
38+ const ballCenterX = data . x + data . radius ;
39+ const ballCenterY = data . y + data . radius ;
40+ const distance = Math . sqrt (
41+ Math . pow ( x - ballCenterX , 2 ) +
42+ Math . pow ( y - ballCenterY , 2 )
43+ ) ;
44+
45+ // Check if click/touch is within the ball's radius
46+ if ( distance <= data . radius ) {
47+ return i ;
48+ }
49+ }
50+ return - 1 ;
51+ }
52+
53+ // Helper function to get mouse/touch position relative to main element
54+ function getRelativePosition ( clientX , clientY ) {
55+ const rect = main . getBoundingClientRect ( ) ;
56+ return {
57+ x : clientX - rect . left ,
58+ y : clientY - rect . top
59+ } ;
60+ }
61+
2562// Centralized ball size management
2663function getBallRadius ( ) {
2764 const viewport = getViewportDimensions ( ) ;
28- const minDimension = Math . min ( viewport . width , viewport . height ) ;
29-
30- // Better scaling for mobile devices
31- if ( minDimension < 400 ) {
32- // Small mobile screens
33- return Math . max ( 25 , minDimension / 12 ) ;
34- } else if ( minDimension < 800 ) {
35- // Larger mobile screens and small tablets
36- return Math . max ( 30 , minDimension / 15 ) ;
37- } else {
38- // Desktop and large tablets
39- const k = Math . round ( viewport . width / 200 ) ;
40- return viewport . width / ( 2 * k ) ;
41- }
65+ return Math . min ( viewport . width , viewport . height ) / 7.5 ; // Simplified - 2x bigger everywhere
4266}
4367
4468function setBallSize ( video , radius ) {
@@ -93,34 +117,36 @@ function updatePhysics() {
93117 const video = main . children [ i ] ;
94118 const data = physicsData [ i ] ;
95119
120+ // Skip physics updates for the ball being dragged
121+ if ( dragState . isDragging && dragState . draggedBallIndex === i ) {
122+ continue ;
123+ }
124+
96125 data . vy += 0.2 ;
97126
98127 data . x += data . vx ;
99128 data . y += data . vy ;
100129 data . r += data . vr ;
101130
102- const maxSpin = 1 ;
131+ const maxSpin = 3 ; // Increased from 1 to allow more dramatic spinning
103132 if ( data . vr > maxSpin ) data . vr = maxSpin ;
104133 if ( data . vr < - maxSpin ) data . vr = - maxSpin ;
105134
106135 const centerX = data . x + data . radius ;
107136 const centerY = data . y + data . radius ;
108137
109- const high_update = 1.05 ;
110- const low_update = 1.0 ;
111-
112- // Use viewport dimensions instead of window dimensions
138+ // Simple wall collision with spin effects
113139 if ( centerX - data . radius <= 0 || centerX + data . radius >= viewport . width ) {
114- data . vx *= - high_update ;
115- data . vy *= low_update ;
116- data . vr *= low_update ; // trencle
140+ data . vx *= - 0.9 ; // Lose energy on bounce
141+ data . vy += data . vr * 0.5 ; // Spin affects bounce direction
142+ data . vr *= 0.8 ; // Lose some spin
117143 data . x = Math . max ( 0 , Math . min ( viewport . width - data . radius * 2 , data . x ) ) ;
118144 }
119145
120146 if ( centerY - data . radius <= 0 || centerY + data . radius >= viewport . height ) {
121- data . vy *= - high_update ;
122- data . vx *= low_update ;
123- data . vr *= low_update ; // slipy
147+ data . vy *= - 0.9 ; // Lose energy on bounce
148+ data . vx += data . vr * 0.5 ; // Spin affects bounce direction
149+ data . vr *= 0.8 ; // Lose some spin
124150 data . y = Math . max ( 0 , Math . min ( viewport . height - data . radius * 2 , data . y ) ) ;
125151 }
126152
@@ -131,6 +157,11 @@ function updatePhysics() {
131157
132158 for ( let i = 0 ; i < physicsData . length ; i ++ ) {
133159 for ( let j = i + 1 ; j < physicsData . length ; j ++ ) {
160+ // Skip collision detection if either ball is being dragged
161+ if ( dragState . isDragging && ( dragState . draggedBallIndex === i || dragState . draggedBallIndex === j ) ) {
162+ continue ;
163+ }
164+
134165 const ball1 = physicsData [ i ] ;
135166 const ball2 = physicsData [ j ] ;
136167
@@ -143,59 +174,34 @@ function updatePhysics() {
143174 const dy = center2Y - center1Y ;
144175 const distance = Math . sqrt ( dx * dx + dy * dy ) ;
145176
146- const collisionDistance = ball1 . radius + ball2 . radius ;
147- if ( distance < collisionDistance ) {
148- const overlap = collisionDistance - distance ;
177+ // Simple collision detection and response
178+ if ( distance < ball1 . radius + ball2 . radius ) {
179+ const overlap = ball1 . radius + ball2 . radius - distance ;
149180 const nx = dx / distance ;
150181 const ny = dy / distance ;
151- const separationX = nx * overlap * 0.5 ;
152- const separationY = ny * overlap * 0.5 ;
153- ball1 . x -= separationX ;
154- ball1 . y -= separationY ;
155- ball2 . x += separationX ;
156- ball2 . y += separationY ;
157-
158- const speed1 = Math . sqrt ( ball1 . vx * ball1 . vx + ball1 . vy * ball1 . vy ) ;
159- const speed2 = Math . sqrt ( ball2 . vx * ball2 . vx + ball2 . vy * ball2 . vy ) ;
160- const areTouching = speed1 < 1 && speed2 < 1 ;
161-
162- if ( areTouching ) {
163- const avgSpin = ( ball1 . vr + ball2 . vr ) * 1.05 ;
164- ball1 . vr = avgSpin ;
165- ball2 . vr = avgSpin ;
166-
167- const separationForce = overlap * 0.05 ;
168- ball1 . vx -= nx * separationForce ;
169- ball1 . vy -= ny * separationForce ;
170- ball2 . vx += nx * separationForce ;
171- ball2 . vy += ny * separationForce ;
172-
173- // Apply upward velocity to the upper ball
174- const upwardForce = overlap * 0.1 ;
175- if ( ball1 . y <= ball2 . y ) {
176- // ball1 is higher
177- ball1 . vy -= upwardForce ;
178- } else {
179- // ball2 is higher
180- ball2 . vy -= upwardForce ;
181- }
182- } else {
183- // At least one ball moving fast - collision physics
184- const dvx = ball2 . vx - ball1 . vx ;
185- const dvy = ball2 . vy - ball1 . vy ;
186- const dvn = dvx * nx + dvy * ny ;
187-
188- const impulse = - dvn * 0.8 ;
189- ball1 . vx -= impulse * nx ;
190- ball1 . vy -= impulse * ny ;
191- ball2 . vx += impulse * nx ;
192- ball2 . vy += impulse * ny ;
193-
194- // Rotational effects
195- const tangentialImpulse = impulse * 0.2 ;
196- ball1 . vr += tangentialImpulse / ball1 . radius ;
197- ball2 . vr -= tangentialImpulse / ball2 . radius ;
198- }
182+
183+ // Separate balls
184+ const separation = overlap * 0.5 ;
185+ ball1 . x -= nx * separation ;
186+ ball1 . y -= ny * separation ;
187+ ball2 . x += nx * separation ;
188+ ball2 . y += ny * separation ;
189+
190+ // Simple velocity exchange
191+ const dvx = ball2 . vx - ball1 . vx ;
192+ const dvy = ball2 . vy - ball1 . vy ;
193+ const dvn = dvx * nx + dvy * ny ;
194+
195+ const impulse = dvn * 0.8 ;
196+ ball1 . vx += impulse * nx ;
197+ ball1 . vy += impulse * ny ;
198+ ball2 . vx -= impulse * nx ;
199+ ball2 . vy -= impulse * ny ;
200+
201+ // Exchange some spin
202+ const spinExchange = impulse * 0.3 ;
203+ ball1 . vr += spinExchange ;
204+ ball2 . vr -= spinExchange ;
199205 }
200206 }
201207 }
@@ -221,47 +227,81 @@ globalThis.addEventListener('orientationchange', function() {
221227 setTimeout ( updateBallRadius , 100 ) ;
222228} ) ;
223229
224- // Add touch event handling for mobile interaction
230+ // Simple touch interaction
225231function addTouchInteraction ( ) {
226232 let touchStartTime = 0 ;
227233
228234 main . addEventListener ( 'touchstart' , function ( e ) {
229- e . preventDefault ( ) ; // Prevent scrolling and zooming
235+ e . preventDefault ( ) ;
230236 touchStartTime = Date . now ( ) ;
237+
238+ if ( e . touches . length === 1 ) {
239+ const touch = e . touches [ 0 ] ;
240+ const pos = getRelativePosition ( touch . clientX , touch . clientY ) ;
241+ const ballIndex = findBallAtPosition ( pos . x , pos . y ) ;
242+
243+ if ( ballIndex !== - 1 ) {
244+ dragState . isDragging = true ;
245+ dragState . draggedBallIndex = ballIndex ;
246+ dragState . dragOffset = {
247+ x : pos . x - physicsData [ ballIndex ] . x ,
248+ y : pos . y - physicsData [ ballIndex ] . y
249+ } ;
250+
251+ physicsData [ ballIndex ] . vx = 0 ;
252+ physicsData [ ballIndex ] . vy = 0 ;
253+ physicsData [ ballIndex ] . vr = 0 ;
254+ main . children [ ballIndex ] . classList . add ( 'dragging' ) ;
255+ }
256+ }
231257 } , { passive : false } ) ;
232258
233259 main . addEventListener ( 'touchmove' , function ( e ) {
234- e . preventDefault ( ) ; // Prevent scrolling
260+ e . preventDefault ( ) ;
261+
262+ if ( dragState . isDragging && e . touches . length === 1 ) {
263+ const touch = e . touches [ 0 ] ;
264+ const pos = getRelativePosition ( touch . clientX , touch . clientY ) ;
265+ const data = physicsData [ dragState . draggedBallIndex ] ;
266+ const video = main . children [ dragState . draggedBallIndex ] ;
267+
268+ data . x = pos . x - dragState . dragOffset . x ;
269+ data . y = pos . y - dragState . dragOffset . y ;
270+
271+ const viewport = getViewportDimensions ( ) ;
272+ data . x = Math . max ( 0 , Math . min ( viewport . width - data . radius * 2 , data . x ) ) ;
273+ data . y = Math . max ( 0 , Math . min ( viewport . height - data . radius * 2 , data . y ) ) ;
274+
275+ video . style . left = data . x + 'px' ;
276+ video . style . top = data . y + 'px' ;
277+ }
235278 } , { passive : false } ) ;
236279
237280 main . addEventListener ( 'touchend' , function ( e ) {
238281 e . preventDefault ( ) ;
239- const touchDuration = Date . now ( ) - touchStartTime ;
240282
241- // If it's a quick tap (less than 200ms), add some energy to nearby balls
242- if ( touchDuration < 200 && e . changedTouches . length > 0 ) {
283+ if ( dragState . isDragging ) {
284+ const video = main . children [ dragState . draggedBallIndex ] ;
285+ video . classList . remove ( 'dragging' ) ;
286+ dragState . isDragging = false ;
287+ dragState . draggedBallIndex = - 1 ;
288+ } else if ( Date . now ( ) - touchStartTime < 200 ) {
289+ // Quick tap - push nearby balls
243290 const touch = e . changedTouches [ 0 ] ;
244- const rect = main . getBoundingClientRect ( ) ;
245- const touchX = touch . clientX - rect . left ;
246- const touchY = touch . clientY - rect . top ;
291+ const pos = getRelativePosition ( touch . clientX , touch . clientY ) ;
247292
248- // Find balls near the touch point and give them a little push
249293 for ( let i = 0 ; i < physicsData . length ; i ++ ) {
250294 const data = physicsData [ i ] ;
251- const ballCenterX = data . x + data . radius ;
252- const ballCenterY = data . y + data . radius ;
253- const distance = Math . sqrt (
254- Math . pow ( touchX - ballCenterX , 2 ) +
255- Math . pow ( touchY - ballCenterY , 2 )
256- ) ;
257-
258- // If touch is within 100px of ball center, give it a push
295+ const dx = pos . x - ( data . x + data . radius ) ;
296+ const dy = pos . y - ( data . y + data . radius ) ;
297+ const distance = Math . sqrt ( dx * dx + dy * dy ) ;
298+
259299 if ( distance < 100 ) {
260- const pushStrength = Math . max ( 0.5 , ( 100 - distance ) / 100 * 3 ) ;
261- const angle = Math . atan2 ( ballCenterY - touchY , ballCenterX - touchX ) ;
262- data . vx + = Math . cos ( angle ) * pushStrength ;
263- data . vy + = Math . sin ( angle ) * pushStrength ;
264- data . vr += ( Math . random ( ) - 0.5 ) * 0.2 ;
300+ const force = ( 100 - distance ) / 100 * 3 ;
301+ const angle = Math . atan2 ( dy , dx ) ;
302+ data . vx - = Math . cos ( angle ) * force ;
303+ data . vy - = Math . sin ( angle ) * force ;
304+ data . vr += ( Math . random ( ) - 0.5 ) * 0.3 ;
265305 }
266306 }
267307 }
@@ -276,6 +316,66 @@ main.addEventListener('contextmenu', function(e) {
276316// Initialize touch interaction
277317addTouchInteraction ( ) ;
278318
319+ // Simple mouse interaction
320+ function addMouseInteraction ( ) {
321+ main . addEventListener ( 'mousedown' , function ( e ) {
322+ e . preventDefault ( ) ;
323+
324+ const pos = getRelativePosition ( e . clientX , e . clientY ) ;
325+ const ballIndex = findBallAtPosition ( pos . x , pos . y ) ;
326+
327+ if ( ballIndex !== - 1 ) {
328+ dragState . isDragging = true ;
329+ dragState . draggedBallIndex = ballIndex ;
330+ dragState . dragOffset = {
331+ x : pos . x - physicsData [ ballIndex ] . x ,
332+ y : pos . y - physicsData [ ballIndex ] . y
333+ } ;
334+
335+ physicsData [ ballIndex ] . vx = 0 ;
336+ physicsData [ ballIndex ] . vy = 0 ;
337+ physicsData [ ballIndex ] . vr = 0 ;
338+ main . children [ ballIndex ] . classList . add ( 'dragging' ) ;
339+ document . body . style . cursor = 'grabbing' ;
340+ }
341+ } ) ;
342+
343+ main . addEventListener ( 'mousemove' , function ( e ) {
344+ if ( dragState . isDragging ) {
345+ const pos = getRelativePosition ( e . clientX , e . clientY ) ;
346+ const data = physicsData [ dragState . draggedBallIndex ] ;
347+ const video = main . children [ dragState . draggedBallIndex ] ;
348+
349+ data . x = pos . x - dragState . dragOffset . x ;
350+ data . y = pos . y - dragState . dragOffset . y ;
351+
352+ const viewport = getViewportDimensions ( ) ;
353+ data . x = Math . max ( 0 , Math . min ( viewport . width - data . radius * 2 , data . x ) ) ;
354+ data . y = Math . max ( 0 , Math . min ( viewport . height - data . radius * 2 , data . y ) ) ;
355+
356+ video . style . left = data . x + 'px' ;
357+ video . style . top = data . y + 'px' ;
358+ } else {
359+ const pos = getRelativePosition ( e . clientX , e . clientY ) ;
360+ const ballIndex = findBallAtPosition ( pos . x , pos . y ) ;
361+ document . body . style . cursor = ballIndex !== - 1 ? 'grab' : 'default' ;
362+ }
363+ } ) ;
364+
365+ main . addEventListener ( 'mouseup' , function ( ) {
366+ if ( dragState . isDragging ) {
367+ const video = main . children [ dragState . draggedBallIndex ] ;
368+ video . classList . remove ( 'dragging' ) ;
369+ dragState . isDragging = false ;
370+ dragState . draggedBallIndex = - 1 ;
371+ document . body . style . cursor = 'default' ;
372+ }
373+ } ) ;
374+ }
375+
376+ // Initialize mouse interaction
377+ addMouseInteraction ( ) ;
378+
279379// Handle mobile viewport changes and prevent scrolling
280380function initializeMobileHandling ( ) {
281381 // Prevent pull-to-refresh on mobile
0 commit comments