<div class="elastic-container">
<div class="content-overlay">
<h2>Pull The String</h2>
<p>Grab the glowing line below and release it to see the physics engine in action.</p>
</div>
<div class="svg-wrapper" id="string-wrapper">
<svg viewBox="0 0 1000 500" width="100%" height="100%" preserveAspectRatio="none">
<path id="elastic-line" d="M 0 250 Q 500 250 1000 250" fill="none" stroke="#3b82f6" stroke-width="6" stroke-linecap="round"></path>
</svg>
</div>
</div>
*, *::before, *::after {
box-sizing: border-box;
}
body {
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
margin: 0;
background: #0f172a;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow: hidden;
}
.elastic-container {
position: relative;
width: 100%;
max-width: 1000px;
height: 500px;
display: flex;
justify-content: center;
align-items: center;
}
.content-overlay {
position: absolute;
top: 60px;
text-align: center;
color: #ffffff;
pointer-events: none;
z-index: 10;
}
.content-overlay h2 {
font-size: 2.5rem;
margin: 0 0 10px 0;
letter-spacing: -1px;
}
.content-overlay p {
color: #94a3b8;
font-size: 1.125rem;
margin: 0;
}
.svg-wrapper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
cursor: grab;
}
.svg-wrapper:active {
cursor: grabbing;
}
#elastic-line {
filter: drop-shadow(0 0 15px rgba(59, 130, 246, 0.8));
}
const wrapper = document.getElementById("string-wrapper");
const line = document.getElementById("elastic-line");
let isDragging = false;
let mouseX = 500;
let mouseY = 250;
let currentY = 250;
let targetY = 250;
let velocity = 0;
// Physics constants
const tension = 0.05;
const friction = 0.85;
wrapper.addEventListener("pointerdown", function(e) {
isDragging = true;
wrapper.setPointerCapture(e.pointerId);
updateMousePos(e);
});
wrapper.addEventListener("pointermove", function(e) {
if (isDragging) {
updateMousePos(e);
}
});
function endDrag() {
isDragging = false;
targetY = 250;
mouseX = 500;
}
wrapper.addEventListener("pointerup", endDrag);
wrapper.addEventListener("pointercancel", endDrag);
function updateMousePos(e) {
const rect = wrapper.getBoundingClientRect();
// Map mouse position to the 1000x500 SVG viewBox
mouseX = (e.clientX - rect.left) * (1000 / rect.width);
let rawY = (e.clientY - rect.top) * (500 / rect.height);
targetY = rawY;
}
function animate() {
if (!isDragging) {
// Spring physics calculation
const force = (targetY - currentY) * tension;
velocity += force;
velocity *= friction;
currentY += velocity;
} else {
// Follow mouse smoothly when dragging
currentY += (targetY - currentY) * 0.5;
}
// Update the Quadratic Bezier Curve path
line.setAttribute("d", "M 0 250 Q " + mouseX + " " + currentY + " 1000 250");
window.requestAnimationFrame(animate);
}
animate();