commit e657b0d19023b26ff34ee47a307ef74448910078
parent c063a5e974ce874b241f291266d63251c0b6a33f
Author: Hunter
Date:   Mon, 20 Oct 2025 15:14:19 -0400

first pass at responsive layout

Diffstat:
Mscript.js | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mstyle.css | 69++++++++++++++++++++++++++++++++++++++++++++++++---------------------
2 files changed, 163 insertions(+), 46 deletions(-)

diff --git a/script.js b/script.js @@ -1,14 +1,18 @@ -const MM_TO_PX = 96 / 25.4; -const CELL_SIZE_MM = 4; -const CELL_SIZE_PX = CELL_SIZE_MM * MM_TO_PX; -const GRID_OFFSET_IN = 0.25; -const GRID_OFFSET_PX = GRID_OFFSET_IN * 96; const GRID_COLS = 50; const GRID_ROWS = 66; const grid = document.getElementById('grid'); const page = document.querySelector('.page'); +// Calculate cell size dynamically based on actual grid dimensions +function getCellSize() { + const gridRect = grid.getBoundingClientRect(); + return { + width: gridRect.width / GRID_COLS, + height: gridRect.height / GRID_ROWS + }; +} + // Create grid cells for (let i = 0; i < GRID_COLS * GRID_ROWS; i++) { const cell = document.createElement('div'); @@ -36,14 +40,13 @@ document.addEventListener('drop', async (e) => { // Get drop position relative to the grid const gridRect = grid.getBoundingClientRect(); - const gridWidth = GRID_COLS * CELL_SIZE_PX; - const gridHeight = GRID_ROWS * CELL_SIZE_PX; + const cellSize = getCellSize(); // Clamp drop position to grid boundaries let dropX = e.clientX - gridRect.left; let dropY = e.clientY - gridRect.top; - dropX = Math.max(0, Math.min(gridWidth, dropX)); - dropY = Math.max(0, Math.min(gridHeight, dropY)); + dropX = Math.max(0, Math.min(gridRect.width, dropX)); + dropY = Math.max(0, Math.min(gridRect.height, dropY)); // Load first image to get base dimensions let firstImageDimensions = null; @@ -101,8 +104,8 @@ document.addEventListener('drop', async (e) => { heightCells = Math.min(heightCells, GRID_ROWS); // Calculate position with first image's center under drop point - let xCell = Math.round(dropX / CELL_SIZE_PX - firstImageDimensions.widthCells / 2) + (idx * 2); - let yCell = Math.round(dropY / CELL_SIZE_PX - firstImageDimensions.heightCells / 2); + let xCell = Math.round(dropX / cellSize.width - firstImageDimensions.widthCells / 2) + (idx * 2); + let yCell = Math.round(dropY / cellSize.height - firstImageDimensions.heightCells / 2); // Ensure image stays within grid bounds xCell = Math.max(0, Math.min(GRID_COLS - widthCells, xCell)); @@ -172,8 +175,9 @@ function addImage(src, xCell, yCell, widthCells, heightCells) { imageData.naturalHeight = img.naturalHeight; // Calculate base scale to cover container (mimics object-fit: cover) - const containerWidth = imageData.widthCells * CELL_SIZE_PX; - const containerHeight = imageData.heightCells * CELL_SIZE_PX; + 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; imageData.baseScale = Math.max(scaleX, scaleY); @@ -192,8 +196,9 @@ function addImage(src, xCell, yCell, widthCells, heightCells) { function calculatePanBounds(imageData) { // Container dimensions in the current (possibly swapped) grid orientation - const containerWidth = imageData.widthCells * CELL_SIZE_PX; - const containerHeight = imageData.heightCells * CELL_SIZE_PX; + const cellSize = getCellSize(); + const containerWidth = imageData.widthCells * cellSize.width; + const containerHeight = imageData.heightCells * cellSize.height; if (imageData.naturalWidth === 0 || imageData.naturalHeight === 0) { return { maxPanX: 0, maxPanY: 0 }; @@ -231,10 +236,17 @@ function clampPan(imageData) { } function updateImagePosition(img) { - img.container.style.left = img.xCell * CELL_SIZE_PX + 'px'; - img.container.style.top = img.yCell * CELL_SIZE_PX + 'px'; - img.container.style.width = img.widthCells * CELL_SIZE_PX + 'px'; - img.container.style.height = img.heightCells * CELL_SIZE_PX + 'px'; + 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'; + + // Store cell positions as CSS variables for print styles + img.container.style.setProperty('--x-cell', img.xCell); + img.container.style.setProperty('--y-cell', img.yCell); + img.container.style.setProperty('--width-cells', img.widthCells); + img.container.style.setProperty('--height-cells', img.heightCells); // Update dimension labels const widthLabel = img.container.querySelector('.dimension-label.width'); @@ -244,8 +256,8 @@ function updateImagePosition(img) { // Recalculate baseScale if container size changed if (img.naturalWidth > 0 && img.naturalHeight > 0) { - const containerWidth = img.widthCells * CELL_SIZE_PX; - const containerHeight = img.heightCells * CELL_SIZE_PX; + const containerWidth = img.widthCells * cellSize.width; + const containerHeight = img.heightCells * cellSize.height; // When rotated 90° or 270°, the image dimensions are effectively swapped const isRotated90or270 = img.rotation % 180 !== 0; @@ -267,6 +279,14 @@ function updateImagePosition(img) { // Transform: translate from center (-50%, -50%), scale, rotate, then pan // Pan is applied after rotation so it stays relative to the image's rotated state imgElement.style.transform = `translate(-50%, -50%) scale(${totalScale}) rotate(${img.rotation}deg) translate(${img.panX}px, ${img.panY}px)`; + + // Store transform parameters as CSS variables for print recalculation + img.container.style.setProperty('--user-scale', img.userScale); + img.container.style.setProperty('--rotation', img.rotation); + img.container.style.setProperty('--pan-x', img.panX); + img.container.style.setProperty('--pan-y', img.panY); + img.container.style.setProperty('--natural-width', img.naturalWidth); + img.container.style.setProperty('--natural-height', img.naturalHeight); } } @@ -508,8 +528,9 @@ document.addEventListener('mousemove', (e) => { const dx = e.clientX - dragState.startX; const dy = e.clientY - dragState.startY; - const dxCells = Math.round(dx / CELL_SIZE_PX); - const dyCells = Math.round(dy / CELL_SIZE_PX); + const cellSize = getCellSize(); + const dxCells = Math.round(dx / cellSize.width); + const dyCells = Math.round(dy / cellSize.height); const newXCell = Math.max(0, Math.min(GRID_COLS - dragState.image.widthCells, dragState.startXCell + dxCells)); const newYCell = Math.max(0, Math.min(GRID_ROWS - dragState.image.heightCells, dragState.startYCell + dyCells)); @@ -523,8 +544,9 @@ document.addEventListener('mousemove', (e) => { const dx = e.clientX - resizeState.startX; const dy = e.clientY - resizeState.startY; - const dxCells = Math.round(dx / CELL_SIZE_PX); - const dyCells = Math.round(dy / CELL_SIZE_PX); + const cellSize = getCellSize(); + const dxCells = Math.round(dx / cellSize.width); + const dyCells = Math.round(dy / cellSize.height); const dir = resizeState.direction; const img = resizeState.image; @@ -585,3 +607,71 @@ document.addEventListener('mouseup', () => { document.body.classList.remove('resizing'); } }); + +// Update all image positions when window resizes (for responsive scaling) +window.addEventListener('resize', () => { + images.forEach(img => { + updateImagePosition(img); + }); +}); + +// Store original transforms before print +let beforePrintTransforms = []; + +// Recalculate image transforms for print +window.addEventListener('beforeprint', () => { + const PRINT_CELL_SIZE_MM = 4; + const MM_TO_PX_PRINT = 96 / 25.4; // Conversion for print + const printCellSizePx = PRINT_CELL_SIZE_MM * MM_TO_PX_PRINT; + + // Store current transforms and container styles + beforePrintTransforms = images.map(img => { + const imgElement = img.container.querySelector('img'); + return { + container: img.container, + transform: imgElement ? imgElement.style.transform : '', + left: img.container.style.left, + top: img.container.style.top, + width: img.container.style.width, + height: img.container.style.height + }; + }); + + images.forEach(img => { + const imgElement = img.container.querySelector('img'); + if (imgElement && img.naturalWidth > 0 && img.naturalHeight > 0) { + // Calculate container size at print (in px) + const containerWidth = img.widthCells * printCellSizePx; + const containerHeight = img.heightCells * printCellSizePx; + + // Calculate base scale for print + const isRotated90or270 = img.rotation % 180 !== 0; + const effectiveWidth = isRotated90or270 ? img.naturalHeight : img.naturalWidth; + const effectiveHeight = isRotated90or270 ? img.naturalWidth : img.naturalHeight; + + const scaleX = containerWidth / effectiveWidth; + const scaleY = containerHeight / effectiveHeight; + const printBaseScale = Math.max(scaleX, scaleY); + const printTotalScale = printBaseScale * img.userScale; + + // Apply transform for print + imgElement.style.transform = `translate(-50%, -50%) scale(${printTotalScale}) rotate(${img.rotation}deg) translate(${img.panX}px, ${img.panY}px)`; + } + }); +}); + +// Restore screen transforms after print +window.addEventListener('afterprint', () => { + // Restore the exact transforms and positions from before print + beforePrintTransforms.forEach(saved => { + const imgElement = saved.container.querySelector('img'); + if (imgElement) { + imgElement.style.transform = saved.transform; + } + saved.container.style.left = saved.left; + saved.container.style.top = saved.top; + saved.container.style.width = saved.width; + saved.container.style.height = saved.height; + }); + beforePrintTransforms = []; +}); diff --git a/style.css b/style.css @@ -5,6 +5,17 @@ --color-accent: #79aeea; --color-white: white; --shadow: #00000044; + + /* Grid configuration */ + --grid-cols: 50; + --grid-rows: 66; + --cell-size-mm: 4mm; /* Cell size for print */ + + /* Page dimensions: 8.5in = 216mm, 11in = 279.4mm */ + /* Grid dimensions at print: 200mm x 264mm */ + /* Grid as percentage of page: 200/216 = 92.59%, 264/279.4 = 94.48% */ + --grid-width-percent: 92.59%; + --grid-height-percent: 94.48%; } * { @@ -20,31 +31,39 @@ body { align-items: flex-start; min-height: 100vh; padding: 20px; + overflow-x: hidden; } .page { - width: 8.5in; - height: 11in; + /* Scale down on narrow screens, but never larger than 8.5in */ + max-width: min(8.5in, calc(100vw - 40px)); + width: 100%; + /* Maintain 8.5:11 aspect ratio (11/8.5 = 1.294117647) */ + aspect-ratio: 8.5 / 11; background: var(--color-bg-page); position: relative; box-shadow: 0 2px 10px rgba(0,0,0,0.1); - transform-origin: top center; + display: flex; + align-items: center; + justify-content: center; } .grid { - position: absolute; - top: calc((11in - 264mm) / 2); - left: calc((8.5in - 200mm) / 2); + /* Grid dimensions as percentage of page to scale responsively */ + width: var(--grid-width-percent); + height: var(--grid-height-percent); display: grid; - grid-template-columns: repeat(50, 4mm); - grid-template-rows: repeat(66, 4mm); + grid-template-columns: repeat(var(--grid-cols), 1fr); + grid-template-rows: repeat(var(--grid-rows), 1fr); gap: 0; line-height: 0; + position: relative; /* Image containers are positioned relative to grid */ + /* Grid is centered within .page by the parent's flexbox */ } .grid-cell { - width: 4mm; - height: 4mm; + width: 100%; + height: 100%; box-sizing: border-box; border-right: 1px dashed var(--color-grid-line); border-bottom: 1px dashed var(--color-grid-line); @@ -224,31 +243,34 @@ body.dragging * { } .page { - width: 8.5in; - height: 11in; + width: 8.5in !important; + max-width: 8.5in !important; + height: 11in !important; box-shadow: none; margin: 0; padding: 0; background: var(--color-white); - transform: none !important; - position: relative; + display: flex !important; + align-items: center !important; + justify-content: center !important; } .grid { - position: absolute; - top: calc((11in - 264mm) / 2); - left: calc((8.5in - 200mm) / 2); + /* Use fixed dimensions for print: 50 cells * 4mm = 200mm, 66 cells * 4mm = 264mm */ + width: 200mm !important; + height: 264mm !important; display: grid; - grid-template-columns: repeat(50, 4mm); - grid-template-rows: repeat(66, 4mm); + grid-template-columns: repeat(var(--grid-cols), 1fr) !important; + grid-template-rows: repeat(var(--grid-rows), 1fr) !important; + position: relative !important; } .grid-cell { border-right: 1px dashed var(--color-grid-line) !important; border-bottom: 1px dashed var(--color-grid-line) !important; box-sizing: border-box !important; - width: 4mm !important; - height: 4mm !important; + width: 100% !important; + height: 100% !important; margin: 0 !important; padding: 0 !important; } @@ -265,6 +287,11 @@ body.dragging * { outline: none !important; position: absolute !important; background: var(--color-white) !important; + /* Recalculate positions using cell coordinates and print cell size */ + left: calc(var(--x-cell) * var(--cell-size-mm)) !important; + top: calc(var(--y-cell) * var(--cell-size-mm)) !important; + width: calc(var(--width-cells) * var(--cell-size-mm)) !important; + height: calc(var(--height-cells) * var(--cell-size-mm)) !important; } .image-container .image-wrapper {