diff --git a/apps/web/src/components/InteractiveFlashcards.tsx b/apps/web/src/components/InteractiveFlashcards.tsx index 919abac3..7a223fae 100644 --- a/apps/web/src/components/InteractiveFlashcards.tsx +++ b/apps/web/src/components/InteractiveFlashcards.tsx @@ -94,16 +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(0) const lastMovePositionRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 }) const lastLogTimeRef = useRef(0) // Separate throttling for logging + const cardRef = useRef(null) // Reference to card element const handlePointerDown = (e: React.PointerEvent) => { setIsDragging(true) @@ -121,6 +124,23 @@ 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 const now = Date.now() lastMoveTimeRef.current = now @@ -166,6 +186,18 @@ function DraggableCard({ card }: DraggableCardProps) { lastMovePositionRef.current = { x: e.clientX, y: e.clientY } } + // 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 / 5000 // Scale factor for reasonable rotation (adjust as needed) + const newRotation = baseRotationRef.current + rotationInfluence + + // Clamp rotation to prevent excessive spinning + const clampedRotation = Math.max(-45, Math.min(45, newRotation)) + setRotation(clampedRotation) + // Update card position setPosition({ x: dragStartRef.current.cardX + deltaX, @@ -178,6 +210,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(() => { @@ -205,6 +240,7 @@ function DraggableCard({ card }: DraggableCardProps) { return (