split out html, css, and js

Hunter
Monday, October 20th, 2025 at 12:06 AM

index.html+2-680
script.jsnew
style.cssnew

index.html
 5  	<meta name="viewport" content="width=device-width, initial-scale=1.0">
 6  	<title>memori</title>
 7  	<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>✂️</text></svg>">
   -	<style>
   -		:root {
   -			--color-bg-body: #f5f5f0;
   -			--color-bg-page: #fafaf8;
   -			--color-grid-line: #d0d0d0;
   -			--color-accent: #4a90e2;
   -			--color-white: white;
   -		}
   -
   -		* {
   -			margin: 0;
   -			padding: 0;
   -			box-sizing: border-box;
   -		}
   -
   -		body {
   -			background: var(--color-bg-body);
   -			display: flex;
   -			justify-content: center;
   -			align-items: flex-start;
   -			min-height: 100vh;
   -			padding: 20px;
   -		}
   -
   -		.page {
   -			width: 8.5in;
   -			height: 11in;
   -			background: var(--color-bg-page);
   -			position: relative;
   -			box-shadow: 0 2px 10px rgba(0,0,0,0.1);
   -			transform-origin: top center;
   -		}
   -
   -		.grid {
   -			position: absolute;
   -			top: calc((11in - 264mm) / 2);
   -			left: calc((8.5in - 200mm) / 2);
   -			display: grid;
   -			grid-template-columns: repeat(50, 4mm);
   -			grid-template-rows: repeat(66, 4mm);
   -			gap: 0;
   -			line-height: 0;
   -		}
   -
   -		.grid-cell {
   -			width: 4mm;
   -			height: 4mm;
   -			box-sizing: border-box;
   -			border-right: 1px dashed var(--color-grid-line);
   -			border-bottom: 1px dashed var(--color-grid-line);
   -			margin: 0;
   -			padding: 0;
   -			display: block;
   -		}
   -
   -		.grid-cell:nth-child(-n+50) {
   -			border-top: 1px dashed var(--color-grid-line);
   -		}
   -
   -		.grid-cell:nth-child(50n+1) {
   -			border-left: 1px dashed var(--color-grid-line);
   -		}
   -
   -		.image-container {
   -			position: absolute;
   -			cursor: move;
   -			outline: 2px solid transparent;
   -			outline-offset: -2px;
   -			transition: outline-color 0.2s;
   -			background: var(--color-bg-page);
   -		}
   -
   -		.image-container::after {
   -			content: '';
   -			position: absolute;
   -			top: 0;
   -			left: 0;
   -			right: 0;
   -			bottom: 0;
   -			pointer-events: none;
   -			border: 2px solid transparent;
   -			transition: border-color 0.2s;
   -			z-index: 1;
   -			overflow: hidden;
   -		}
   -
   -		.image-container .image-wrapper {
   -			position: absolute;
   -			top: 0;
   -			left: 0;
   -			right: 0;
   -			bottom: 0;
   -			overflow: hidden;
   -		}
   -
   -		.image-container:hover::after {
   -			border-color: var(--color-accent);
   -		}
   -
   -		.image-container.dragging {
   -			opacity: 0.7;
   -		}
   -
   -		.image-container.dragging::after {
   -			border-color: var(--color-accent);
   -		}
   -
   -		.image-container.resizing::after {
   -			border-color: var(--color-accent);
   -		}
   -
   -		body.resizing *,
   -		body.dragging * {
   -			cursor: inherit !important;
   -		}
   -
   -		.image-container img {
   -			position: absolute;
   -			top: 50%;
   -			left: 50%;
   -			display: block;
   -			pointer-events: none;
   -			transform-origin: center center;
   -			transition: none;
   -		}
   -
   -		.resize-handle {
   -			position: absolute;
   -			background: var(--color-accent);
   -			opacity: 0;
   -			transition: opacity 0.2s;
   -			z-index: 2;
   -		}
   -
   -		.image-container:hover .resize-handle {
   -			opacity: 0.8;
   -		}
   -
   -		.resize-handle.corner {
   -			width: 10px;
   -			height: 10px;
   -			background: transparent;
   -		}
   -
   -		.resize-handle.corner::before {
   -			content: '';
   -			position: absolute;
   -			width: 10px;
   -			height: 10px;
   -			background: var(--color-accent);
   -			border-radius: 50%;
   -		}
   -
   -		.resize-handle.edge {
   -			background: transparent;
   -		}
   -
   -		.resize-handle.n { top: -5px; left: 5px; right: 5px; height: 10px; cursor: n-resize; }
   -		.resize-handle.s { bottom: -5px; left: 5px; right: 5px; height: 10px; cursor: s-resize; }
   -		.resize-handle.e { right: -5px; top: 5px; bottom: 5px; width: 10px; cursor: e-resize; }
   -		.resize-handle.w { left: -5px; top: 5px; bottom: 5px; width: 10px; cursor: w-resize; }
   -
   -		.resize-handle.ne { top: -5px; right: -5px; cursor: ne-resize; }
   -		.resize-handle.ne::before { top: 0; right: 0; }
   -
   -		.resize-handle.nw { top: -5px; left: -5px; cursor: nw-resize; }
   -		.resize-handle.nw::before { top: 0; left: 0; }
   -
   -		.resize-handle.se { bottom: -5px; right: -5px; cursor: se-resize; }
   -		.resize-handle.se::before { bottom: 0; right: 0; }
   -
   -		.resize-handle.sw { bottom: -5px; left: -5px; cursor: sw-resize; }
   -		.resize-handle.sw::before { bottom: 0; left: 0; }
   -
   -		@media print {
   -			* {
   -				-webkit-print-color-adjust: exact !important;
   -				print-color-adjust: exact !important;
   -			}
   -
   -			body {
   -				background: var(--color-white);
   -				padding: 0;
   -				margin: 0;
   -				display: block;
   -				align-items: initial;
   -				justify-content: initial;
   -			}
   -
   -			.page {
   -				width: 8.5in;
   -				height: 11in;
   -				box-shadow: none;
   -				margin: 0;
   -				padding: 0;
   -				background: var(--color-white);
   -				transform: none !important;
   -				position: relative;
   -			}
   -
   -			.grid {
   -				position: absolute;
   -				top: calc((11in - 264mm) / 2);
   -				left: calc((8.5in - 200mm) / 2);
   -				display: grid;
   -				grid-template-columns: repeat(50, 4mm);
   -				grid-template-rows: repeat(66, 4mm);
   -			}
   -
   -			.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;
   -				margin: 0 !important;
   -				padding: 0 !important;
   -			}
   -
   -			.grid-cell:nth-child(-n+50) {
   -				border-top: 1px dashed var(--color-grid-line) !important;
   -			}
   -
   -			.grid-cell:nth-child(50n+1) {
   -				border-left: 1px dashed var(--color-grid-line) !important;
   -			}
   -
   -			.image-container {
   -				outline: none !important;
   -				position: absolute !important;
   -				background: var(--color-white) !important;
   -			}
   -
   -			.image-container .image-wrapper {
   -				position: absolute !important;
   -				top: 0 !important;
   -				left: 0 !important;
   -				right: 0 !important;
   -				bottom: 0 !important;
   -				overflow: hidden !important;
   -			}
   -
   -			.image-container img {
   -				position: absolute !important;
   -				top: 50% !important;
   -				left: 50% !important;
   -				display: block !important;
   -				pointer-events: none !important;
   -				/* Preserve the transform from the screen view */
   -			}
   -
   -			.resize-handle {
   -				display: none !important;
   -			}
   -		}
   -
   -		@page {
   -			size: 8.5in 11in;
   -			margin: 0;
   -		}
   -	</style>
 8 +	<link rel="stylesheet" href="style.css">
 9  </head>
