|
|
|
|
@@ -94,15 +94,19 @@ interface DraggableCardProps {
|
|
|
|
|
function DraggableCard({ card }: DraggableCardProps) {
|
|
|
|
|
// Track position - starts at initial, updates when dragged
|
|
|
|
|
const [position, setPosition] = useState({ x: card.initialX, y: card.initialY })
|
|
|
|
|
const [rotation] = useState(card.initialRotation)
|
|
|
|
|
const [rotation, setRotation] = useState(card.initialRotation) // Now dynamic!
|
|
|
|
|
const [zIndex, setZIndex] = useState(card.zIndex)
|
|
|
|
|
const [isDragging, setIsDragging] = useState(false)
|
|
|
|
|
const [dragSpeed, setDragSpeed] = useState(0) // Speed for dynamic shadow
|
|
|
|
|
|
|
|
|
|
// Track drag state
|
|
|
|
|
const dragStartRef = useRef<{ x: number; y: number; cardX: number; cardY: number } | null>(null)
|
|
|
|
|
const grabOffsetRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 }) // Offset from card center where grabbed
|
|
|
|
|
const baseRotationRef = useRef(card.initialRotation) // Starting rotation
|
|
|
|
|
const lastMoveTimeRef = useRef<number>(0)
|
|
|
|
|
const lastMovePositionRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 })
|
|
|
|
|
const lastLogTimeRef = useRef<number>(0) // Separate throttling for logging
|
|
|
|
|
const cardRef = useRef<HTMLDivElement>(null) // Reference to card element
|
|
|
|
|
|
|
|
|
|
const handlePointerDown = (e: React.PointerEvent) => {
|
|
|
|
|
setIsDragging(true)
|
|
|
|
|
@@ -120,9 +124,28 @@ function DraggableCard({ card }: DraggableCardProps) {
|
|
|
|
|
cardY: position.y,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate grab offset from card center
|
|
|
|
|
if (cardRef.current) {
|
|
|
|
|
const rect = cardRef.current.getBoundingClientRect()
|
|
|
|
|
const cardCenterX = rect.left + rect.width / 2
|
|
|
|
|
const cardCenterY = rect.top + rect.height / 2
|
|
|
|
|
grabOffsetRef.current = {
|
|
|
|
|
x: e.clientX - cardCenterX,
|
|
|
|
|
y: e.clientY - cardCenterY,
|
|
|
|
|
}
|
|
|
|
|
console.log(
|
|
|
|
|
`[GrabPoint] Grabbed at offset: (${grabOffsetRef.current.x.toFixed(0)}, ${grabOffsetRef.current.y.toFixed(0)})px from center`
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Store the current rotation as the base for this drag
|
|
|
|
|
baseRotationRef.current = rotation
|
|
|
|
|
|
|
|
|
|
// Initialize velocity tracking
|
|
|
|
|
lastMoveTimeRef.current = Date.now()
|
|
|
|
|
const now = Date.now()
|
|
|
|
|
lastMoveTimeRef.current = now
|
|
|
|
|
lastMovePositionRef.current = { x: e.clientX, y: e.clientY }
|
|
|
|
|
lastLogTimeRef.current = now
|
|
|
|
|
|
|
|
|
|
console.log('[Shadow] Drag started, speed reset to 0')
|
|
|
|
|
}
|
|
|
|
|
@@ -150,22 +173,78 @@ function DraggableCard({ card }: DraggableCardProps) {
|
|
|
|
|
|
|
|
|
|
setDragSpeed(scaledSpeed)
|
|
|
|
|
|
|
|
|
|
// Log occasionally (every ~100ms) to avoid console spam
|
|
|
|
|
if (timeDelta > 100) {
|
|
|
|
|
// Log occasionally (every ~200ms) to avoid console spam
|
|
|
|
|
const timeSinceLastLog = now - lastLogTimeRef.current
|
|
|
|
|
if (timeSinceLastLog > 200) {
|
|
|
|
|
console.log(
|
|
|
|
|
`[Shadow] Speed: ${scaledSpeed.toFixed(1)}, distance: ${distance.toFixed(0)}px, time: ${timeDelta}ms`
|
|
|
|
|
`[Shadow] Speed: ${scaledSpeed.toFixed(1)}, distance: ${distance.toFixed(0)}px, timeDelta: ${timeDelta}ms`
|
|
|
|
|
)
|
|
|
|
|
lastLogTimeRef.current = now
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lastMoveTimeRef.current = now
|
|
|
|
|
lastMovePositionRef.current = { x: e.clientX, y: e.clientY }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update card position
|
|
|
|
|
setPosition({
|
|
|
|
|
x: dragStartRef.current.cardX + deltaX,
|
|
|
|
|
y: dragStartRef.current.cardY + deltaY,
|
|
|
|
|
})
|
|
|
|
|
// Calculate rotation based on grab point physics
|
|
|
|
|
// Cross product of grab offset and drag direction determines rotation
|
|
|
|
|
// If grabbed on left and dragged right → clockwise rotation
|
|
|
|
|
// If grabbed on right and dragged left → counter-clockwise rotation
|
|
|
|
|
const crossProduct = grabOffsetRef.current.x * deltaY - grabOffsetRef.current.y * deltaX
|
|
|
|
|
const rotationInfluence = crossProduct / 500 // Reduced scale factor for more visible rotation
|
|
|
|
|
const newRotation = baseRotationRef.current + rotationInfluence
|
|
|
|
|
|
|
|
|
|
// Clamp rotation to prevent excessive spinning
|
|
|
|
|
const clampedRotation = Math.max(-45, Math.min(45, newRotation))
|
|
|
|
|
setRotation(clampedRotation)
|
|
|
|
|
|
|
|
|
|
// Log rotation changes occasionally (same throttle as shadow logging)
|
|
|
|
|
const timeSinceLastLog = now - lastLogTimeRef.current
|
|
|
|
|
if (timeSinceLastLog > 200) {
|
|
|
|
|
console.log(
|
|
|
|
|
`[GrabPoint] Rotation: ${clampedRotation.toFixed(1)}° (influence: ${rotationInfluence.toFixed(1)}°, cross: ${crossProduct.toFixed(0)})`
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update card position - keep the grab point "stuck" to the cursor
|
|
|
|
|
// As the card rotates, the grab point rotates with it, so we need to account for rotation
|
|
|
|
|
const rotationRad = (clampedRotation * Math.PI) / 180
|
|
|
|
|
const cosRot = Math.cos(rotationRad)
|
|
|
|
|
const sinRot = Math.sin(rotationRad)
|
|
|
|
|
|
|
|
|
|
// Rotate the grab offset by the current rotation angle
|
|
|
|
|
const rotatedGrabX = grabOffsetRef.current.x * cosRot - grabOffsetRef.current.y * sinRot
|
|
|
|
|
const rotatedGrabY = grabOffsetRef.current.x * sinRot + grabOffsetRef.current.y * cosRot
|
|
|
|
|
|
|
|
|
|
// Current cursor position
|
|
|
|
|
const cursorX = e.clientX
|
|
|
|
|
const cursorY = e.clientY
|
|
|
|
|
|
|
|
|
|
// Card center should be at: cursor position - rotated grab offset
|
|
|
|
|
// But we need to position the card element (top-left), not the center
|
|
|
|
|
// Get card dimensions to calculate offset from center to top-left
|
|
|
|
|
if (cardRef.current) {
|
|
|
|
|
const rect = cardRef.current.getBoundingClientRect()
|
|
|
|
|
const cardWidth = rect.width
|
|
|
|
|
const cardHeight = rect.height
|
|
|
|
|
|
|
|
|
|
// Card center position in screen space
|
|
|
|
|
const cardCenterX = cursorX - rotatedGrabX
|
|
|
|
|
const cardCenterY = cursorY - rotatedGrabY
|
|
|
|
|
|
|
|
|
|
// Convert center position to top-left position (what we store in position state)
|
|
|
|
|
// Note: position.x/y is used in translate(), which positions the element
|
|
|
|
|
setPosition({
|
|
|
|
|
x: cardCenterX - cardWidth / 2,
|
|
|
|
|
y: cardCenterY - cardHeight / 2,
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
// Fallback to simple delta if we don't have card dimensions yet
|
|
|
|
|
setPosition({
|
|
|
|
|
x: dragStartRef.current.cardX + deltaX,
|
|
|
|
|
y: dragStartRef.current.cardY + deltaY,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handlePointerUp = (e: React.PointerEvent) => {
|
|
|
|
|
@@ -173,6 +252,9 @@ function DraggableCard({ card }: DraggableCardProps) {
|
|
|
|
|
dragStartRef.current = null
|
|
|
|
|
|
|
|
|
|
console.log('[Shadow] Drag released, speed decaying to 0')
|
|
|
|
|
console.log(
|
|
|
|
|
`[GrabPoint] Final rotation: ${rotation.toFixed(1)}° (base was ${baseRotationRef.current.toFixed(1)}°)`
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Gradually decay speed back to 0 for smooth shadow transition
|
|
|
|
|
const decayInterval = setInterval(() => {
|
|
|
|
|
@@ -200,6 +282,7 @@ function DraggableCard({ card }: DraggableCardProps) {
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
ref={cardRef}
|
|
|
|
|
onPointerDown={handlePointerDown}
|
|
|
|
|
onPointerMove={handlePointerMove}
|
|
|
|
|
onPointerUp={handlePointerUp}
|
|
|
|
|
|