align zine to grid

Hunter
Monday, February 2nd, 2026 at 11:40 PM

resources/editor.css+13-1
resources/editor.js+20 
resources/spread-layout.js+23 

resources/editor.css
40  	height: 100%;
41  	display: flex;
42  	flex-direction: column;
   -	background: linear-gradient(to top, #252525, #3a3a3a);
43 +	--grid-width: 33px;
44 +	--grid-height: 33px;
45 +	--spread-left: 0px;
46 +	--spread-top: 0px;
47 +	background:
48 +		linear-gradient(to right, #4a7a6a 1px, transparent 1px),
49 +		linear-gradient(to bottom, #4a7a6a 1px, transparent 1px),
50 +		linear-gradient(to bottom, #1a4d3a, #184534);
51 +	background-size: var(--grid-width) var(--grid-height), var(--grid-width) var(--grid-height), 100% 100%;
52 +	background-position:
53 +		calc(var(--spread-left) + var(--grid-width) / 2) var(--spread-top),
54 +		calc(var(--spread-left) + var(--grid-width) / 2) var(--spread-top),
55 +		0 0;
56  }
57  
58  .preview-pane.fullscreen {
resources/editor.js
31  		window.saveFile();
32  	} else if (event.data === 'saveFileWithViewer') {
33  		window.saveFileWithViewer();
34 +	} else if (event.data?.type === 'scaleChange') {
35 +		// Update background grid size and position to match zine preview
36 +		const previewPane = document.querySelector('.preview-pane');
37 +		const preview = document.querySelector('#preview');
38 +		if (previewPane && preview) {
39 +			// Get iframe position relative to preview pane
40 +			const iframeRect = preview.getBoundingClientRect();
41 +			const paneRect = previewPane.getBoundingClientRect();
42 +			const iframeOffsetX = iframeRect.left - paneRect.left;
43 +			const iframeOffsetY = iframeRect.top - paneRect.top;
44 +
45 +			// Spread position in pane coordinates
46 +			const spreadLeft = iframeOffsetX + event.data.spreadLeft;
47 +			const spreadTop = iframeOffsetY + event.data.spreadTop;
48 +
49 +			previewPane.style.setProperty('--grid-width', `${event.data.gridWidth}px`);
50 +			previewPane.style.setProperty('--grid-height', `${event.data.gridHeight}px`);
51 +			previewPane.style.setProperty('--spread-left', `${spreadLeft}px`);
52 +			previewPane.style.setProperty('--spread-top', `${spreadTop}px`);
53 +		}
54  	}
55  });
56  
resources/spread-layout.js
 79  	const translateX = translateMatch ? translateMatch[0] : '';
 80  
 81  	bookContainer.style.transform = translateX ? `scale(${scale}) ${translateX}` : `scale(${scale})`;
 82 +
 83 +	// Notify parent of spread position and grid size for background grid
 84 +	if (doc.defaultView?.parent && doc.defaultView.parent !== doc.defaultView) {
 85 +		const rect = bookContainer.getBoundingClientRect();
 86 +
 87 +		// Derive grid from height (always 1 page tall = 11 cells)
 88 +		// Cell aspect ratio: (2.75/7) / (4.25/11) = 30.25/29.75
 89 +		const gridHeight = rect.height / 11;
 90 +		const gridWidth = gridHeight * (30.25 / 29.75);
 91 +
 92 +		// Remove translateX shift so grid always anchors to the un-shifted book position
 93 +		// The translateX is in inches and gets multiplied by scale
 94 +		const translateInchMatch = translateX.match(/translateX\(([^)]+)in\)/);
 95 +		const translatePx = translateInchMatch ? parseFloat(translateInchMatch[1]) * 96 * scale : 0;
 96 +
 97 +		doc.defaultView.parent.postMessage({
 98 +			type: 'scaleChange',
 99 +			gridWidth,
100 +			gridHeight,
101 +			spreadLeft: rect.left - translatePx,
102 +			spreadTop: rect.top
103 +		}, '*');
104 +	}
105  }