Skip to content

Commit 5e9ef01

Browse files
koule
1 parent fb6afa9 commit 5e9ef01

File tree

2 files changed

+216
-100
lines changed

2 files changed

+216
-100
lines changed

static/kvh/app.js

Lines changed: 199 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ function loadVideo(src) {
1313
const physicsData = [];
1414
const 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
1726
function 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
2663
function 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

4468
function 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 *= -1.1;
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 *= -1.1;
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
225231
function 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
277317
addTouchInteraction();
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
280380
function initializeMobileHandling() {
281381
// Prevent pull-to-refresh on mobile

0 commit comments

Comments
 (0)