Compare commits

...

8 Commits

Author SHA1 Message Date
semantic-release-bot
e4ae3aefef chore(release): 4.63.3 [skip ci]
## [4.63.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.63.2...v4.63.3) (2025-10-21)

### Bug Fixes

* **flashcards:** revert to simple delta positioning to prevent card jumping ([d018b69](d018b699c4))
2025-10-21 16:22:59 +00:00
Thomas Hallock
d018b699c4 fix(flashcards): revert to simple delta positioning to prevent card jumping
Reverted the complex rotation-compensated positioning that was causing
cards to jump to the right of the viewport on click.

The issue: The previous attempt to keep the grab point "stuck" to the cursor
while rotating was mixing screen coordinates with container-relative coords,
causing cards to teleport on pointer down.

The solution: Use simple delta positioning (cursor movement from drag start)
and let CSS handle rotation around the card center. While this means the grab
point won't stay perfectly under the cursor as the card rotates, it's much
better than cards jumping unexpectedly.

The rotation still works - cards rotate based on grab point physics - but
the positioning is now stable and predictable.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 11:21:49 -05:00
semantic-release-bot
be323bfbc5 chore(release): 4.63.2 [skip ci]
## [4.63.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.63.1...v4.63.2) (2025-10-21)

### Bug Fixes

* **flashcards:** correct pivot point to rotate around card center ([50fc3fd](50fc3fdf7f))
2025-10-21 16:19:23 +00:00
Thomas Hallock
50fc3fdf7f fix(flashcards): correct pivot point to rotate around card center
Fixed issue where cards appeared to pivot around the grab point in
viewport space instead of rotating around their own center.

The problem: When dragging, the card would stay "pinned" at the grab
point in screen space, making it rotate around that viewport location.

The solution: As the card rotates, calculate the rotated grab offset
and reposition the card so that:
1. The grab point stays under the cursor
2. The card rotates around its own center
3. The visual result feels like the card is "stuck" to your finger

Implementation:
- Convert rotation angle to radians
- Apply 2D rotation matrix to grab offset vector
- Calculate card center position: cursor - rotated grab offset
- Convert center position to top-left for CSS translate positioning

This creates the natural feeling of grabbing and spinning a physical
card by its edge.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 11:18:04 -05:00
semantic-release-bot
e52d907087 chore(release): 4.63.1 [skip ci]
## [4.63.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.63.0...v4.63.1) (2025-10-21)

### Bug Fixes

* **flashcards:** increase rotation sensitivity 10x for visible grab point physics ([c0fa926](c0fa926d16))
2025-10-21 16:08:00 +00:00
Thomas Hallock
c0fa926d16 fix(flashcards): increase rotation sensitivity 10x for visible grab point physics
The previous scale factor of 5000 made rotation changes too subtle to see.
Reduced to 500 (10x more sensitive) so card rotation is clearly visible
when dragging from off-center grab points.

Also added detailed rotation logging during drag to help debug:
- Shows current rotation angle
- Shows rotation influence being applied
- Shows cross product value from physics calculation

This will help test and tune the rotation feel.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 11:06:48 -05:00
semantic-release-bot
1fd0474cd5 chore(release): 4.63.0 [skip ci]
## [4.63.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.62.1...v4.63.0) (2025-10-21)

### Features

* **flashcards:** add grab point physics for realistic rotation ([bf37eb1](bf37eb1928))
2025-10-21 16:05:21 +00:00
Thomas Hallock
bf37eb1928 feat(flashcards): add grab point physics for realistic rotation
Implements physics-based rotation that responds to where the user grabs
the card. When you grab a card off-center and drag it, it rotates
naturally based on the grab point and drag direction.

Features:
- Calculate grab offset from card center on pointer down
- Apply rotation using cross product of grab offset and drag direction
- Rotation clamped to ±45° to prevent excessive spinning
- Final rotation preserved when card is released
- Console logging for grab point coordinates and rotation changes

Physics details:
- Cross product (grabOffset.x * deltaY - grabOffset.y * deltaX) determines
  rotation direction and magnitude
- Grabbing left side + dragging right = clockwise rotation
- Grabbing right side + dragging left = counter-clockwise rotation
- Scale factor of 5000 provides smooth, realistic rotation feel

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 11:04:07 -05:00
3 changed files with 76 additions and 3 deletions

View File

@@ -1,3 +1,31 @@
## [4.63.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.63.2...v4.63.3) (2025-10-21)
### Bug Fixes
* **flashcards:** revert to simple delta positioning to prevent card jumping ([d018b69](https://github.com/antialias/soroban-abacus-flashcards/commit/d018b699c46aea90e9cdc3309e797ff2d7447ecf))
## [4.63.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.63.1...v4.63.2) (2025-10-21)
### Bug Fixes
* **flashcards:** correct pivot point to rotate around card center ([50fc3fd](https://github.com/antialias/soroban-abacus-flashcards/commit/50fc3fdf7f2c9b7412f6d7d890f5e0d52cb86a9b))
## [4.63.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.63.0...v4.63.1) (2025-10-21)
### Bug Fixes
* **flashcards:** increase rotation sensitivity 10x for visible grab point physics ([c0fa926](https://github.com/antialias/soroban-abacus-flashcards/commit/c0fa926d16d02c1bfe880b7f0056a760e8461b3b))
## [4.63.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.62.1...v4.63.0) (2025-10-21)
### Features
* **flashcards:** add grab point physics for realistic rotation ([bf37eb1](https://github.com/antialias/soroban-abacus-flashcards/commit/bf37eb1928de8d07673234e2faa1fa6268c45686))
## [4.62.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.62.0...v4.62.1) (2025-10-21)

View File

@@ -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<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)
@@ -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,7 +186,28 @@ function DraggableCard({ card }: DraggableCardProps) {
lastMovePositionRef.current = { x: e.clientX, y: e.clientY }
}
// Update card position
// 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 - simple delta from drag start
// The rotation is visual only and happens around the card's center via CSS transform-origin
setPosition({
x: dragStartRef.current.cardX + deltaX,
y: dragStartRef.current.cardY + deltaY,
@@ -178,6 +219,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 +249,7 @@ function DraggableCard({ card }: DraggableCardProps) {
return (
<div
ref={cardRef}
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}

View File

@@ -1,6 +1,6 @@
{
"name": "soroban-monorepo",
"version": "4.62.1",
"version": "4.63.3",
"private": true,
"description": "Beautiful Soroban Flashcard Generator - Monorepo",
"workspaces": [