wip grid rework

Hunter
Wednesday, October 22nd, 2025 at 6:27 PM

script.js+73-16

script.js
  9  	const gridRect = grid.getBoundingClientRect();
 10  	return {
 11  		width: gridRect.width / GRID_COLS,
    -		height: gridRect.height / GRID_ROWS
 12 +		height: gridRect.height / GRID_ROWS,
 13 +		totalWidth: gridRect.width,
 14 +		totalHeight: gridRect.height
 15 +	};
 16 +}
 17 +
 18 +// Calculate pixel-perfect position for a cell range
 19 +// This accounts for grid cell borders (1px per cell) to ensure perfect alignment
 20 +function getPixelPerfectBounds(cellX, cellY, cellWidth, cellHeight) {
 21 +	// Get all grid cells and measure their actual positions
 22 +	const gridCells = grid.querySelectorAll('.grid-cell');
 23 +
 24 +	// Calculate the index of the top-left cell
 25 +	const startCellIndex = cellY * GRID_COLS + cellX;
 26 +	const startCell = gridCells[startCellIndex];
 27 +
 28 +	if (!startCell) {
 29 +		// Fallback if cell doesn't exist
 30 +		const cellSize = getCellSize();
 31 +		return {
 32 +			left: cellX * cellSize.width,
 33 +			top: cellY * cellSize.height,
 34 +			width: cellWidth * cellSize.width,
 35 +			height: cellHeight * cellSize.height
 36 +		};
 37 +	}
 38 +
 39 +	// Get the actual position of the start cell relative to the grid
 40 +	const gridRect = grid.getBoundingClientRect();
 41 +	const startCellRect = startCell.getBoundingClientRect();
 42 +
 43 +	const left = startCellRect.left - gridRect.left;
 44 +	const top = startCellRect.top - gridRect.top;
 45 +
 46 +	// Calculate end position by finding the bottom-right cell
 47 +	const endCellIndex = (cellY + cellHeight - 1) * GRID_COLS + (cellX + cellWidth - 1);
 48 +	const endCell = gridCells[endCellIndex];
 49 +
 50 +	if (!endCell) {
 51 +		// Fallback if end cell doesn't exist
 52 +		const cellSize = getCellSize();
 53 +		return {
 54 +			left: left,
 55 +			top: top,
 56 +			width: cellWidth * cellSize.width,
 57 +			height: cellHeight * cellSize.height
 58 +		};
 59 +	}
 60 +
 61 +	const endCellRect = endCell.getBoundingClientRect();
 62 +	const right = endCellRect.right - gridRect.left;
 63 +	const bottom = endCellRect.bottom - gridRect.top;
 64 +
 65 +	return {
 66 +		left: left,
 67 +		top: top,
 68 +		width: right - left,
 69 +		height: bottom - top
 70  	};
 71  }
 72  

250  		imageData.naturalHeight = img.naturalHeight;
251  
252  		// Calculate base scale to cover container (mimics object-fit: cover)
    -		const cellSize = getCellSize();
    -		const containerWidth = imageData.widthCells * cellSize.width;
    -		const containerHeight = imageData.heightCells * cellSize.height;
    -		const scaleX = containerWidth / img.naturalWidth;
    -		const scaleY = containerHeight / img.naturalHeight;
253 +		const bounds = getPixelPerfectBounds(imageData.xCell, imageData.yCell, imageData.widthCells, imageData.heightCells);
254 +		const scaleX = bounds.width / img.naturalWidth;
255 +		const scaleY = bounds.height / img.naturalHeight;
256  		imageData.baseScale = Math.max(scaleX, scaleY);
257  
258  		updateImagePosition(imageData);

269  
270  function calculatePanBounds(imageData) {
271  	// Container dimensions in the current (possibly swapped) grid orientation
    -	const cellSize = getCellSize();
    -	const containerWidth = imageData.widthCells * cellSize.width;
    -	const containerHeight = imageData.heightCells * cellSize.height;
272 +	const bounds = getPixelPerfectBounds(imageData.xCell, imageData.yCell, imageData.widthCells, imageData.heightCells);
273 +	const containerWidth = bounds.width;
274 +	const containerHeight = bounds.height;
275  
276  	if (imageData.naturalWidth === 0 || imageData.naturalHeight === 0) {
277  		return { maxPanX: 0, maxPanY: 0 };

336  }
337  
338  function updateImagePosition(img) {
    -	const cellSize = getCellSize();
    -	img.container.style.left = img.xCell * cellSize.width + 'px';
    -	img.container.style.top = img.yCell * cellSize.height + 'px';
    -	img.container.style.width = img.widthCells * cellSize.width + 'px';
    -	img.container.style.height = img.heightCells * cellSize.height + 'px';
339 +	// Use pixel-perfect bounds to prevent subpixel accumulation
340 +	const bounds = getPixelPerfectBounds(img.xCell, img.yCell, img.widthCells, img.heightCells);
341 +	img.container.style.left = bounds.left + 'px';
342 +	img.container.style.top = bounds.top + 'px';
343 +	img.container.style.width = bounds.width + 'px';
344 +	img.container.style.height = bounds.height + 'px';
345  
346  	// Store cell positions as CSS variables for print styles
347  	img.container.style.setProperty('--x-cell', img.xCell);

369  
370  	// Recalculate baseScale if container size changed
371  	if (img.naturalWidth > 0 && img.naturalHeight > 0) {
    -		const containerWidth = img.widthCells * cellSize.width;
    -		const containerHeight = img.heightCells * cellSize.height;
372 +		// Use pixel-perfect bounds for container dimensions
373 +		const containerWidth = bounds.width;
374 +		const containerHeight = bounds.height;
375  
376  		// When rotated 90° or 270°, the image dimensions are effectively swapped
377  		const isRotated90or270 = img.rotation % 180 !== 0;