feat(flashcards): add dynamic shadow based on drag speed
First physics enhancement - shadow changes based on how fast you drag: - Tracks drag velocity (distance/time) during pointer move - Shadow grows larger and darker with faster dragging - Base: 8px offset, 24px blur, 0.3 opacity - Fast: 32px offset, 64px blur, 0.6 opacity - Smooth decay when released Console logging included: - [Shadow] logs on drag start/release - Speed/distance/time logged during drag (throttled to ~100ms) Test: Drag cards slowly vs fast and watch shadow change 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
7088a7096a
commit
92148a4cf8
|
|
@ -97,13 +97,17 @@ function DraggableCard({ card }: DraggableCardProps) {
|
|||
const [rotation] = useState(card.initialRotation)
|
||||
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 lastMoveTimeRef = useRef<number>(0)
|
||||
const lastMovePositionRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 })
|
||||
|
||||
const handlePointerDown = (e: React.PointerEvent) => {
|
||||
setIsDragging(true)
|
||||
setZIndex(1000) // Bring to front
|
||||
setDragSpeed(0)
|
||||
|
||||
// Capture the pointer
|
||||
e.currentTarget.setPointerCapture(e.pointerId)
|
||||
|
|
@ -115,6 +119,12 @@ function DraggableCard({ card }: DraggableCardProps) {
|
|||
cardX: position.x,
|
||||
cardY: position.y,
|
||||
}
|
||||
|
||||
// Initialize velocity tracking
|
||||
lastMoveTimeRef.current = Date.now()
|
||||
lastMovePositionRef.current = { x: e.clientX, y: e.clientY }
|
||||
|
||||
console.log('[Shadow] Drag started, speed reset to 0')
|
||||
}
|
||||
|
||||
const handlePointerMove = (e: React.PointerEvent) => {
|
||||
|
|
@ -124,6 +134,33 @@ function DraggableCard({ card }: DraggableCardProps) {
|
|||
const deltaX = e.clientX - dragStartRef.current.x
|
||||
const deltaY = e.clientY - dragStartRef.current.y
|
||||
|
||||
// Calculate velocity for dynamic shadow
|
||||
const now = Date.now()
|
||||
const timeDelta = now - lastMoveTimeRef.current
|
||||
|
||||
if (timeDelta > 0) {
|
||||
// Distance moved since last frame
|
||||
const distX = e.clientX - lastMovePositionRef.current.x
|
||||
const distY = e.clientY - lastMovePositionRef.current.y
|
||||
const distance = Math.sqrt(distX * distX + distY * distY)
|
||||
|
||||
// Speed in pixels per millisecond, then convert to reasonable scale
|
||||
const speed = distance / timeDelta
|
||||
const scaledSpeed = Math.min(speed * 100, 100) // Cap at 100 for reasonable shadow size
|
||||
|
||||
setDragSpeed(scaledSpeed)
|
||||
|
||||
// Log occasionally (every ~100ms) to avoid console spam
|
||||
if (timeDelta > 100) {
|
||||
console.log(
|
||||
`[Shadow] Speed: ${scaledSpeed.toFixed(1)}, distance: ${distance.toFixed(0)}px, time: ${timeDelta}ms`
|
||||
)
|
||||
}
|
||||
|
||||
lastMoveTimeRef.current = now
|
||||
lastMovePositionRef.current = { x: e.clientX, y: e.clientY }
|
||||
}
|
||||
|
||||
// Update card position
|
||||
setPosition({
|
||||
x: dragStartRef.current.cardX + deltaX,
|
||||
|
|
@ -135,10 +172,32 @@ function DraggableCard({ card }: DraggableCardProps) {
|
|||
setIsDragging(false)
|
||||
dragStartRef.current = null
|
||||
|
||||
console.log('[Shadow] Drag released, speed decaying to 0')
|
||||
|
||||
// Gradually decay speed back to 0 for smooth shadow transition
|
||||
const decayInterval = setInterval(() => {
|
||||
setDragSpeed((prev) => {
|
||||
const newSpeed = prev * 0.8 // Decay by 20% each frame
|
||||
if (newSpeed < 1) {
|
||||
clearInterval(decayInterval)
|
||||
return 0
|
||||
}
|
||||
return newSpeed
|
||||
})
|
||||
}, 50) // Update every 50ms
|
||||
|
||||
// Release the pointer capture
|
||||
e.currentTarget.releasePointerCapture(e.pointerId)
|
||||
}
|
||||
|
||||
// Calculate dynamic shadow based on drag speed
|
||||
// Base shadow: 0 8px 24px rgba(0, 0, 0, 0.3)
|
||||
// Fast drag: 0 32px 64px rgba(0, 0, 0, 0.6)
|
||||
const shadowY = 8 + (dragSpeed / 100) * 24 // 8px to 32px
|
||||
const shadowBlur = 24 + (dragSpeed / 100) * 40 // 24px to 64px
|
||||
const shadowOpacity = 0.3 + (dragSpeed / 100) * 0.3 // 0.3 to 0.6
|
||||
const boxShadow = `0 ${shadowY}px ${shadowBlur}px rgba(0, 0, 0, ${shadowOpacity})`
|
||||
|
||||
return (
|
||||
<div
|
||||
onPointerDown={handlePointerDown}
|
||||
|
|
@ -159,20 +218,20 @@ function DraggableCard({ card }: DraggableCardProps) {
|
|||
})}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
boxShadow, // Dynamic shadow based on drag speed
|
||||
}}
|
||||
className={css({
|
||||
bg: 'white',
|
||||
rounded: 'lg',
|
||||
p: '4',
|
||||
boxShadow: isDragging
|
||||
? '0 16px 48px rgba(0, 0, 0, 0.5)'
|
||||
: '0 8px 24px rgba(0, 0, 0, 0.3)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '2',
|
||||
minW: '120px',
|
||||
border: '2px solid rgba(0, 0, 0, 0.1)',
|
||||
transition: 'box-shadow 0.2s',
|
||||
transition: 'box-shadow 0.1s', // Quick transition for responsive feel
|
||||
})}
|
||||
>
|
||||
{/* Abacus visualization */}
|
||||
|
|
|
|||
Loading…
Reference in New Issue