diff --git a/public/js/book-page-format-module.js b/public/js/book-page-format-module.js index 5456954..5b968cd 100644 --- a/public/js/book-page-format-module.js +++ b/public/js/book-page-format-module.js @@ -17,14 +17,14 @@ class BookPageFormatModule extends BaseModule { margins: Object.freeze({ topIn: 0.46, bottomIn: 0.58, - innerIn: 0.68, - outerIn: 0.46 + innerIn: 0.62, + outerIn: 0.72 }), typography: Object.freeze({ fontFamily: '"EB Garamond", "EB Garamond 12", serif', - bodyFontSizePt: 10.8, - lineHeightPt: 14.9, - headingFontSizePt: 13.2, + linesPerPage: 25, + bodyLineRatio: 1.5, + headingScale: 1, dropCapLines: 2 }) }); @@ -63,18 +63,24 @@ class BookPageFormatModule extends BaseModule { inner: this.inchesToTexture(this.format.margins.innerIn, height), outer: this.inchesToTexture(this.format.margins.outerIn, height) }; + const content = { + x: margins.outer, + y: margins.top, + width: Math.max(1, width - margins.outer - margins.inner), + height: Math.max(1, height - margins.top - margins.bottom) + }; + const linesPerPage = Math.max(1, Number(this.format.typography.linesPerPage || 25)); + const typographyLineHeightPx = content.height / linesPerPage; + const bodyFontSizePx = typographyLineHeightPx / Math.max(1, Number(this.format.typography.bodyLineRatio || 1.5)); return { width, height, aspectRatio: this.getAspectRatio(), margins, - content: { - x: margins.outer, - y: margins.top, - width: Math.max(1, width - margins.outer - margins.inner), - height: Math.max(1, height - margins.top - margins.bottom) - }, - typographyLineHeightPx: this.inchesToTexture(this.format.typography.lineHeightPt / 72, height), + content, + linesPerPage, + bodyFontSizePx, + typographyLineHeightPx, typography: this.format.typography }; } diff --git a/public/js/book-pagination-module.js b/public/js/book-pagination-module.js index ab0904f..1a5e304 100644 --- a/public/js/book-pagination-module.js +++ b/public/js/book-pagination-module.js @@ -92,7 +92,8 @@ class BookPaginationModule extends BaseModule { lineIndex: cursorLine, pageLine: geometry.pageLine, fontPx: layout.fontPx, - lineHeightPx: layout.lineHeightPx + lineHeightPx: layout.lineHeightPx, + fontStyle: layout.fontStyle }); cursorLine += 1; }); @@ -108,8 +109,8 @@ class BookPaginationModule extends BaseModule { const typography = this.metrics.typography; const role = block.role || block.metadata?.role || (type === 'heading' ? 'chapter-heading' : 'body'); const isHeading = type === 'heading' || role === 'chapter-heading' || role === 'section-heading'; - const fontPx = Math.max(1, Math.round(this.pageFormat.inchesToTexture(typography.bodyFontSizePt / 72, this.metrics.height))); - const lineHeightPx = Math.max(fontPx + 2, Math.round(this.pageFormat.inchesToTexture(typography.lineHeightPt / 72, this.metrics.height))); + const lineHeightPx = Math.max(1, Number(this.metrics.typographyLineHeightPx || 1)); + const fontPx = Math.max(1, Number(this.metrics.bodyFontSizePx || lineHeightPx / 1.5)); const indent = (isHeading || block.isFirstParagraphInChapter || block.metadata?.isFirstParagraphInChapter || block.addTopSpace) ? 0 : lineHeightPx * 1.5; @@ -131,6 +132,7 @@ class BookPaginationModule extends BaseModule { role, fontPx, lineHeightPx, + fontStyle: isHeading ? 'italic' : 'normal', lines: this.extractLines(layout, { measures, lineOffsets, diff --git a/public/js/book-texture-renderer-module.js b/public/js/book-texture-renderer-module.js index 3de42ae..9bec7d0 100644 --- a/public/js/book-texture-renderer-module.js +++ b/public/js/book-texture-renderer-module.js @@ -120,6 +120,7 @@ class BookTextureRendererModule extends BaseModule { const metrics = this.metrics; const fontPx = Math.max(1, Number(lineRecord.fontPx || 22)); const lineHeightPx = Math.max(fontPx + 2, Number(lineRecord.lineHeightPx || metrics.typographyLineHeightPx || 30)); + const fontStyle = lineRecord.fontStyle === 'italic' ? 'italic ' : ''; const line = lineRecord.line || {}; const nodes = Array.isArray(line.nodes) ? line.nodes : []; const baseY = metrics.content.y + (Number(lineRecord.pageLine || 0) * lineHeightPx) + fontPx; @@ -133,7 +134,7 @@ class BookTextureRendererModule extends BaseModule { : Number(line.offset || 0); let x = metrics.content.x + centerOffset; - ctx.font = `${fontPx}px ${metrics.typography.fontFamily}`; + ctx.font = `${fontStyle}${fontPx}px ${metrics.typography.fontFamily}`; nodes.forEach((node, index) => { if (!node) return; if (node.type === 'box' && node.value) { diff --git a/public/js/loader.js b/public/js/loader.js index 98df9bc..b63cc65 100644 --- a/public/js/loader.js +++ b/public/js/loader.js @@ -24,7 +24,7 @@ const ModuleState = { ERROR: 'ERROR' }; -const MODULE_CACHE_BUSTER = '20260606-webgl-pagination-foundation'; +const MODULE_CACHE_BUSTER = '20260606-webgl-overlay-page-layout'; window.MODULE_CACHE_BUSTER = MODULE_CACHE_BUSTER; /** diff --git a/public/js/procedural-book-model.js b/public/js/procedural-book-model.js index aaa6235..3016066 100644 --- a/public/js/procedural-book-model.js +++ b/public/js/procedural-book-model.js @@ -9,6 +9,7 @@ export const PROCEDURAL_BOOK = { PAGE_WIDTH: 2.24 * 2 / 3, COVER_DEPTH: 2.30, OPEN_SEAM_GAP: 0.003, + PAGE_TEXTURE_FORE_EDGE_INSET_RATIO: 0.075, PROFILE: { tableY: 0, coverThickness: 0.03, @@ -569,7 +570,8 @@ function createLoftedLineBody(model, lines, depth) { const pageDistance = side > 0 ? point.x - model.spineHalf : -model.spineHalf - point.x; - const pageU = THREE.MathUtils.clamp(pageDistance / model.pageWidth, 0, 1); + const textureInset = model.pageWidth * PROCEDURAL_BOOK.PAGE_TEXTURE_FORE_EDGE_INSET_RATIO; + const pageU = THREE.MathUtils.clamp(pageDistance / Math.max(0.001, model.pageWidth - textureInset), 0, 1); return { u: side < 0 ? 1 - pageU : pageU, v: 1 - ((z + depth * 0.5) / depth) diff --git a/public/js/webgl-book-lab.js b/public/js/webgl-book-lab.js index f075494..59c5349 100644 --- a/public/js/webgl-book-lab.js +++ b/public/js/webgl-book-lab.js @@ -4,7 +4,7 @@ import { RenderPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postproces import { SSAOPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postprocessing/SSAOPass.js'; import { SMAAPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postprocessing/SMAAPass.js'; import { OutputPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postprocessing/OutputPass.js'; -import { PROCEDURAL_BOOK, createProceduralBookModel, snapProceduralPageCount } from './procedural-book-model.js?v=20260606-book-page-format-restore'; +import { PROCEDURAL_BOOK, createProceduralBookModel, snapProceduralPageCount } from './procedural-book-model.js?v=20260606-webgl-overlay-page-layout'; const canvas = document.getElementById('scene'); canvas.style.cursor = 'grab'; diff --git a/public/js/webgl-book-scene-module.js b/public/js/webgl-book-scene-module.js index 01b9cef..3c9407e 100644 --- a/public/js/webgl-book-scene-module.js +++ b/public/js/webgl-book-scene-module.js @@ -34,7 +34,7 @@ class WebGLBookSceneModule extends BaseModule { 'installTextureEventBridge', 'applyMode', 'adoptPageContent', - 'moveBookOffscreen', + 'moveBookToControlOverlay', 'restoreBookPlacement', 'refreshModalOverview', 'triggerTextureRefresh', @@ -166,7 +166,7 @@ class WebGLBookSceneModule extends BaseModule { app.appendChild(canvas); } - this.moveBookOffscreen(); + this.moveBookToControlOverlay(); const pageCount = this.persistenceManager?.getPreference?.('webgl', 'bookPageCount', DEFAULT_BOOK_PAGE_COUNT) ?? DEFAULT_BOOK_PAGE_COUNT; const progress = this.persistenceManager?.getPreference?.('webgl', 'bookProgress', DEFAULT_BOOK_PROGRESS) ?? DEFAULT_BOOK_PROGRESS; @@ -177,22 +177,29 @@ class WebGLBookSceneModule extends BaseModule { }; } - moveBookOffscreen() { + moveBookToControlOverlay() { const book = document.getElementById('book'); if (!book) return; if (this.originalBookInlineStyle === null) { this.originalBookInlineStyle = book.getAttribute('style') || ''; } book.style.position = 'fixed'; - book.style.left = 'calc(100vw + 50%)'; - book.style.top = '50%'; - book.style.width = 'var(--book-width)'; - book.style.height = 'var(--book-height)'; - book.style.transform = 'translate(-50%, -50%) scale(var(--book-scale))'; - book.style.transformOrigin = 'center center'; + book.style.left = '1rem'; + book.style.top = '1rem'; + book.style.width = 'min(31rem, calc(100vw - 2rem))'; + book.style.height = 'min(27rem, calc(100vh - 2rem))'; + book.style.background = 'rgba(18, 11, 8, 0.62)'; + book.style.border = '1px solid rgba(240, 205, 142, 0.28)'; + book.style.boxShadow = '0 1.2rem 3rem rgba(0, 0, 0, 0.42)'; + book.style.backdropFilter = 'blur(5px)'; + book.style.transform = 'none'; + book.style.transformOrigin = 'top left'; book.style.opacity = '1'; book.style.visibility = 'visible'; + book.style.zIndex = '40'; + book.style.pointerEvents = 'none'; this.removePagePerspectiveTransforms(); + this.positionOverlayPages(); } restoreBookPlacement() { @@ -219,6 +226,38 @@ class WebGLBookSceneModule extends BaseModule { }); } + positionOverlayPages() { + const pageLeft = document.getElementById('page_left'); + if (pageLeft) { + Object.assign(pageLeft.style, { + position: 'absolute', + inset: '0', + width: 'auto', + height: 'auto', + padding: '1rem', + overflow: 'auto', + opacity: '1', + mixBlendMode: 'normal', + clipPath: 'none', + pointerEvents: 'auto' + }); + } + + const pageRight = document.getElementById('page_right'); + if (pageRight) { + Object.assign(pageRight.style, { + position: 'fixed', + left: 'calc(100vw + 2rem)', + top: '0', + width: 'var(--book-right-page-width)', + height: 'var(--book-page-height)', + opacity: '1', + visibility: 'visible', + pointerEvents: 'none' + }); + } + } + restorePagePerspectiveTransforms() { this.originalPageInlineStyles.forEach((style, id) => { const page = document.getElementById(id);