@@ -13,6 +13,15 @@ function loadVideo(src) {
1313const physicsData = [ ] ;
1414const main = document . querySelector ( 'main' ) ;
1515
16+ // Drag and drop state management
17+ let 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,13 +117,18 @@ 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
@@ -109,18 +138,18 @@ function updatePhysics() {
109138 const high_update = 1.05 ;
110139 const low_update = 1.0 ;
111140
112- // Use viewport dimensions instead of window dimensions
141+ // Simple wall collision with spin effects
113142 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
143+ data . vx *= - 1.1 ;
144+ data . vy += data . vr * 0.5 ; // Spin affects bounce direction
145+ data . vr *= 0.8 ; // Lose some spin
117146 data . x = Math . max ( 0 , Math . min ( viewport . width - data . radius * 2 , data . x ) ) ;
118147 }
119148
120149 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
150+ data . vy *= - 1.1 ;
151+ data . vx += data . vr * 0.5 ; // Spin affects bounce direction
152+ data . vr *= 0.8 ; // Lose some spin
124153 data . y = Math . max ( 0 , Math . min ( viewport . height - data . radius * 2 , data . y ) ) ;
125154 }
126155
@@ -131,6 +160,11 @@ function updatePhysics() {
131160
132161 for ( let i = 0 ; i < physicsData . length ; i ++ ) {
133162 for ( let j = i + 1 ; j < physicsData . length ; j ++ ) {
163+ // Skip collision detection if either ball is being dragged
164+ if ( dragState . isDragging && ( dragState . draggedBallIndex === i || dragState . draggedBallIndex === j ) ) {
165+ continue ;
166+ }
167+
134168 const ball1 = physicsData [ i ] ;
135169 const ball2 = physicsData [ j ] ;
136170
@@ -143,59 +177,34 @@ function updatePhysics() {
143177 const dy = center2Y - center1Y ;
144178 const distance = Math . sqrt ( dx * dx + dy * dy ) ;
145179
146- const collisionDistance = ball1 . radius + ball2 . radius ;
147- if ( distance < collisionDistance ) {
148- const overlap = collisionDistance - distance ;
180+ // Simple collision detection and response
181+ if ( distance < ball1 . radius + ball2 . radius ) {
182+ const overlap = ball1 . radius + ball2 . radius - distance ;
149183 const nx = dx / distance ;
150184 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- }
185+
186+ // Separate balls
187+ const separation = overlap * 0.5 ;
188+ ball1 . x -= nx * separation ;
189+ ball1 . y -= ny * separation ;
190+ ball2 . x += nx * separation ;
191+ ball2 . y += ny * separation ;
192+
193+ // Simple velocity exchange
194+ const dvx = ball2 . vx - ball1 . vx ;
195+ const dvy = ball2 . vy - ball1 . vy ;
196+ const dvn = dvx * nx + dvy * ny ;
197+
198+ const impulse = dvn * 0.8 ;
199+ ball1 . vx += impulse * nx ;
200+ ball1 . vy += impulse * ny ;
201+ ball2 . vx -= impulse * nx ;
202+ ball2 . vy -= impulse * ny ;
203+
204+ // Exchange some spin
205+ const spinExchange = impulse * 0.3 ;
206+ ball1 . vr += spinExchange ;
207+ ball2 . vr -= spinExchange ;
199208 }
200209 }
201210 }
@@ -221,47 +230,81 @@ globalThis.addEventListener('orientationchange', function() {
221230 setTimeout ( updateBallRadius , 100 ) ;
222231} ) ;
223232
224- // Add touch event handling for mobile interaction
233+ // Simple touch interaction
225234function addTouchInteraction ( ) {
226235 let touchStartTime = 0 ;
227236
228237 main . addEventListener ( 'touchstart' , function ( e ) {
229- e . preventDefault ( ) ; // Prevent scrolling and zooming
238+ e . preventDefault ( ) ;
230239 touchStartTime = Date . now ( ) ;
240+
241+ if ( e . touches . length === 1 ) {
242+ const touch = e . touches [ 0 ] ;
243+ const pos = getRelativePosition ( touch . clientX , touch . clientY ) ;
244+ const ballIndex = findBallAtPosition ( pos . x , pos . y ) ;
245+
246+ if ( ballIndex !== - 1 ) {
247+ dragState . isDragging = true ;
248+ dragState . draggedBallIndex = ballIndex ;
249+ dragState . dragOffset = {
250+ x : pos . x - physicsData [ ballIndex ] . x ,
251+ y : pos . y - physicsData [ ballIndex ] . y
252+ } ;
253+
254+ physicsData [ ballIndex ] . vx = 0 ;
255+ physicsData [ ballIndex ] . vy = 0 ;
256+ physicsData [ ballIndex ] . vr = 0 ;
257+ main . children [ ballIndex ] . classList . add ( 'dragging' ) ;
258+ }
259+ }
231260 } , { passive : false } ) ;
232261
233262 main . addEventListener ( 'touchmove' , function ( e ) {
234- e . preventDefault ( ) ; // Prevent scrolling
263+ e . preventDefault ( ) ;
264+
265+ if ( dragState . isDragging && e . touches . length === 1 ) {
266+ const touch = e . touches [ 0 ] ;
267+ const pos = getRelativePosition ( touch . clientX , touch . clientY ) ;
268+ const data = physicsData [ dragState . draggedBallIndex ] ;
269+ const video = main . children [ dragState . draggedBallIndex ] ;
270+
271+ data . x = pos . x - dragState . dragOffset . x ;
272+ data . y = pos . y - dragState . dragOffset . y ;
273+
274+ const viewport = getViewportDimensions ( ) ;
275+ data . x = Math . max ( 0 , Math . min ( viewport . width - data . radius * 2 , data . x ) ) ;
276+ data . y = Math . max ( 0 , Math . min ( viewport . height - data . radius * 2 , data . y ) ) ;
277+
278+ video . style . left = data . x + 'px' ;
279+ video . style . top = data . y + 'px' ;
280+ }
235281 } , { passive : false } ) ;
236282
237283 main . addEventListener ( 'touchend' , function ( e ) {
238284 e . preventDefault ( ) ;
239- const touchDuration = Date . now ( ) - touchStartTime ;
240285
241- // If it's a quick tap (less than 200ms), add some energy to nearby balls
242- if ( touchDuration < 200 && e . changedTouches . length > 0 ) {
286+ if ( dragState . isDragging ) {
287+ const video = main . children [ dragState . draggedBallIndex ] ;
288+ video . classList . remove ( 'dragging' ) ;
289+ dragState . isDragging = false ;
290+ dragState . draggedBallIndex = - 1 ;
291+ } else if ( Date . now ( ) - touchStartTime < 200 ) {
292+ // Quick tap - push nearby balls
243293 const touch = e . changedTouches [ 0 ] ;
244- const rect = main . getBoundingClientRect ( ) ;
245- const touchX = touch . clientX - rect . left ;
246- const touchY = touch . clientY - rect . top ;
294+ const pos = getRelativePosition ( touch . clientX , touch . clientY ) ;
247295
248- // Find balls near the touch point and give them a little push
249296 for ( let i = 0 ; i < physicsData . length ; i ++ ) {
250297 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
298+ const dx = pos . x - ( data . x + data . radius ) ;
299+ const dy = pos . y - ( data . y + data . radius ) ;
300+ const distance = Math . sqrt ( dx * dx + dy * dy ) ;
301+
259302 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 ;
303+ const force = ( 100 - distance ) / 100 * 3 ;
304+ const angle = Math . atan2 ( dy , dx ) ;
305+ data . vx - = Math . cos ( angle ) * force ;
306+ data . vy - = Math . sin ( angle ) * force ;
307+ data . vr += ( Math . random ( ) - 0.5 ) * 0.3 ;
265308 }
266309 }
267310 }
@@ -276,6 +319,66 @@ main.addEventListener('contextmenu', function(e) {
276319// Initialize touch interaction
277320addTouchInteraction ( ) ;
278321
322+ // Simple mouse interaction
323+ function addMouseInteraction ( ) {
324+ main . addEventListener ( 'mousedown' , function ( e ) {
325+ e . preventDefault ( ) ;
326+
327+ const pos = getRelativePosition ( e . clientX , e . clientY ) ;
328+ const ballIndex = findBallAtPosition ( pos . x , pos . y ) ;
329+
330+ if ( ballIndex !== - 1 ) {
331+ dragState . isDragging = true ;
332+ dragState . draggedBallIndex = ballIndex ;
333+ dragState . dragOffset = {
334+ x : pos . x - physicsData [ ballIndex ] . x ,
335+ y : pos . y - physicsData [ ballIndex ] . y
336+ } ;
337+
338+ physicsData [ ballIndex ] . vx = 0 ;
339+ physicsData [ ballIndex ] . vy = 0 ;
340+ physicsData [ ballIndex ] . vr = 0 ;
341+ main . children [ ballIndex ] . classList . add ( 'dragging' ) ;
342+ document . body . style . cursor = 'grabbing' ;
343+ }
344+ } ) ;
345+
346+ main . addEventListener ( 'mousemove' , function ( e ) {
347+ if ( dragState . isDragging ) {
348+ const pos = getRelativePosition ( e . clientX , e . clientY ) ;
349+ const data = physicsData [ dragState . draggedBallIndex ] ;
350+ const video = main . children [ dragState . draggedBallIndex ] ;
351+
352+ data . x = pos . x - dragState . dragOffset . x ;
353+ data . y = pos . y - dragState . dragOffset . y ;
354+
355+ const viewport = getViewportDimensions ( ) ;
356+ data . x = Math . max ( 0 , Math . min ( viewport . width - data . radius * 2 , data . x ) ) ;
357+ data . y = Math . max ( 0 , Math . min ( viewport . height - data . radius * 2 , data . y ) ) ;
358+
359+ video . style . left = data . x + 'px' ;
360+ video . style . top = data . y + 'px' ;
361+ } else {
362+ const pos = getRelativePosition ( e . clientX , e . clientY ) ;
363+ const ballIndex = findBallAtPosition ( pos . x , pos . y ) ;
364+ document . body . style . cursor = ballIndex !== - 1 ? 'grab' : 'default' ;
365+ }
366+ } ) ;
367+
368+ main . addEventListener ( 'mouseup' , function ( e ) {
369+ if ( dragState . isDragging ) {
370+ const video = main . children [ dragState . draggedBallIndex ] ;
371+ video . classList . remove ( 'dragging' ) ;
372+ dragState . isDragging = false ;
373+ dragState . draggedBallIndex = - 1 ;
374+ document . body . style . cursor = 'default' ;
375+ }
376+ } ) ;
377+ }
378+
379+ // Initialize mouse interaction
380+ addMouseInteraction ( ) ;
381+
279382// Handle mobile viewport changes and prevent scrolling
280383function initializeMobileHandling ( ) {
281384 // Prevent pull-to-refresh on mobile
0 commit comments