fix(book): cover preview paints actual layers + book-aware project-title readouts

Two fixes wrapped together because they're both about the user
not seeing a meaningful preview in book mode.

1. renderBookCoverPreview() now paints the actual back / spine /
   front layers into a single composite canvas the size of the full
   wrap-around spread, using the same renderCustomArtPreviewDOM
   helper the Pages-overview tiles use. Each layer's target is
   remapped to 'canvas' and offset by its panel's position within the
   spread (back: x=bleed, spine: x=bleed+trimW, front: x=bleed+
   trimW+spineW). On top: a dashed-red trim guide, dashed fold-line
   dividers, subtle 'Back / Spine / Front' panel labels, and an
   empty-state hint when no cover artwork exists yet. The geometric
   panel-mock fallback is gone — the .bookCoverPanel CSS still ships
   but is now dead.

2. renderDimensions() no longer falls through to the DVD-cover
   else-branch in book mode. The 'Trim: 10.8 × 7.25 in / PDF page:
   8.50 × 11.00 in (portrait)' strings that showed for books were
   the DVD totalTrimW + state.trimH leaking through. A dedicated
   book branch now reads from state.book.spec and shows
   'Trim: 6 × 9 in / Spread: ... (with bleed)' when editing the
   cover, 'Page: ... (with bleed)' otherwise.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.7 committed 2026-06-04 15:00:26 -04:00
