commit 819736d277878fb5f67a662bab383cccff300b94
Author: Hunter
Date: Wed, 15 Oct 2025 16:13:01 -0400
initial commit
Diffstat:
| A | index.html | | | 396 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 396 insertions(+), 0 deletions(-)
diff --git a/index.html b/index.html
@@ -0,0 +1,396 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>memori</title>
+ <style>
+ * {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ }
+
+ body {
+ background: #f5f5f0;
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ min-height: 100vh;
+ padding: 20px;
+ }
+
+ .page {
+ width: 8.5in;
+ height: 11in;
+ background: #fafaf8;
+ position: relative;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+ transform-origin: top center;
+ }
+
+ .grid {
+ position: absolute;
+ top: 0.25in;
+ left: 0.25in;
+ 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 solid #d0d0d0;
+ border-bottom: 1px solid #d0d0d0;
+ margin: 0;
+ padding: 0;
+ display: block;
+ }
+
+ .grid-cell:nth-child(-n+50) {
+ border-top: 1px solid #d0d0d0;
+ }
+
+ .grid-cell:nth-child(50n+1) {
+ border-left: 1px solid #d0d0d0;
+ }
+
+ .image-container {
+ position: absolute;
+ cursor: move;
+ outline: 2px solid transparent;
+ outline-offset: -2px;
+ transition: outline-color 0.2s;
+ }
+
+ .image-container:hover {
+ outline-color: #4a90e2;
+ }
+
+ .image-container.dragging {
+ opacity: 0.7;
+ outline-color: #4a90e2;
+ }
+
+ .image-container.resizing {
+ outline-color: #4a90e2;
+ }
+
+ .image-container img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+ pointer-events: none;
+ }
+
+ .resize-handle {
+ position: absolute;
+ background: #4a90e2;
+ opacity: 0;
+ transition: opacity 0.2s;
+ }
+
+ .image-container:hover .resize-handle {
+ opacity: 0.8;
+ }
+
+ .resize-handle.corner {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ }
+
+ .resize-handle.edge {
+ background: transparent;
+ }
+
+ .resize-handle.n { top: -5px; left: 50%; transform: translateX(-50%); width: 30px; height: 10px; cursor: n-resize; }
+ .resize-handle.s { bottom: -5px; left: 50%; transform: translateX(-50%); width: 30px; height: 10px; cursor: s-resize; }
+ .resize-handle.e { right: -5px; top: 50%; transform: translateY(-50%); width: 10px; height: 30px; cursor: e-resize; }
+ .resize-handle.w { left: -5px; top: 50%; transform: translateY(-50%); width: 10px; height: 30px; cursor: w-resize; }
+ .resize-handle.ne { top: -5px; right: -5px; cursor: ne-resize; }
+ .resize-handle.nw { top: -5px; left: -5px; cursor: nw-resize; }
+ .resize-handle.se { bottom: -5px; right: -5px; cursor: se-resize; }
+ .resize-handle.sw { bottom: -5px; left: -5px; cursor: sw-resize; }
+
+ @media print {
+ * {
+ -webkit-print-color-adjust: exact !important;
+ print-color-adjust: exact !important;
+ }
+
+ body {
+ background: white;
+ padding: 0;
+ margin: 0;
+ }
+
+ .page {
+ width: 8.5in;
+ height: 11in;
+ box-shadow: none;
+ margin: 0;
+ padding: 0;
+ background: white;
+ transform: none !important;
+ }
+
+ .grid {
+ position: absolute;
+ top: 0.25in;
+ left: 0.25in;
+ }
+
+ .grid-cell {
+ border-right: 1px solid #d0d0d0 !important;
+ border-bottom: 1px solid #d0d0d0 !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 solid #d0d0d0 !important;
+ }
+
+ .grid-cell:nth-child(50n+1) {
+ border-left: 1px solid #d0d0d0 !important;
+ }
+
+ .image-container {
+ outline: none !important;
+ position: absolute !important;
+ }
+
+ .image-container img {
+ width: 100% !important;
+ height: 100% !important;
+ object-fit: cover !important;
+ }
+
+ .resize-handle {
+ display: none !important;
+ }
+ }
+
+ @page {
+ size: 8.5in 11in;
+ margin: 0;
+ }
+ </style>
+</head>
+<body>
+ <div class="page">
+ <div class="grid" id="grid"></div>
+ </div>
+
+ <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
+ page.addEventListener('dragover', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ });
+
+ page.addEventListener('drop', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const files = Array.from(e.dataTransfer.files).filter(f => f.type.startsWith('image/'));
+
+ files.forEach((file, idx) => {
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ const img = new Image();
+ img.onload = () => {
+ const aspectRatio = img.width / img.height;
+
+ // Calculate best fit in grid cells
+ let widthCells = Math.round(Math.sqrt(16 * aspectRatio));
+ let heightCells = Math.round(widthCells / aspectRatio);
+
+ // Ensure minimum size
+ widthCells = Math.max(2, widthCells);
+ heightCells = Math.max(2, heightCells);
+
+ // Calculate position in grid cells
+ const xCell = 2 + (idx * 2) % 20;
+ const yCell = 2 + Math.floor((idx * 2) / 20) * 2;
+
+ addImage(event.target.result, xCell, yCell, widthCells, heightCells);
+ };
+ img.src = event.target.result;
+ };
+ reader.readAsDataURL(file);
+ });
+ });
+
+ function addImage(src, xCell, yCell, widthCells, heightCells) {
+ const container = document.createElement('div');
+ container.className = 'image-container';
+
+ const img = document.createElement('img');
+ img.src = src;
+ container.appendChild(img);
+
+ // 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 };
+ images.push(imageData);
+
+ updateImagePosition(imageData);
+ grid.appendChild(container);
+
+ setupImageHandlers(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';
+ }
+
+ function setupImageHandlers(imageData) {
+ const container = imageData.container;
+
+ // Moving
+ container.addEventListener('mousedown', (e) => {
+ if (e.target.classList.contains('resize-handle')) return;
+
+ e.preventDefault();
+ 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();
+
+ 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');
+ });
+ });
+ }
+
+ 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, dragState.startXCell + dxCells);
+ const newYCell = Math.max(0, 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 = resizeState.startXCell;
+ let newY = resizeState.startYCell;
+ let newW = resizeState.startWidthCells;
+ let newH = resizeState.startHeightCells;
+
+ if (dir.includes('e')) {
+ newW = Math.max(1, resizeState.startWidthCells + dxCells);
+ }
+ if (dir.includes('w')) {
+ const delta = Math.min(dxCells, resizeState.startWidthCells - 1);
+ newX = resizeState.startXCell + delta;
+ newW = resizeState.startWidthCells - delta;
+ }
+ if (dir.includes('s')) {
+ newH = Math.max(1, resizeState.startHeightCells + dyCells);
+ }
+ if (dir.includes('n')) {
+ const delta = Math.min(dyCells, resizeState.startHeightCells - 1);
+ newY = resizeState.startYCell + delta;
+ newH = resizeState.startHeightCells - delta;
+ }
+
+ img.xCell = Math.max(0, newX);
+ img.yCell = Math.max(0, newY);
+ img.widthCells = newW;
+ img.heightCells = newH;
+ updateImagePosition(img);
+ }
+ });
+
+ document.addEventListener('mouseup', () => {
+ if (dragState) {
+ dragState.image.container.classList.remove('dragging');
+ dragState = null;
+ }
+ if (resizeState) {
+ resizeState.image.container.classList.remove('resizing');
+ resizeState = null;
+ }
+ });
+ </script>
+</body>
+</html>