Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion web/src/components/cards/KubeChess.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ function minimax(state: GameState, depth: number, alpha: number, beta: number, m
counter.count++

// Bail out if we've evaluated too many positions to prevent UI freeze
if (counter.count > MAX_POSITIONS_EVALUATED) {
if (counter.count >= MAX_POSITIONS_EVALUATED) {
return evaluateBoard(state.board, state)
}

Expand Down
37 changes: 31 additions & 6 deletions web/src/components/cards/KubeCraft.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -281,15 +281,40 @@ export function KubeCraft() {
if (!canvas) return null

const rect = canvas.getBoundingClientRect()
// Scale mouse coordinates to canvas logical size (handles CSS scaling in expanded mode)
const scaleX = CANVAS_WIDTH / rect.width
const scaleY = CANVAS_HEIGHT / rect.height
const x = Math.floor((e.clientX - rect.left) * scaleX / CELL_SIZE)
const y = Math.floor((e.clientY - rect.top) * scaleY / CELL_SIZE)
// Guard against zero-size rect (e.g. hidden or collapsed element) to avoid NaN
if (rect.width <= 0 || rect.height <= 0) return null

// When objectFit: 'contain' is active (expanded mode), the canvas content may be
// letterboxed β€” the rendered image is centered within the element with padding on
// the shorter axis. We must compute the actual rendered content area so that clicks
// in the padded region are correctly rejected instead of mapping to wrong tiles.
const elementAspect = rect.width / rect.height
const canvasAspect = CANVAS_WIDTH / CANVAS_HEIGHT
let contentLeft = rect.left
let contentTop = rect.top
let contentWidth = rect.width
let contentHeight = rect.height

if (isExpanded) {
if (elementAspect > canvasAspect) {
// Pillarboxing: content is narrower than element, padded on left/right
contentWidth = rect.height * canvasAspect
contentLeft = rect.left + (rect.width - contentWidth) / 2
} else if (elementAspect < canvasAspect) {
// Letterboxing: content is shorter than element, padded on top/bottom
contentHeight = rect.width / canvasAspect
contentTop = rect.top + (rect.height - contentHeight) / 2
}
}

const scaleX = CANVAS_WIDTH / contentWidth
const scaleY = CANVAS_HEIGHT / contentHeight
const x = Math.floor((e.clientX - contentLeft) * scaleX / CELL_SIZE)
const y = Math.floor((e.clientY - contentTop) * scaleY / CELL_SIZE)

if (x < 0 || x >= GRID_SIZE || y < 0 || y >= GRID_SIZE) return null
return { x, y }
}, [])
}, [isExpanded])

// Place or erase a block at the given grid coordinates
const placeBlock = useCallback((gridX: number, gridY: number) => {
Expand Down
Loading