10  <body>
11  	<div class="page">
12  		<div class="grid" id="grid"></div>
13  	</div>
14  
   -	<script>
   -		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');
   -
   -		// Create grid cells
   -		for (let i = 0; i < GRID_COLS * GRID_ROWS; i++) {
   -			const cell = document.createElement('div');
   -			cell.className = 'grid-cell';
   -			grid.appendChild(cell);
   -		}
   -
   -		let images = [];
   -		let dragState = null;
   -		let resizeState = null;
   -
   -		// Prevent default drag behavior on entire document
   -		document.addEventListener('dragover', (e) => {
   -			e.preventDefault();
   -			e.stopPropagation();
   -		});
   -
   -		document.addEventListener('drop', async (e) => {
   -			e.preventDefault();
   -			e.stopPropagation();
   -
   -			const files = Array.from(e.dataTransfer.files).filter(f => f.type.startsWith('image/'));
   -			if (files.length === 0) return;
   -
   -			// 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;
   -
   -			// 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));
   -
   -			// Load first image to get base dimensions
   -			let firstImageDimensions = null;
   -			if (files.length > 0) {
   -				const firstFile = files[0];
   -				const reader = new FileReader();
   -				const dataUrl = await new Promise(resolve => {
   -					reader.onload = (e) => resolve(e.target.result);
   -					reader.readAsDataURL(firstFile);
   -				});
   -
   -				const img = new Image();
   -				await new Promise(resolve => {
   -					img.onload = () => {
   -						const aspectRatio = img.width / img.height;
   -						let widthCells, heightCells;
   -						if (aspectRatio >= 1) {
   -							heightCells = 5;
   -							widthCells = Math.round(heightCells * aspectRatio);
   -						} else {
   -							widthCells = 5;
   -							heightCells = Math.round(widthCells / aspectRatio);
   -						}
   -						widthCells = Math.min(widthCells, GRID_COLS);
   -						heightCells = Math.min(heightCells, GRID_ROWS);
   -						firstImageDimensions = { widthCells, heightCells };
   -						resolve();
   -					};
   -					img.src = dataUrl;
   -				});
   -			}
   -
   -			// Now process all files
   -			files.forEach((file, idx) => {
   -				const reader = new FileReader();
   -				reader.onload = (event) => {
   -					const img = new Image();
   -					img.onload = () => {
   -						const aspectRatio = img.width / img.height;
   -
   -						// Set smaller dimension to 5 cells, calculate larger dimension
   -						let widthCells, heightCells;
   -						if (aspectRatio >= 1) {
   -							// Width is larger or equal
   -							heightCells = 5;
   -							widthCells = Math.round(heightCells * aspectRatio);
   -						} else {
   -							// Height is larger
   -							widthCells = 5;
   -							heightCells = Math.round(widthCells / aspectRatio);
   -						}
   -
   -						// Clamp to grid boundaries
   -						widthCells = Math.min(widthCells, GRID_COLS);
   -						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);
   -
   -						// Ensure image stays within grid bounds
   -						xCell = Math.max(0, Math.min(GRID_COLS - widthCells, xCell));
   -						yCell = Math.max(0, Math.min(GRID_ROWS - heightCells, yCell));
   -
   -						addImage(event.target.result, xCell, yCell, widthCells, heightCells, idx + 1);
   -					};
   -					img.src = event.target.result;
   -				};
   -				reader.readAsDataURL(file);
   -			});
   -		});
   -
   -		function addImage(src, xCell, yCell, widthCells, heightCells, zIndex = 0) {
   -			const container = document.createElement('div');
   -			container.className = 'image-container';
   -			if (zIndex > 0) {
   -				container.style.zIndex = zIndex;
   -			}
   -
   -			const wrapper = document.createElement('div');
   -			wrapper.className = 'image-wrapper';
   -			const img = document.createElement('img');
   -			img.src = src;
   -			wrapper.appendChild(img);
   -			container.appendChild(wrapper);
   -
   -			// Add resize handles
   -			const handles = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'];
   -			handles.forEach(dir => {
   -				const handle = document.createElement('div');
   -				handle.className = `resize-handle ${dir.length === 1 ? 'edge' : 'corner'} ${dir}`;
   -				handle.dataset.direction = dir;
   -				container.appendChild(handle);
   -			});
   -
   -			const imageData = {
   -				container,
   -				xCell,
   -				yCell,
   -				widthCells,
   -				heightCells,
   -				// Image positioning within container (in pixels, relative to center)
   -				panX: 0,
   -				panY: 0,
   -				userScale: 1,  // user zoom level (1-5)
   -				// Store natural image dimensions for calculations
   -				naturalWidth: 0,
   -				naturalHeight: 0,
   -				baseScale: 1  // scale needed to cover container
   -			};
   -
   -			// Calculate dimensions and scale once image loads
   -			img.onload = () => {
   -				imageData.naturalWidth = img.naturalWidth;
   -				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 scaleX = containerWidth / img.naturalWidth;
   -				const scaleY = containerHeight / img.naturalHeight;
   -				imageData.baseScale = Math.max(scaleX, scaleY);
   -
   -				updateImagePosition(imageData);
   -			};
   -			images.push(imageData);
   -
   -			updateImagePosition(imageData);
   -			grid.appendChild(container);
   -
   -			setupImageHandlers(imageData);
   -		}
   -
   -		function calculatePanBounds(imageData) {
   -			const containerWidth = imageData.widthCells * CELL_SIZE_PX;
   -			const containerHeight = imageData.heightCells * CELL_SIZE_PX;
   -
   -			if (imageData.naturalWidth === 0 || imageData.naturalHeight === 0) {
   -				return { maxPanX: 0, maxPanY: 0 };
   -			}
   -
   -			// Calculate actual rendered size with both base scale and user scale
   -			const totalScale = imageData.baseScale * imageData.userScale;
   -			const renderedWidth = imageData.naturalWidth * totalScale;
   -			const renderedHeight = imageData.naturalHeight * totalScale;
   -
   -			// Calculate maximum pan in each direction
   -			const maxPanX = Math.max(0, (renderedWidth - containerWidth) / 2);
   -			const maxPanY = Math.max(0, (renderedHeight - containerHeight) / 2);
   -
   -			return { maxPanX, maxPanY };
   -		}
   -
   -		function clampPan(imageData) {
   -			const { maxPanX, maxPanY } = calculatePanBounds(imageData);
   -			imageData.panX = Math.max(-maxPanX, Math.min(maxPanX, imageData.panX));
   -			imageData.panY = Math.max(-maxPanY, Math.min(maxPanY, imageData.panY));
   -		}
   -
   -		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';
   -
   -			// 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 scaleX = containerWidth / img.naturalWidth;
   -				const scaleY = containerHeight / img.naturalHeight;
   -				img.baseScale = Math.max(scaleX, scaleY);
   -
   -				// Reclamp pan after recalculating base scale
   -				clampPan(img);
   -			}
   -
   -			// Apply image positioning and scale using transform
   -			const imgElement = img.container.querySelector('img');
   -			if (imgElement) {
   -				const totalScale = img.baseScale * img.userScale;
   -				// Transform: translate from center (-50%, -50%), then pan, then scale
   -				imgElement.style.transform = `translate(-50%, -50%) translate(${img.panX}px, ${img.panY}px) scale(${totalScale})`;
   -			}
   -		}
   -
   -		function setupImageHandlers(imageData) {
   -			const container = imageData.container;
   -
   -			// Moving / Deleting
   -			container.addEventListener('mousedown', (e) => {
   -				if (e.target.classList.contains('resize-handle')) return;
   -
   -				e.preventDefault();
   -
   -				// Shift-click to delete
   -				if (e.shiftKey) {
   -					const index = images.indexOf(imageData);
   -					if (index > -1) {
   -						images.splice(index, 1);
   -					}
   -					container.remove();
   -					return;
   -				}
   -
   -				// Lock cursor to move during drag operation
   -				document.body.style.cursor = 'move';
   -				document.body.classList.add('dragging');
   -
   -				dragState = {
   -					image: imageData,
   -					startX: e.clientX,
   -					startY: e.clientY,
   -					startXCell: imageData.xCell,
   -					startYCell: imageData.yCell
   -				};
   -				container.classList.add('dragging');
   -			});
   -
   -			// Resizing
   -			container.querySelectorAll('.resize-handle').forEach(handle => {
   -				handle.addEventListener('mousedown', (e) => {
   -					e.preventDefault();
   -					e.stopPropagation();
   -
   -					// Get the cursor style from the handle and apply it to the body
   -					const cursorStyle = window.getComputedStyle(handle).cursor;
   -					document.body.style.cursor = cursorStyle;
   -					document.body.classList.add('resizing');
   -
   -					resizeState = {
   -						image: imageData,
   -						direction: handle.dataset.direction,
   -						startX: e.clientX,
   -						startY: e.clientY,
   -						startXCell: imageData.xCell,
   -						startYCell: imageData.yCell,
   -						startWidthCells: imageData.widthCells,
   -						startHeightCells: imageData.heightCells
   -					};
   -					container.classList.add('resizing');
   -				});
   -			});
   -
   -			// Pan and Zoom with wheel events (macOS trackpad gestures)
   -			container.addEventListener('wheel', (e) => {
   -				// Don't interfere with dragging or resizing
   -				if (dragState || resizeState) return;
   -
   -				e.preventDefault();
   -				e.stopPropagation();
   -
   -				// Detect pinch zoom (ctrlKey is set for pinch gestures on macOS trackpad)
   -				if (e.ctrlKey) {
   -					// Zoom under cursor
   -					const rect = container.getBoundingClientRect();
   -					const mouseX = e.clientX - rect.left;
   -					const mouseY = e.clientY - rect.top;
   -
   -					// Calculate mouse position relative to container center
   -					const centerX = rect.width / 2;
   -					const centerY = rect.height / 2;
   -					const offsetX = mouseX - centerX;
   -					const offsetY = mouseY - centerY;
   -
   -					const oldUserScale = imageData.userScale;
   -					const zoomDelta = -e.deltaY * 0.01;
   -					const newUserScale = Math.max(1, Math.min(5, oldUserScale * (1 + zoomDelta)));
   -
   -					// Adjust pan to keep point under cursor stationary
   -					const scaleFactor = newUserScale / oldUserScale - 1;
   -					imageData.panX -= offsetX * scaleFactor;
   -					imageData.panY -= offsetY * scaleFactor;
   -					imageData.userScale = newUserScale;
   -
   -					// Clamp after zooming to prevent whitespace
   -					clampPan(imageData);
   -				} else {
   -					// Pan (two-finger scroll on macOS trackpad)
   -					imageData.panX -= e.deltaX;
   -					imageData.panY -= e.deltaY;
   -
   -					// Clamp pan to prevent whitespace
   -					clampPan(imageData);
   -				}
   -
   -				updateImagePosition(imageData);
   -			}, { passive: false });
   -		}
   -
   -		document.addEventListener('mousemove', (e) => {
   -			if (dragState) {
   -				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 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));
   -
   -				dragState.image.xCell = newXCell;
   -				dragState.image.yCell = newYCell;
   -				updateImagePosition(dragState.image);
   -			}
   -
   -			if (resizeState) {
   -				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 dir = resizeState.direction;
   -				const img = resizeState.image;
   -
   -				let newX = img.xCell;
   -				let newY = img.yCell;
   -				let newW = img.widthCells;
   -				let newH = img.heightCells;
   -
   -				if (dir.includes('e')) {
   -					const proposedW = Math.max(1, resizeState.startWidthCells + dxCells);
   -					// Clamp to grid boundary
   -					newW = Math.min(proposedW, GRID_COLS - resizeState.startXCell);
   -				}
   -				if (dir.includes('w')) {
   -					const delta = Math.min(dxCells, resizeState.startWidthCells - 1);
   -					const proposedX = resizeState.startXCell + delta;
   -					// Clamp to grid boundary
   -					const clampedX = Math.max(0, proposedX);
   -					newX = clampedX;
   -					newW = resizeState.startWidthCells - (clampedX - resizeState.startXCell);
   -				}
   -				if (dir.includes('s')) {
   -					const proposedH = Math.max(1, resizeState.startHeightCells + dyCells);
   -					// Clamp to grid boundary
   -					newH = Math.min(proposedH, GRID_ROWS - resizeState.startYCell);
   -				}
   -				if (dir.includes('n')) {
   -					const delta = Math.min(dyCells, resizeState.startHeightCells - 1);
   -					const proposedY = resizeState.startYCell + delta;
   -					// Clamp to grid boundary
   -					const clampedY = Math.max(0, proposedY);
   -					newY = clampedY;
   -					newH = resizeState.startHeightCells - (clampedY - resizeState.startYCell);
   -				}
   -
   -				img.xCell = newX;
   -				img.yCell = newY;
   -				img.widthCells = newW;
   -				img.heightCells = newH;
   -				updateImagePosition(img);
   -			}
   -		});
   -
   -		document.addEventListener('mouseup', () => {
   -			if (dragState) {
   -				dragState.image.container.classList.remove('dragging');
   -				dragState = null;
   -				// Restore the default cursor
   -				document.body.style.cursor = '';
   -				document.body.classList.remove('dragging');
   -			}
   -			if (resizeState) {
   -				resizeState.image.container.classList.remove('resizing');
   -				resizeState = null;
   -				// Restore the default cursor
   -				document.body.style.cursor = '';
   -				document.body.classList.remove('resizing');
   -			}
   -		});
   -	</script>
