initial commit

Hunter
Wednesday, October 15th, 2025 at 4:13 PM

index.htmlnew

index.html
  1 +<!DOCTYPE html>
  2 +<html lang="en">
  3 +<head>
  4 +	<meta charset="UTF-8">
  5 +	<meta name="viewport" content="width=device-width, initial-scale=1.0">
  6 +	<title>memori</title>
  7 +	<style>
  8 +		* {
  9 +			margin: 0;
 10 +			padding: 0;
 11 +			box-sizing: border-box;
 12 +		}
 13 +
 14 +		body {
 15 +			background: #f5f5f0;
 16 +			display: flex;
 17 +			justify-content: center;
 18 +			align-items: flex-start;
 19 +			min-height: 100vh;
 20 +			padding: 20px;
 21 +		}
 22 +
 23 +		.page {
 24 +			width: 8.5in;
 25 +			height: 11in;
 26 +			background: #fafaf8;
 27 +			position: relative;
 28 +			box-shadow: 0 2px 10px rgba(0,0,0,0.1);
 29 +			transform-origin: top center;
 30 +		}
 31 +
 32 +		.grid {
 33 +			position: absolute;
 34 +			top: 0.25in;
 35 +			left: 0.25in;
 36 +			display: grid;
 37 +			grid-template-columns: repeat(50, 4mm);
 38 +			grid-template-rows: repeat(66, 4mm);
 39 +			gap: 0;
 40 +			line-height: 0;
 41 +		}
 42 +
 43 +		.grid-cell {
 44 +			width: 4mm;
 45 +			height: 4mm;
 46 +			box-sizing: border-box;
 47 +			border-right: 1px solid #d0d0d0;
 48 +			border-bottom: 1px solid #d0d0d0;
 49 +			margin: 0;
 50 +			padding: 0;
 51 +			display: block;
 52 +		}
 53 +
 54 +		.grid-cell:nth-child(-n+50) {
 55 +			border-top: 1px solid #d0d0d0;
 56 +		}
 57 +
 58 +		.grid-cell:nth-child(50n+1) {
 59 +			border-left: 1px solid #d0d0d0;
 60 +		}
 61 +
 62 +		.image-container {
 63 +			position: absolute;
 64 +			cursor: move;
 65 +			outline: 2px solid transparent;
 66 +			outline-offset: -2px;
 67 +			transition: outline-color 0.2s;
 68 +		}
 69 +
 70 +		.image-container:hover {
 71 +			outline-color: #4a90e2;
 72 +		}
 73 +
 74 +		.image-container.dragging {
 75 +			opacity: 0.7;
 76 +			outline-color: #4a90e2;
 77 +		}
 78 +
 79 +		.image-container.resizing {
 80 +			outline-color: #4a90e2;
 81 +		}
 82 +
 83 +		.image-container img {
 84 +			width: 100%;
 85 +			height: 100%;
 86 +			object-fit: cover;
 87 +			display: block;
 88 +			pointer-events: none;
 89 +		}
 90 +
 91 +		.resize-handle {
 92 +			position: absolute;
 93 +			background: #4a90e2;
 94 +			opacity: 0;
 95 +			transition: opacity 0.2s;
 96 +		}
 97 +
 98 +		.image-container:hover .resize-handle {
 99 +			opacity: 0.8;
100 +		}
101 +
102 +		.resize-handle.corner {
103 +			width: 10px;
104 +			height: 10px;
105 +			border-radius: 50%;
106 +		}
107 +
108 +		.resize-handle.edge {
109 +			background: transparent;
110 +		}
111 +
112 +		.resize-handle.n { top: -5px; left: 50%; transform: translateX(-50%); width: 30px; height: 10px; cursor: n-resize; }
113 +		.resize-handle.s { bottom: -5px; left: 50%; transform: translateX(-50%); width: 30px; height: 10px; cursor: s-resize; }
114 +		.resize-handle.e { right: -5px; top: 50%; transform: translateY(-50%); width: 10px; height: 30px; cursor: e-resize; }
115 +		.resize-handle.w { left: -5px; top: 50%; transform: translateY(-50%); width: 10px; height: 30px; cursor: w-resize; }
116 +		.resize-handle.ne { top: -5px; right: -5px; cursor: ne-resize; }
117 +		.resize-handle.nw { top: -5px; left: -5px; cursor: nw-resize; }
118 +		.resize-handle.se { bottom: -5px; right: -5px; cursor: se-resize; }
119 +		.resize-handle.sw { bottom: -5px; left: -5px; cursor: sw-resize; }
120 +
121 +		@media print {
122 +			* {
123 +				-webkit-print-color-adjust: exact !important;
124 +				print-color-adjust: exact !important;
125 +			}
126 +
127 +			body {
128 +				background: white;
129 +				padding: 0;
130 +				margin: 0;
131 +			}
132 +
133 +			.page {
134 +				width: 8.5in;
135 +				height: 11in;
136 +				box-shadow: none;
137 +				margin: 0;
138 +				padding: 0;
139 +				background: white;
140 +				transform: none !important;
141 +			}
142 +
143 +			.grid {
144 +				position: absolute;
145 +				top: 0.25in;
146 +				left: 0.25in;
147 +			}
148 +
149 +			.grid-cell {
150 +				border-right: 1px solid #d0d0d0 !important;
151 +				border-bottom: 1px solid #d0d0d0 !important;
152 +				box-sizing: border-box !important;
153 +				width: 4mm !important;
154 +				height: 4mm !important;
155 +				margin: 0 !important;
156 +				padding: 0 !important;
157 +			}
158 +
159 +			.grid-cell:nth-child(-n+50) {
160 +				border-top: 1px solid #d0d0d0 !important;
161 +			}
162 +
163 +			.grid-cell:nth-child(50n+1) {
164 +				border-left: 1px solid #d0d0d0 !important;
165 +			}
166 +
167 +			.image-container {
168 +				outline: none !important;
169 +				position: absolute !important;
170 +			}
171 +
172 +			.image-container img {
173 +				width: 100% !important;
174 +				height: 100% !important;
175 +				object-fit: cover !important;
176 +			}
177 +
178 +			.resize-handle {
179 +				display: none !important;
180 +			}
181 +		}
182 +
183 +		@page {
184 +			size: 8.5in 11in;
185 +			margin: 0;
186 +		}
187 +	</style>
188 +</head>
189 +<body>
190 +	<div class="page">
191 +		<div class="grid" id="grid"></div>
192 +	</div>
193 +
194 +	<script>
195 +		const MM_TO_PX = 96 / 25.4;
196 +		const CELL_SIZE_MM = 4;
197 +		const CELL_SIZE_PX = CELL_SIZE_MM * MM_TO_PX;
198 +		const GRID_OFFSET_IN = 0.25;
199 +		const GRID_OFFSET_PX = GRID_OFFSET_IN * 96;
200 +		const GRID_COLS = 50;
201 +		const GRID_ROWS = 66;
202 +
203 +		const grid = document.getElementById('grid');
204 +		const page = document.querySelector('.page');
205 +
206 +		// Create grid cells
207 +		for (let i = 0; i < GRID_COLS * GRID_ROWS; i++) {
208 +			const cell = document.createElement('div');
209 +			cell.className = 'grid-cell';
210 +			grid.appendChild(cell);
211 +		}
212 +
213 +		let images = [];
214 +		let dragState = null;
215 +		let resizeState = null;
216 +
217 +		// Prevent default drag behavior
218 +		page.addEventListener('dragover', (e) => {
219 +			e.preventDefault();
220 +			e.stopPropagation();
221 +		});
222 +
223 +		page.addEventListener('drop', (e) => {
224 +			e.preventDefault();
225 +			e.stopPropagation();
226 +
227 +			const files = Array.from(e.dataTransfer.files).filter(f => f.type.startsWith('image/'));
228 +
229 +			files.forEach((file, idx) => {
230 +				const reader = new FileReader();
231 +				reader.onload = (event) => {
232 +					const img = new Image();
233 +					img.onload = () => {
234 +						const aspectRatio = img.width / img.height;
235 +
236 +						// Calculate best fit in grid cells
237 +						let widthCells = Math.round(Math.sqrt(16 * aspectRatio));
238 +						let heightCells = Math.round(widthCells / aspectRatio);
239 +
240 +						// Ensure minimum size
241 +						widthCells = Math.max(2, widthCells);
242 +						heightCells = Math.max(2, heightCells);
243 +
244 +						// Calculate position in grid cells
245 +						const xCell = 2 + (idx * 2) % 20;
246 +						const yCell = 2 + Math.floor((idx * 2) / 20) * 2;
247 +
248 +						addImage(event.target.result, xCell, yCell, widthCells, heightCells);
249 +					};
250 +					img.src = event.target.result;
251 +				};
252 +				reader.readAsDataURL(file);
253 +			});
254 +		});
255 +
256 +		function addImage(src, xCell, yCell, widthCells, heightCells) {
257 +			const container = document.createElement('div');
258 +			container.className = 'image-container';
259 +
260 +			const img = document.createElement('img');
261 +			img.src = src;
262 +			container.appendChild(img);
263 +
264 +			// Add resize handles
265 +			const handles = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'];
266 +			handles.forEach(dir => {
267 +				const handle = document.createElement('div');
268 +				handle.className = `resize-handle ${dir.length === 1 ? 'edge' : 'corner'} ${dir}`;
269 +				handle.dataset.direction = dir;
270 +				container.appendChild(handle);
271 +			});
272 +
273 +			const imageData = { container, xCell, yCell, widthCells, heightCells };
274 +			images.push(imageData);
275 +
276 +			updateImagePosition(imageData);
277 +			grid.appendChild(container);
278 +
279 +			setupImageHandlers(imageData);
280 +		}
281 +
282 +		function updateImagePosition(img) {
283 +			img.container.style.left = img.xCell * CELL_SIZE_PX + 'px';
284 +			img.container.style.top = img.yCell * CELL_SIZE_PX + 'px';
285 +			img.container.style.width = img.widthCells * CELL_SIZE_PX + 'px';
286 +			img.container.style.height = img.heightCells * CELL_SIZE_PX + 'px';
287 +		}
288 +
289 +		function setupImageHandlers(imageData) {
290 +			const container = imageData.container;
291 +
292 +			// Moving
293 +			container.addEventListener('mousedown', (e) => {
294 +				if (e.target.classList.contains('resize-handle')) return;
295 +
296 +				e.preventDefault();
297 +				dragState = {
298 +					image: imageData,
299 +					startX: e.clientX,
300 +					startY: e.clientY,
301 +					startXCell: imageData.xCell,
302 +					startYCell: imageData.yCell
303 +				};
304 +				container.classList.add('dragging');
305 +			});
306 +
307 +			// Resizing
308 +			container.querySelectorAll('.resize-handle').forEach(handle => {
309 +				handle.addEventListener('mousedown', (e) => {
310 +					e.preventDefault();
311 +					e.stopPropagation();
312 +
313 +					resizeState = {
314 +						image: imageData,
315 +						direction: handle.dataset.direction,
316 +						startX: e.clientX,
317 +						startY: e.clientY,
318 +						startXCell: imageData.xCell,
319 +						startYCell: imageData.yCell,
320 +						startWidthCells: imageData.widthCells,
321 +						startHeightCells: imageData.heightCells
322 +					};
323 +					container.classList.add('resizing');
324 +				});
325 +			});
326 +		}
327 +
328 +		document.addEventListener('mousemove', (e) => {
329 +			if (dragState) {
330 +				const dx = e.clientX - dragState.startX;
331 +				const dy = e.clientY - dragState.startY;
332 +
333 +				const dxCells = Math.round(dx / CELL_SIZE_PX);
334 +				const dyCells = Math.round(dy / CELL_SIZE_PX);
335 +
336 +				const newXCell = Math.max(0, dragState.startXCell + dxCells);
337 +				const newYCell = Math.max(0, dragState.startYCell + dyCells);
338 +
339 +				dragState.image.xCell = newXCell;
340 +				dragState.image.yCell = newYCell;
341 +				updateImagePosition(dragState.image);
342 +			}
343 +
344 +			if (resizeState) {
345 +				const dx = e.clientX - resizeState.startX;
346 +				const dy = e.clientY - resizeState.startY;
347 +
348 +				const dxCells = Math.round(dx / CELL_SIZE_PX);
349 +				const dyCells = Math.round(dy / CELL_SIZE_PX);
350 +
351 +				const dir = resizeState.direction;
352 +				const img = resizeState.image;
353 +
354 +				let newX = resizeState.startXCell;
355 +				let newY = resizeState.startYCell;
356 +				let newW = resizeState.startWidthCells;
357 +				let newH = resizeState.startHeightCells;
358 +
359 +				if (dir.includes('e')) {
360 +					newW = Math.max(1, resizeState.startWidthCells + dxCells);
361 +				}
362 +				if (dir.includes('w')) {
363 +					const delta = Math.min(dxCells, resizeState.startWidthCells - 1);
364 +					newX = resizeState.startXCell + delta;
365 +					newW = resizeState.startWidthCells - delta;
366 +				}
367 +				if (dir.includes('s')) {
368 +					newH = Math.max(1, resizeState.startHeightCells + dyCells);
369 +				}
370 +				if (dir.includes('n')) {
371 +					const delta = Math.min(dyCells, resizeState.startHeightCells - 1);
372 +					newY = resizeState.startYCell + delta;
373 +					newH = resizeState.startHeightCells - delta;
374 +				}
375 +
376 +				img.xCell = Math.max(0, newX);
377 +				img.yCell = Math.max(0, newY);
378 +				img.widthCells = newW;
379 +				img.heightCells = newH;
380 +				updateImagePosition(img);
381 +			}
382 +		});
383 +
384 +		document.addEventListener('mouseup', () => {
385 +			if (dragState) {
386 +				dragState.image.container.classList.remove('dragging');
387 +				dragState = null;
388 +			}
389 +			if (resizeState) {
390 +				resizeState.image.container.classList.remove('resizing');
391 +				resizeState = null;
392 +			}
393 +		});
394 +	</script>
395 +</body>
396 +</html>