1 parent faa4849523
commit c66b1c03af
1 file changed
+99 -22
+99 -22
View File
@@ -12638,33 +12638,96 @@
</div>
`).join('');
// Spread preview. Scale to fit in ~720 px wide; min 200 px.
// Spread preview. Paint the actual cover layers into a single
// composite canvas the size of the full wrap-around spread,
// overlaid with subtle panel labels + fold-line dividers + the
// red trim guide. Layers are stored in panel-local space
// (book-cover-back / book-spine / book-cover-front, each at
// x=0 = left edge of that panel's trim area), so we remap each
// layer's target to 'canvas' and add the panel's offset within
// the spread + the outer bleed.
const previewMaxPx = 720;
const pxPerIn = totalW > 0 ? Math.min(previewMaxPx / totalW, 60) : 60;
const heightPx = Math.max(40, totalH * pxPerIn);
const renderWidth = Math.max(40, Math.round(totalW * pxPerIn));
wrap.innerHTML = '';
const addPanel = (cls, label, widthIn, dim) => {
const panel = document.createElement('div');
panel.className = `bookCoverPanel ${cls}`;
panel.style.width = `${widthIn * pxPerIn}px`;
panel.style.height = `${heightPx}px`;
const lab = document.createElement('div');
lab.className = 'bookCoverPanelLabel';
lab.textContent = label;
panel.appendChild(lab);
if (dim) {
const dimEl = document.createElement('div');
dimEl.className = 'bookCoverPanelDim';
dimEl.textContent = dim;
panel.appendChild(dimEl);
}
wrap.appendChild(panel);
};
const backX = bleed;
const spineX = bleed + (hasBack ? trimW : 0);
const frontX = bleed + (hasBack ? trimW : 0) + (hasSpine ? spineW : 0);
const panelY = bleed;
if (hasBack) addPanel('back', 'Back', trimW + bleed, `${fmt(trimW)} in`);
if (hasSpine && spineW > 0) addPanel('spine', 'Spine', spineW, `${fmt(spineW)} in`);
addPanel('front', 'Front', trimW + bleed, `${fmt(trimW)} in`);
const compositeLayers = [];
for (const l of (state.layers || [])) {
if (l.hidden) continue;
const t = String(l.target || '');
let xo, yo;
if (t === 'book-cover-back' && hasBack) { xo = backX; yo = panelY; }
else if (t === 'book-spine' && hasSpine) { xo = spineX; yo = panelY; }
else if (t === 'book-cover-front') { xo = frontX; yo = panelY; }
else continue;
compositeLayers.push({
...l,
target: 'canvas',
x: (Number(l.x) || 0) + xo,
y: (Number(l.y) || 0) + yo,
});
}
compositeLayers.sort((a, b) => (a.z || 0) - (b.z || 0));
const container = document.createElement('div');
container.style.cssText = 'position:relative;display:inline-block;border:1px solid var(--line);box-shadow:0 1px 3px rgba(0,0,0,.08);background:#fff';
try {
renderCustomArtPreviewDOM(container, {
customArt: {
width: Math.round(totalW * 96),
height: Math.round(totalH * 96),
background: book.cover?.background || s.background || '#ffffff',
},
layers: compositeLayers,
}, { compact: true, renderWidth });
} catch (err) {
console.warn('Cover preview paint failed:', err);
}
const overlay = document.createElement('div');
overlay.style.cssText = 'position:absolute;inset:0;pointer-events:none';
// Trim guide — dashed red rectangle around the printable trim.
const trimGuide = document.createElement('div');
trimGuide.style.cssText = `position:absolute;left:${(bleed / totalW) * 100}%;top:${(bleed / totalH) * 100}%;width:${((totalW - 2 * bleed) / totalW) * 100}%;height:${((totalH - 2 * bleed) / totalH) * 100}%;border:1px dashed rgba(220,38,38,.55);box-sizing:border-box`;
overlay.appendChild(trimGuide);
// Vertical fold lines at each visible panel boundary.
const addFold = (xIn) => {
const fold = document.createElement('div');
fold.style.cssText = `position:absolute;top:${(bleed / totalH) * 100}%;height:${((totalH - 2 * bleed) / totalH) * 100}%;width:0;border-left:1px dashed rgba(15,23,42,.35);left:${(xIn / totalW) * 100}%`;
overlay.appendChild(fold);
};
if (hasBack) addFold(spineX); // back / spine boundary
if (hasSpine) addFold(spineX + spineW); // spine / front boundary
// Panel labels — bottom of each panel.
const labelTopPct = ((bleed + trimH) / totalH) * 100 - 4;
const addLabel = (text, centerXIn) => {
const tag = document.createElement('div');
tag.textContent = text;
tag.style.cssText = `position:absolute;left:${(centerXIn / totalW) * 100}%;top:${labelTopPct}%;transform:translate(-50%, -50%);background:rgba(255,255,255,.85);color:var(--muted);font-size:.58rem;letter-spacing:.08em;text-transform:uppercase;padding:2px 6px;border-radius:3px;font-family:inherit`;
overlay.appendChild(tag);
};
if (hasBack) addLabel('Back', backX + trimW / 2);
if (hasSpine && spineW > 0) addLabel('Spine', spineX + spineW / 2);
addLabel('Front', frontX + trimW / 2);
// Empty-state hint, centered, when no cover layers exist yet.
if (!compositeLayers.length) {
const empty = document.createElement('div');
empty.style.cssText = 'position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--muted);font-size:.85rem;text-align:center;padding:0 16px';
empty.textContent = 'No cover artwork yet — click Edit cover to design the spread.';
overlay.appendChild(empty);
}
container.appendChild(overlay);
wrap.appendChild(container);
// Ref status hint — published / unpublished / publication target.
const refHint = document.getElementById('bookCoverRefHint');
@@ -14288,6 +14351,20 @@
document.querySelector('.printHelp').textContent = 'Paint a pixel-art sprite — publishes as its own design.';
$('trimReadout').textContent = `${pa.width} × ${pa.height} px`;
$('sheetReadout').textContent = 'Indexed pixel grid';
} else if (state.templateMode === 'book') {
const spec = state.book?.spec || {};
const tW = Number(spec.trimW) || 0;
const tH = Number(spec.trimH) || 0;
const pages = bookPageCount();
const editing = !!state.book?.editingCover;
$('projectTitle').textContent = editing ? 'Book cover spread' : 'Book';
document.querySelector('.printHelp').textContent = editing
? `Back · spine · front laid out for ${spec.bookSize || `${fmt(tW)} × ${fmt(tH)} in`}.`
: `${spec.bookSize || `${fmt(tW)} × ${fmt(tH)} in`} · ${pages} page${pages === 1 ? '' : 's'}.`;
$('trimReadout').textContent = `Trim: ${fmt(tW)} × ${fmt(tH)} in`;
$('sheetReadout').textContent = editing
? `Spread: ${fmt(sheetW())} × ${fmt(sheetH())} in (with bleed)`
: `Page: ${fmt(sheetW())} × ${fmt(sheetH())} in (with bleed)`;
} else if (state.templateMode === 'jewel') {
$('projectTitle').textContent = 'CD jewel insert preview';
document.querySelector('.printHelp').textContent = 'Avery 8693 / 8943 layout: front and tray inserts on Letter paper.';