15 +	<script src="script.js"></script>
16  </body>
17  </html>
script.js
  1 +const MM_TO_PX = 96 / 25.4;
  2 +const CELL_SIZE_MM = 4;
  3 +const CELL_SIZE_PX = CELL_SIZE_MM * MM_TO_PX;
  4 +const GRID_OFFSET_IN = 0.25;
  5 +const GRID_OFFSET_PX = GRID_OFFSET_IN * 96;
  6 +const GRID_COLS = 50;
  7 +const GRID_ROWS = 66;
  8 +
  9 +const grid = document.getElementById('grid');
 10 +const page = document.querySelector('.page');
 11 +
 12 +// Create grid cells
 13 +for (let i = 0; i < GRID_COLS * GRID_ROWS; i++) {
 14 +	const cell = document.createElement('div');
 15 +	cell.className = 'grid-cell';
 16 +	grid.appendChild(cell);
 17 +}
 18 +
 19 +let images = [];
 20 +let dragState = null;
 21 +let resizeState = null;
 22 +
 23 +// Prevent default drag behavior on entire document
 24 +document.addEventListener('dragover', (e) => {
 25 +	e.preventDefault();
 26 +	e.stopPropagation();
 27 +});
 28 +
 29 +document.addEventListener('drop', async (e) => {
 30 +	e.preventDefault();
 31 +	e.stopPropagation();
 32 +
 33 +	const files = Array.from(e.dataTransfer.files).filter(f => f.type.startsWith('image/'));
 34 +	if (files.length === 0) return;
 35 +
 36 +	// Get drop position relative to the grid
 37 +	const gridRect = grid.getBoundingClientRect();
 38 +	const gridWidth = GRID_COLS * CELL_SIZE_PX;
 39 +	const gridHeight = GRID_ROWS * CELL_SIZE_PX;
 40 +
 41 +	// Clamp drop position to grid boundaries
 42 +	let dropX = e.clientX - gridRect.left;
 43 +	let dropY = e.clientY - gridRect.top;
 44 +	dropX = Math.max(0, Math.min(gridWidth, dropX));
 45 +	dropY = Math.max(0, Math.min(gridHeight, dropY));
 46 +
 47 +	// Load first image to get base dimensions
 48 +	let firstImageDimensions = null;
 49 +	if (files.length > 0) {
 50 +		const firstFile = files[0];
 51 +		const reader = new FileReader();
 52 +		const dataUrl = await new Promise(resolve => {
 53 +			reader.onload = (e) => resolve(e.target.result);
 54 +			reader.readAsDataURL(firstFile);
 55 +		});
 56 +
 57 +		const img = new Image();
 58 +		await new Promise(resolve => {
 59 +			img.onload = () => {
 60 +				const aspectRatio = img.width / img.height;
 61 +				let widthCells, heightCells;
 62 +				if (aspectRatio >= 1) {
 63 +					heightCells = 5;
 64 +					widthCells = Math.round(heightCells * aspectRatio);
 65 +				} else {
 66 +					widthCells = 5;
 67 +					heightCells = Math.round(widthCells / aspectRatio);
 68 +				}
 69 +				widthCells = Math.min(widthCells, GRID_COLS);
 70 +				heightCells = Math.min(heightCells, GRID_ROWS);
 71 +				firstImageDimensions = { widthCells, heightCells };
 72 +				resolve();
 73 +			};
 74 +			img.src = dataUrl;
 75 +		});
 76 +	}
 77 +
 78 +	// Now process all files
 79 +	files.forEach((file, idx) => {
 80 +		const reader = new FileReader();
 81 +		reader.onload = (event) => {
 82 +			const img = new Image();
 83 +			img.onload = () => {
 84 +				const aspectRatio = img.width / img.height;
 85 +
 86 +				// Set smaller dimension to 5 cells, calculate larger dimension
 87 +				let widthCells, heightCells;
 88 +				if (aspectRatio >= 1) {
 89 +					// Width is larger or equal
 90 +					heightCells = 5;
 91 +					widthCells = Math.round(heightCells * aspectRatio);
 92 +				} else {
 93 +					// Height is larger
 94 +					widthCells = 5;
 95 +					heightCells = Math.round(widthCells / aspectRatio);
 96 +				}
 97 +
 98 +				// Clamp to grid boundaries
 99 +				widthCells = Math.min(widthCells, GRID_COLS);
100 +				heightCells = Math.min(heightCells, GRID_ROWS);
101 +
102 +				// Calculate position with first image's center under drop point
103 +				let xCell = Math.round(dropX / CELL_SIZE_PX - firstImageDimensions.widthCells / 2) + (idx * 2);
104 +				let yCell = Math.round(dropY / CELL_SIZE_PX - firstImageDimensions.heightCells / 2);
105 +
106 +				// Ensure image stays within grid bounds
107 +				xCell = Math.max(0, Math.min(GRID_COLS - widthCells, xCell));
108 +				yCell = Math.max(0, Math.min(GRID_ROWS - heightCells, yCell));
109 +
110 +				addImage(event.target.result, xCell, yCell, widthCells, heightCells, idx + 1);
111 +			};
112 +			img.src = event.target.result;
113 +		};
114 +		reader.readAsDataURL(file);
115 +	});
116 +});
117 +
118 +function addImage(src, xCell, yCell, widthCells, heightCells, zIndex = 0) {
119 +	const container = document.createElement('div');
120 +	container.className = 'image-container';
121 +	if (zIndex > 0) {
122 +		container.style.zIndex = zIndex;
123 +	}
124 +
125 +	const wrapper = document.createElement('div');
126 +	wrapper.className = 'image-wrapper';
127 +	const img = document.createElement('img');
128 +	img.src = src;
129 +	wrapper.appendChild(img);
130 +	container.appendChild(wrapper);
131 +
132 +	// Add resize handles
133 +	const handles = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'];
134 +	handles.forEach(dir => {
135 +		const handle = document.createElement('div');
136 +		handle.className = `resize-handle ${dir.length === 1 ? 'edge' : 'corner'} ${dir}`;
137 +		handle.dataset.direction = dir;
138 +		container.appendChild(handle);
139 +	});
140 +
141 +	const imageData = {
142 +		container,
143 +		xCell,
144 +		yCell,
145 +		widthCells,
146 +		heightCells,
147 +		// Image positioning within container (in pixels, relative to center)
148 +		panX: 0,
149 +		panY: 0,
150 +		userScale: 1,  // user zoom level (1-5)
151 +		// Store natural image dimensions for calculations
152 +		naturalWidth: 0,
153 +		naturalHeight: 0,
154 +		baseScale: 1  // scale needed to cover container
155 +	};
156 +
157 +	// Calculate dimensions and scale once image loads
158 +	img.onload = () => {
159 +		imageData.naturalWidth = img.naturalWidth;
160 +		imageData.naturalHeight = img.naturalHeight;
161 +
162 +		// Calculate base scale to cover container (mimics object-fit: cover)
163 +		const containerWidth = imageData.widthCells * CELL_SIZE_PX;
164 +		const containerHeight = imageData.heightCells * CELL_SIZE_PX;
165 +		const scaleX = containerWidth / img.naturalWidth;
166 +		const scaleY = containerHeight / img.naturalHeight;
167 +		imageData.baseScale = Math.max(scaleX, scaleY);
168 +
169 +		updateImagePosition(imageData);
170 +	};
171 +	images.push(imageData);
172 +
173 +	updateImagePosition(imageData);
174 +	grid.appendChild(container);
175 +
176 +	setupImageHandlers(imageData);
177 +}
178 +
179 +function calculatePanBounds(imageData) {
180 +	const containerWidth = imageData.widthCells * CELL_SIZE_PX;
181 +	const containerHeight = imageData.heightCells * CELL_SIZE_PX;
182 +
183 +	if (imageData.naturalWidth === 0 || imageData.naturalHeight === 0) {
184 +		return { maxPanX: 0, maxPanY: 0 };
185 +	}
186 +
187 +	// Calculate actual rendered size with both base scale and user scale
188 +	const totalScale = imageData.baseScale * imageData.userScale;
189 +	const renderedWidth = imageData.naturalWidth * totalScale;
190 +	const renderedHeight = imageData.naturalHeight * totalScale;
191 +
192 +	// Calculate maximum pan in each direction
193 +	const maxPanX = Math.max(0, (renderedWidth - containerWidth) / 2);
194 +	const maxPanY = Math.max(0, (renderedHeight - containerHeight) / 2);
195 +
196 +	return { maxPanX, maxPanY };
197 +}
198 +
199 +function clampPan(imageData) {
200 +	const { maxPanX, maxPanY } = calculatePanBounds(imageData);
201 +	imageData.panX = Math.max(-maxPanX, Math.min(maxPanX, imageData.panX));
202 +	imageData.panY = Math.max(-maxPanY, Math.min(maxPanY, imageData.panY));
203 +}
204 +
205 +function updateImagePosition(img) {
206 +	img.container.style.left = img.xCell * CELL_SIZE_PX + 'px';
207 +	img.container.style.top = img.yCell * CELL_SIZE_PX + 'px';
208 +	img.container.style.width = img.widthCells * CELL_SIZE_PX + 'px';
209 +	img.container.style.height = img.heightCells * CELL_SIZE_PX + 'px';
210 +
211 +	// Recalculate baseScale if container size changed
212 +	if (img.naturalWidth > 0 && img.naturalHeight > 0) {
213 +		const containerWidth = img.widthCells * CELL_SIZE_PX;
214 +		const containerHeight = img.heightCells * CELL_SIZE_PX;
215 +		const scaleX = containerWidth / img.naturalWidth;
216 +		const scaleY = containerHeight / img.naturalHeight;
217 +		img.baseScale = Math.max(scaleX, scaleY);
218 +
219 +		// Reclamp pan after recalculating base scale
220 +		clampPan(img);
221 +	}
222 +
223 +	// Apply image positioning and scale using transform
224 +	const imgElement = img.container.querySelector('img');
225 +	if (imgElement) {
226 +		const totalScale = img.baseScale * img.userScale;
227 +		// Transform: translate from center (-50%, -50%), then pan, then scale
228 +		imgElement.style.transform = `translate(-50%, -50%) translate(${img.panX}px, ${img.panY}px) scale(${totalScale})`;
229 +	}
230 +}
231 +
232 +function setupImageHandlers(imageData) {
233 +	const container = imageData.container;
234 +
235 +	// Moving / Deleting
236 +	container.addEventListener('mousedown', (e) => {
237 +		if (e.target.classList.contains('resize-handle')) return;
238 +
239 +		e.preventDefault();
240 +
241 +		// Shift-click to delete
242 +		if (e.shiftKey) {
243 +			const index = images.indexOf(imageData);
244 +			if (index > -1) {
245 +				images.splice(index, 1);
246 +			}
247 +			container.remove();
248 +			return;
249 +		}
250 +
251 +		// Lock cursor to move during drag operation
252 +		document.body.style.cursor = 'move';
253 +		document.body.classList.add('dragging');
254 +
255 +		dragState = {
256 +			image: imageData,
257 +			startX: e.clientX,
258 +			startY: e.clientY,
259 +			startXCell: imageData.xCell,
260 +			startYCell: imageData.yCell
261 +		};
262 +		container.classList.add('dragging');
263 +	});
264 +
265 +	// Resizing
266 +	container.querySelectorAll('.resize-handle').forEach(handle => {
267 +		handle.addEventListener('mousedown', (e) => {
268 +			e.preventDefault();
269 +			e.stopPropagation();
270 +
271 +			// Get the cursor style from the handle and apply it to the body
272 +			const cursorStyle = window.getComputedStyle(handle).cursor;
273 +			document.body.style.cursor = cursorStyle;
274 +			document.body.classList.add('resizing');
275 +
276 +			resizeState = {
277 +				image: imageData,
278 +				direction: handle.dataset.direction,
279 +				startX: e.clientX,
280 +				startY: e.clientY,
281 +				startXCell: imageData.xCell,
282 +				startYCell: imageData.yCell,
283 +				startWidthCells: imageData.widthCells,
284 +				startHeightCells: imageData.heightCells
285 +			};
286 +			container.classList.add('resizing');
287 +		});
288 +	});
289 +
290 +	// Pan and Zoom with wheel events (macOS trackpad gestures)
291 +	container.addEventListener('wheel', (e) => {
292 +		// Don't interfere with dragging or resizing
293 +		if (dragState || resizeState) return;
294 +
295 +		e.preventDefault();
296 +		e.stopPropagation();
297 +
298 +		// Detect pinch zoom (ctrlKey is set for pinch gestures on macOS trackpad)
299 +		if (e.ctrlKey) {
300 +			// Zoom under cursor
301 +			const rect = container.getBoundingClientRect();
302 +			const mouseX = e.clientX - rect.left;
303 +			const mouseY = e.clientY - rect.top;
304 +
305 +			// Calculate mouse position relative to container center
306 +			const centerX = rect.width / 2;
307 +			const centerY = rect.height / 2;
308 +			const offsetX = mouseX - centerX;
309 +			const offsetY = mouseY - centerY;
310 +
311 +			const oldUserScale = imageData.userScale;
312 +			const zoomDelta = -e.deltaY * 0.01;
313 +			const newUserScale = Math.max(1, Math.min(5, oldUserScale * (1 + zoomDelta)));
314 +
315 +			// Adjust pan to keep point under cursor stationary
316 +			const scaleFactor = newUserScale / oldUserScale - 1;
317 +			imageData.panX -= offsetX * scaleFactor;
318 +			imageData.panY -= offsetY * scaleFactor;
319 +			imageData.userScale = newUserScale;
320 +
321 +			// Clamp after zooming to prevent whitespace
322 +			clampPan(imageData);
323 +		} else {
324 +			// Pan (two-finger scroll on macOS trackpad)
325 +			imageData.panX -= e.deltaX;
326 +			imageData.panY -= e.deltaY;
327 +
328 +			// Clamp pan to prevent whitespace
329 +			clampPan(imageData);
330 +		}
331 +
332 +		updateImagePosition(imageData);
333 +	}, { passive: false });
334 +}
335 +
336 +document.addEventListener('mousemove', (e) => {
337 +	if (dragState) {
338 +		const dx = e.clientX - dragState.startX;
339 +		const dy = e.clientY - dragState.startY;
340 +
341 +		const dxCells = Math.round(dx / CELL_SIZE_PX);
342 +		const dyCells = Math.round(dy / CELL_SIZE_PX);
343 +
344 +		const newXCell = Math.max(0, Math.min(GRID_COLS - dragState.image.widthCells, dragState.startXCell + dxCells));
345 +		const newYCell = Math.max(0, Math.min(GRID_ROWS - dragState.image.heightCells, dragState.startYCell + dyCells));
346 +
347 +		dragState.image.xCell = newXCell;
348 +		dragState.image.yCell = newYCell;
349 +		updateImagePosition(dragState.image);
350 +	}
351 +
352 +	if (resizeState) {
353 +		const dx = e.clientX - resizeState.startX;
354 +		const dy = e.clientY - resizeState.startY;
355 +
356 +		const dxCells = Math.round(dx / CELL_SIZE_PX);
357 +		const dyCells = Math.round(dy / CELL_SIZE_PX);
358 +
359 +		const dir = resizeState.direction;
360 +		const img = resizeState.image;
361 +
362 +		let newX = img.xCell;
363 +		let newY = img.yCell;
364 +		let newW = img.widthCells;
365 +		let newH = img.heightCells;
366 +
367 +		if (dir.includes('e')) {
368 +			const proposedW = Math.max(1, resizeState.startWidthCells + dxCells);
369 +			// Clamp to grid boundary
370 +			newW = Math.min(proposedW, GRID_COLS - resizeState.startXCell);
371 +		}
372 +		if (dir.includes('w')) {
373 +			const delta = Math.min(dxCells, resizeState.startWidthCells - 1);
374 +			const proposedX = resizeState.startXCell + delta;
375 +			// Clamp to grid boundary
376 +			const clampedX = Math.max(0, proposedX);
377 +			newX = clampedX;
378 +			newW = resizeState.startWidthCells - (clampedX - resizeState.startXCell);
379 +		}
380 +		if (dir.includes('s')) {
381 +			const proposedH = Math.max(1, resizeState.startHeightCells + dyCells);
382 +			// Clamp to grid boundary
383 +			newH = Math.min(proposedH, GRID_ROWS - resizeState.startYCell);
384 +		}
385 +		if (dir.includes('n')) {
386 +			const delta = Math.min(dyCells, resizeState.startHeightCells - 1);
387 +			const proposedY = resizeState.startYCell + delta;
388 +			// Clamp to grid boundary
389 +			const clampedY = Math.max(0, proposedY);
390 +			newY = clampedY;
391 +			newH = resizeState.startHeightCells - (clampedY - resizeState.startYCell);
392 +		}
393 +
394 +		img.xCell = newX;
395 +		img.yCell = newY;
396 +		img.widthCells = newW;
397 +		img.heightCells = newH;
398 +		updateImagePosition(img);
399 +	}
400 +});
401 +
402 +document.addEventListener('mouseup', () => {
403 +	if (dragState) {
404 +		dragState.image.container.classList.remove('dragging');
405 +		dragState = null;
406 +		// Restore the default cursor
407 +		document.body.style.cursor = '';
408 +		document.body.classList.remove('dragging');
409 +	}
410 +	if (resizeState) {
411 +		resizeState.image.container.classList.remove('resizing');
412 +		resizeState = null;
413 +		// Restore the default cursor
414 +		document.body.style.cursor = '';
415 +		document.body.classList.remove('resizing');
416 +	}
417 +});
style.css
  1 +:root {
  2 +	--color-bg-body: #f5f5f0;
  3 +	--color-bg-page: #fafaf8;
  4 +	--color-grid-line: #d0d0d0;
  5 +	--color-accent: #4a90e2;
  6 +	--color-white: white;
  7 +}
  8 +
  9 +* {
 10 +	margin: 0;
 11 +	padding: 0;
 12 +	box-sizing: border-box;
 13 +}
 14 +
 15 +body {
 16 +	background: var(--color-bg-body);
 17 +	display: flex;
 18 +	justify-content: center;
 19 +	align-items: flex-start;
 20 +	min-height: 100vh;
 21 +	padding: 20px;
 22 +}
 23 +
 24 +.page {
 25 +	width: 8.5in;
 26 +	height: 11in;
 27 +	background: var(--color-bg-page);
 28 +	position: relative;
 29 +	box-shadow: 0 2px 10px rgba(0,0,0,0.1);
 30 +	transform-origin: top center;
 31 +}
 32 +
 33 +.grid {
 34 +	position: absolute;
 35 +	top: calc((11in - 264mm) / 2);
 36 +	left: calc((8.5in - 200mm) / 2);
 37 +	display: grid;
 38 +	grid-template-columns: repeat(50, 4mm);
 39 +	grid-template-rows: repeat(66, 4mm);
 40 +	gap: 0;
 41 +	line-height: 0;
 42 +}
 43 +
 44 +.grid-cell {
 45 +	width: 4mm;
 46 +	height: 4mm;
 47 +	box-sizing: border-box;
 48 +	border-right: 1px dashed var(--color-grid-line);
 49 +	border-bottom: 1px dashed var(--color-grid-line);
 50 +	margin: 0;
 51 +	padding: 0;
 52 +	display: block;
 53 +}
 54 +
 55 +.grid-cell:nth-child(-n+50) {
 56 +	border-top: 1px dashed var(--color-grid-line);
 57 +}
 58 +
 59 +.grid-cell:nth-child(50n+1) {
 60 +	border-left: 1px dashed var(--color-grid-line);
 61 +}
 62 +
 63 +.image-container {
 64 +	position: absolute;
 65 +	cursor: move;
 66 +	outline: 2px solid transparent;
 67 +	outline-offset: -2px;
 68 +	transition: outline-color 0.2s;
 69 +	background: var(--color-bg-page);
 70 +}
 71 +
 72 +.image-container::after {
 73 +	content: '';
 74 +	position: absolute;
 75 +	top: 0;
 76 +	left: 0;
 77 +	right: 0;
 78 +	bottom: 0;
 79 +	pointer-events: none;
 80 +	border: 2px solid transparent;
 81 +	transition: border-color 0.2s;
 82 +	z-index: 1;
 83 +	overflow: hidden;
 84 +}
 85 +
 86 +.image-container .image-wrapper {
 87 +	position: absolute;
 88 +	top: 0;
 89 +	left: 0;
 90 +	right: 0;
 91 +	bottom: 0;
 92 +	overflow: hidden;
 93 +}
 94 +
 95 +.image-container:hover::after {
 96 +	border-color: var(--color-accent);
 97 +}
 98 +
 99 +.image-container.dragging {
100 +	opacity: 0.7;
101 +}
102 +
103 +.image-container.dragging::after {
104 +	border-color: var(--color-accent);
105 +}
106 +
107 +.image-container.resizing::after {
108 +	border-color: var(--color-accent);
109 +}
110 +
111 +body.resizing *,
112 +body.dragging * {
113 +	cursor: inherit !important;
114 +}
115 +
116 +.image-container img {
117 +	position: absolute;
118 +	top: 50%;
119 +	left: 50%;
120 +	display: block;
121 +	pointer-events: none;
122 +	transform-origin: center center;
123 +	transition: none;
124 +}
125 +
126 +.resize-handle {
127 +	position: absolute;
128 +	background: var(--color-accent);
129 +	opacity: 0;
130 +	transition: opacity 0.2s;
131 +	z-index: 2;
132 +}
133 +
134 +.image-container:hover .resize-handle {
135 +	opacity: 0.8;
136 +}
137 +
138 +.resize-handle.corner {
139 +	width: 10px;
140 +	height: 10px;
141 +	background: transparent;
142 +}
143 +
144 +.resize-handle.corner::before {
145 +	content: '';
146 +	position: absolute;
147 +	width: 10px;
148 +	height: 10px;
149 +	background: var(--color-accent);
150 +	border-radius: 50%;
151 +}
152 +
153 +.resize-handle.edge {
154 +	background: transparent;
155 +}
156 +
157 +.resize-handle.n { top: -5px; left: 5px; right: 5px; height: 10px; cursor: n-resize; }
158 +.resize-handle.s { bottom: -5px; left: 5px; right: 5px; height: 10px; cursor: s-resize; }
159 +.resize-handle.e { right: -5px; top: 5px; bottom: 5px; width: 10px; cursor: e-resize; }
160 +.resize-handle.w { left: -5px; top: 5px; bottom: 5px; width: 10px; cursor: w-resize; }
161 +
162 +.resize-handle.ne { top: -5px; right: -5px; cursor: ne-resize; }
163 +.resize-handle.ne::before { top: 0; right: 0; }
164 +
165 +.resize-handle.nw { top: -5px; left: -5px; cursor: nw-resize; }
166 +.resize-handle.nw::before { top: 0; left: 0; }
167 +
168 +.resize-handle.se { bottom: -5px; right: -5px; cursor: se-resize; }
169 +.resize-handle.se::before { bottom: 0; right: 0; }
170 +
171 +.resize-handle.sw { bottom: -5px; left: -5px; cursor: sw-resize; }
172 +.resize-handle.sw::before { bottom: 0; left: 0; }
173 +
174 +@media print {
175 +	* {
176 +		-webkit-print-color-adjust: exact !important;
177 +		print-color-adjust: exact !important;
178 +	}
179 +
180 +	body {
181 +		background: var(--color-white);
182 +		padding: 0;
183 +		margin: 0;
184 +		display: block;
185 +		align-items: initial;
186 +		justify-content: initial;
187 +	}
188 +
189 +	.page {
190 +		width: 8.5in;
191 +		height: 11in;
192 +		box-shadow: none;
193 +		margin: 0;
194 +		padding: 0;
195 +		background: var(--color-white);
196 +		transform: none !important;
197 +		position: relative;
198 +	}
199 +
200 +	.grid {
201 +		position: absolute;
202 +		top: calc((11in - 264mm) / 2);
203 +		left: calc((8.5in - 200mm) / 2);
204 +		display: grid;
205 +		grid-template-columns: repeat(50, 4mm);
206 +		grid-template-rows: repeat(66, 4mm);
207 +	}
208 +
209 +	.grid-cell {
210 +		border-right: 1px dashed var(--color-grid-line) !important;
211 +		border-bottom: 1px dashed var(--color-grid-line) !important;
212 +		box-sizing: border-box !important;
213 +		width: 4mm !important;
214 +		height: 4mm !important;
215 +		margin: 0 !important;
216 +		padding: 0 !important;
217 +	}
218 +
219 +	.grid-cell:nth-child(-n+50) {
220 +		border-top: 1px dashed var(--color-grid-line) !important;
221 +	}
222 +
223 +	.grid-cell:nth-child(50n+1) {
224 +		border-left: 1px dashed var(--color-grid-line) !important;
225 +	}
226 +
227 +	.image-container {
228 +		outline: none !important;
229 +		position: absolute !important;
230 +		background: var(--color-white) !important;
231 +	}
232 +
233 +	.image-container .image-wrapper {
234 +		position: absolute !important;
235 +		top: 0 !important;
236 +		left: 0 !important;
237 +		right: 0 !important;
238 +		bottom: 0 !important;
239 +		overflow: hidden !important;
240 +	}
241 +
242 +	.image-container img {
243 +		position: absolute !important;
244 +		top: 50% !important;
245 +		left: 50% !important;
246 +		display: block !important;
247 +		pointer-events: none !important;
248 +		/* Preserve the transform from the screen view */
249 +	}
250 +
251 +	.resize-handle {
252 +		display: none !important;
253 +	}
254 +}
255 +
256 +@page {
257 +	size: 8.5in 11in;
258 +	margin: 0;
259 +}