diff --git a/src/index.html b/src/index.html
index 2bb22f6..e32cb26 100644
--- a/src/index.html
+++ b/src/index.html
@@ -4416,6 +4416,7 @@
+
@@ -8409,6 +8410,41 @@
syncInputs();
render();
}
+ // Add a text-frame layer (Phase C4 structural placeholder) to
+ // the master currently open in the editor. Sized to fill the
+ // trim with a small inset so it reads as a "type area" out of
+ // the box; the user can resize / move like any other shape.
+ function addBookTextFrameToActiveMaster() {
+ const book = state.book;
+ if (!book || !book.editingMasterId) return;
+ const target = `book-master:${book.editingMasterId}`;
+ const s = book.spec;
+ const trimW = Number(s.trimW) || 6;
+ const trimH = Number(s.trimH) || 9;
+ const bleed = Number(s.bleed) || 0;
+ // Type area = trim minus a 0.75 in margin all around (a
+ // reasonable book-page default; the user can drag it tighter
+ // or wider). Coordinates are inches in the canvas space; the
+ // canvas covers (trim + 2 × bleed) in each axis.
+ const margin = 0.75;
+ const x = bleed + margin;
+ const y = bleed + margin;
+ const w = Math.max(0.5, trimW - margin * 2);
+ const h = Math.max(0.5, trimH - margin * 2);
+ pushHistory('book: add text frame');
+ addLayer({
+ id: makeLayerId(),
+ type: 'shape',
+ name: 'Text frame',
+ target,
+ x, y, w, h,
+ rotate: 0, opacity: 1, z: nextLayerZ(),
+ shape: defaultShapeData('text-frame'),
+ fill: { type: 'none' },
+ stroke: { type: 'none', color: '#60a5fa', width: 0, dash: 'solid' },
+ });
+ }
+
function setBookDefaultMaster(id) {
const book = state.book;
if (!book) return;
@@ -8861,17 +8897,20 @@
const editingMaster = inBook && !!book?.editingMasterId;
bar.style.display = (editingPage || editingMaster) ? '' : 'none';
if (!editingPage && !editingMaster) return;
+ const tfBtn = document.getElementById('bookMasterAddTextFrame');
if (editingMaster) {
const m = book.masters.find(x => x.id === book.editingMasterId);
if (label) label.textContent = `Master: ${m?.label || 'Untitled'}`;
if (back) back.textContent = '← Back to masters';
if (prev) prev.style.display = 'none';
if (next) next.style.display = 'none';
+ if (tfBtn) tfBtn.style.display = '';
return;
}
// editingPage path
if (prev) prev.style.display = '';
if (next) next.style.display = '';
+ if (tfBtn) tfBtn.style.display = 'none';
if (back) back.textContent = '← Back to Pages';
const designed = book.pages.filter(p => p.type === 'design');
const idx = designed.findIndex(p => p.id === book.editingEntryId);
@@ -17082,10 +17121,19 @@
if (kind === 'star') { out.points = 5; out.innerRadiusRatio = 0.45; }
if (kind === 'line') { out.x1 = 0; out.y1 = 50; out.x2 = 100; out.y2 = 50; out.strokeWidth = 6; }
if (kind === 'path') {
- // Default heart shape so a fresh path layer is immediately useful.
out.d = 'M 50 90 C 0 60 0 20 30 10 C 40 5 50 20 50 30 C 50 20 60 5 70 10 C 100 20 100 60 50 90 Z';
out.viewBox = { x: 0, y: 0, w: 100, h: 100 };
}
+ if (kind === 'text-frame') {
+ // Phase C4 — structural layer only. The pagination engine
+ // in C.5 pours markdown into this frame; until then it
+ // renders as a labelled dashed rectangle on the master so
+ // users can position + size it like any other shape.
+ out.padding = 0.05;
+ out.verticalAlign = 'top';
+ out.columns = 1;
+ out.styleOverrides = null;
+ }
return out;
}
@@ -17531,6 +17579,15 @@
let body = '';
if (kind === 'rect') {
body = ``;
+ } else if (kind === 'text-frame') {
+ // Phase C4 structural rectangle. Dashed accent outline +
+ // centered "Text frame" label; transparent fill. The
+ // pagination engine in C.5 will replace this render path
+ // for frames that have markdown poured into them — at that
+ // point the dashed outline only renders in the editor.
+ const dash = Math.max(0.8, Math.min(4, 100 / 40));
+ body = ``
+ + `Text frame`;
} else if (kind === 'rounded-rect') {
const r = Math.max(0, Math.min(50, Number(shape.cornerRadius) || 0));
body = ``;
@@ -32011,6 +32068,7 @@
});
on('bookPageEditPrev', 'click', () => gotoBookPageRelative(-1));
on('bookPageEditNext', 'click', () => gotoBookPageRelative(1));
+ on('bookMasterAddTextFrame', 'click', () => addBookTextFrameToActiveMaster());
// Book Designer — Masters modal.
on('bookManageMastersBtn', 'click', openBookMastersModal);