feat(card-sorting): wrap prefix/suffix cards to multiple rows

Previously, when more cards were correct than could fit in a single
row, they would be positioned off-screen (>100% or <0%).

Now cards wrap to multiple rows:
- Prefix cards: Top-left, wrapping downward in rows
- Suffix cards: Bottom-right, wrapping upward in rows (right-to-left)
- Max cards per row calculated dynamically based on viewport width
- Uses 5% margins on left/right sides

Example with 15 cards (5 cards per row):
- Prefix: 3 rows starting at top-left
- Suffix: 3 rows starting at bottom-right

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock 2025-10-23 22:17:52 -05:00
parent 34785f466f
commit e3184dd0d4
1 changed files with 15 additions and 23 deletions

View File

@ -1307,13 +1307,14 @@ export function PlayingPhaseDrag() {
)
}
// Arrange prefix cards at top-left, side by side
// But only if they fit within the viewport
const lastPrefixX = 5 + (prefixCards.length - 1) * (CARD_WIDTH_PCT + SPACING) + CARD_WIDTH_PCT
if (lastPrefixX <= 100) {
// Arrange prefix cards at top-left, wrapping to multiple rows if needed
const maxCardsPerRow = Math.floor((100 - 10) / (CARD_WIDTH_PCT + SPACING)) // 5% margin on each side
if (maxCardsPerRow > 0) {
prefixCards.forEach((card, index) => {
const x = 5 + index * (CARD_WIDTH_PCT + SPACING) // Start at 5% margin
const y = 5 // Top margin
const row = Math.floor(index / maxCardsPerRow)
const col = index % maxCardsPerRow
const x = 5 + col * (CARD_WIDTH_PCT + SPACING) // Start at 5% margin
const y = 5 + row * (CARD_HEIGHT_PCT + SPACING) // Stack rows vertically
const rotation = 0 // No rotation for organized cards
const zIndex = 1000 + index // Higher z-index so they're on top
@ -1331,22 +1332,17 @@ export function PlayingPhaseDrag() {
}
})
} else if (shouldLog) {
console.log(
'[AutoArrange] Skipping prefix arrange: would exceed viewport (',
lastPrefixX,
'% > 100%)'
)
console.log('[AutoArrange] Skipping prefix arrange: viewport too narrow')
}
// Arrange suffix cards at bottom-right, side by side (right to left)
// But only if they fit within the viewport
const firstSuffixX =
100 - CARD_WIDTH_PCT - 5 - (suffixCards.length - 1) * (CARD_WIDTH_PCT + SPACING)
if (firstSuffixX >= 0) {
// Arrange suffix cards at bottom-right, wrapping to multiple rows if needed (right to left)
if (maxCardsPerRow > 0) {
suffixCards.forEach((card, index) => {
const fromRight = suffixCards.length - 1 - index
const row = Math.floor(index / maxCardsPerRow)
const col = index % maxCardsPerRow
const fromRight = col
const x = 100 - CARD_WIDTH_PCT - 5 - fromRight * (CARD_WIDTH_PCT + SPACING) // From right edge
const y = 100 - CARD_HEIGHT_PCT - 5 // Bottom margin
const y = 100 - CARD_HEIGHT_PCT - 5 - row * (CARD_HEIGHT_PCT + SPACING) // Stack rows upward
const rotation = 0 // No rotation for organized cards
const zIndex = 1000 + index // Higher z-index so they're on top
@ -1364,11 +1360,7 @@ export function PlayingPhaseDrag() {
}
})
} else if (shouldLog) {
console.log(
'[AutoArrange] Skipping suffix arrange: would exceed viewport (',
firstSuffixX,
'% < 0%)'
)
console.log('[AutoArrange] Skipping suffix arrange: viewport too narrow')
}
if (hasChanges) {