diff --git a/src/index.html b/src/index.html index 3151fd2..0f801da 100644 --- a/src/index.html +++ b/src/index.html @@ -4921,6 +4921,7 @@ + @@ -12497,6 +12498,8 @@ if (prev) prev.style.display = 'none'; if (next) next.style.display = 'none'; if (tfBtn) tfBtn.style.display = 'none'; + const coverExportBtn = document.getElementById('bookCoverExportJsonBtn'); + if (coverExportBtn) coverExportBtn.style.display = ''; if (pubBtn) { const visTag = state.visibility === 'private' ? ' 🔒' : ''; const ref = book.cover?.ref; @@ -12507,6 +12510,11 @@ } return; } + // Editing a page / master → hide the cover-only export button. + { + const coverExportBtn = document.getElementById('bookCoverExportJsonBtn'); + if (coverExportBtn) coverExportBtn.style.display = 'none'; + } if (editingMaster) { const m = book.masters.find(x => x.id === book.editingMasterId); if (label) label.textContent = `Master: ${m?.label || 'Untitled'}`; @@ -25918,6 +25926,90 @@ }; } + // Whole-book payload — spec + pages + masters + cover metadata + + // every layer that belongs to any book surface (pages, masters, + // cover panels). Transient UI fields (editingCover / editingEntryId + // / editingMasterId / editingChapterId) are excluded; restore opens + // the book at the Pages overview. + function bookPayload() { + const book = state.book || {}; + return { + version: SCHEMA_VERSION, + templateMode: 'book', + templateType: 'book', + meta: state.meta, + ui: { + zoom: state.zoom, + showGuides: state.showGuides, + showBleed: state.showBleed, + showCrops: state.showCrops, + printGuides: state.printGuides, + printCrops: state.printCrops, + }, + book: { + spec: book.spec || {}, + pages: book.pages || [], + masters: book.masters || [], + cover: book.cover || {}, + }, + layerGroups: { ...ensureLayerGroups() }, + layers: nonGeneratedLayers().filter(l => { + const t = String(l.target || ''); + return t.startsWith('book-page:') + || t.startsWith('book-master:') + || t === 'book-cover-front' + || t === 'book-spine' + || t === 'book-cover-back'; + }), + }; + } + + // Cover-only payload — just the three cover panels + the spec + // fields the cover needs to render correctly (trim, bleed, safe, + // spine width snapshotted at export so the file is portable). Use + // it to move a cover spread between books or stash it as a + // standalone artifact. + function bookCoverPayload() { + const spec = state.book?.spec || {}; + return { + version: SCHEMA_VERSION, + templateMode: 'book-cover', + templateType: 'book-cover', + meta: state.meta, + ui: { + zoom: state.zoom, + showGuides: state.showGuides, + showBleed: state.showBleed, + showCrops: state.showCrops, + }, + bookCover: { + trimW: Number(spec.trimW) || 0, + trimH: Number(spec.trimH) || 0, + bleed: Number(spec.bleed) || 0, + safe: Number(spec.safe) || 0.125, + coverHasBack: !!spec.coverHasBack, + coverHasSpine: !!spec.coverHasSpine, + // Snapshot the derived spine width so the cover is + // self-contained — re-importing into a book with a + // different page count won't shift it unless the user opts + // in by toggling spineFromPages. + spineWidth: bookSpineWidthInches(), + spineFromPages: !!spec.spineFromPages, + spineWManual: Number(spec.spineWManual) || 0, + paperThickness: Number(spec.paperThickness) || 0, + pageCountAtExport: bookPageCount(), + bookSize: spec.bookSize || 'Custom', + }, + layerGroups: { ...ensureLayerGroups() }, + layers: nonGeneratedLayers().filter(l => { + const t = String(l.target || ''); + return t === 'book-cover-front' + || t === 'book-spine' + || t === 'book-cover-back'; + }), + }; + } + function exportPayload() { const templateType = templateTypeForMode(); return { @@ -27966,6 +28058,11 @@ } else if (state.templateMode === 'pixelart') { const pa = state.pixelArt || defaultPixelArt(32, 32); triggerDownload(pixelArtPayload(), projectFilename('pixel_art', `${pa.width}x${pa.height}`)); + } else if (state.templateMode === 'book') { + const spec = state.book?.spec || {}; + const variant = filenamePart(spec.bookSize, 'custom') + || `${Number(spec.trimW) || 0}x${Number(spec.trimH) || 0}`; + triggerDownload(bookPayload(), projectFilename('book', variant)); } else if (isCanvasMode(state.templateMode)) { const ca = state.customArt || { width: 1920, height: 1080 }; const stem = state.templateMode === 'slide' ? 'slide' : 'custom_art'; @@ -37146,6 +37243,16 @@ const id = state.book?.editingEntryId; if (id) openBookPageGatingDialog(id); }); + on('bookCoverExportJsonBtn', 'click', () => { + if (!state.book?.editingCover) return; + const titlePart = filenamePart(state.meta?.title, 'untitled'); + const spec = state.book?.spec || {}; + const sizePart = filenamePart(spec.bookSize, '') + || `${Number(spec.trimW) || 0}x${Number(spec.trimH) || 0}`; + const filename = [titlePart, 'book_cover', sizePart].filter(Boolean).join('_') + '.json'; + triggerDownload(bookCoverPayload(), filename); + toastSuccess?.('Cover spread saved.'); + }); on('bookPageEditProofPdf', 'click', async () => { const book = state.book; const entryId = book?.editingEntryId;