diff --git a/internal/check-packet-a1-structure.mjs b/internal/check-packet-a1-structure.mjs index 9b343cd5a..d5698dd72 100644 --- a/internal/check-packet-a1-structure.mjs +++ b/internal/check-packet-a1-structure.mjs @@ -78,7 +78,7 @@ function sequence(html) { : /word-title/.test(seg) ? 'word' // point 4's passage in full + the Father's-heart list (2026-09-30) : /points-title|points_title|Prayer Points/.test(seg) ? 'points' : `?${cls.trim()}` - if ((kind === 'profiles' || kind === 'worksheet') && out[out.length - 1] === kind) return // one entry per run + if ((kind === 'profiles' || kind === 'worksheet' || kind === 'points') && out[out.length - 1] === kind) return // one entry per run out.push(kind) }) return out.join(' · ') diff --git a/internal/check-packet-render.mjs b/internal/check-packet-render.mjs index d9f0bcd34..ad43cf9cf 100644 --- a/internal/check-packet-render.mjs +++ b/internal/check-packet-render.mjs @@ -279,8 +279,11 @@ const r = await page.evaluate(() => { return Math.round(Math.max(0, ...[...b.children].map((k) => k.getBoundingClientRect().bottom + parseFloat(getComputedStyle(k).marginBottom))) - top - b.clientHeight) })(), // The points page is ONE sheet by design and fits only because its sizes were tuned to the longest locale; // measure its content against its box (studio's ask), as fdTopOver does for the list's first page. - pointsOver: (() => { const b = document.querySelector('.points-body'); if (!b) return null; const top = b.getBoundingClientRect().top - return Math.round(Math.max(b.scrollHeight - b.clientHeight, ...[...b.children].map((k) => k.getBoundingClientRect().bottom - top - b.clientHeight))) })(), + // The points flow over TWO sheets (2026-10-02); each sheet's body is measured, the worst one reported. + pointsOver: (() => { const bs = [...document.querySelectorAll('.points-body')]; if (!bs.length) return null + return Math.max(...bs.map((b) => { const top = b.getBoundingClientRect().top + return Math.round(Math.max(b.scrollHeight - b.clientHeight, ...[...b.children].map((k) => k.getBoundingClientRect().bottom - top - b.clientHeight))) })) })(), + pointsSheets: document.querySelectorAll('.sheet-points').length, funnelTip: document.querySelectorAll('.funnel-tip').length, lifeShaded: (() => { const g = document.querySelector('.sheet-christian .lc-svg'); return g ? window.__lcBoxes(g).shaded.length : null })(), // ★ FILLED FROM THE TOP, in reading order (the packet's design), measured on the DOM order of the cells: @@ -456,7 +459,7 @@ const r = await page.evaluate(() => { if (!host) return -1 // Each point's heading carries class="pt-h": a numbered LINE is not a point. German cites "1. Petrus 5:6–11" // (its own name for 1 Peter), and that reference line was counted as a thirteenth point. - const heads = host.querySelectorAll('.pt-h') + const heads = document.querySelectorAll('.pt-h') if (heads.length) return heads.length return (host.innerText || '').split('\n').filter((l) => /^\s*\d{1,2}\.\s/.test(l)).length })(), @@ -783,7 +786,9 @@ if (r.fdTopOver > 0) fails.push(`the Full detail's explanation block outgrows it if (r.wsRows && r.wsSheets !== wsPages(r.wsRows)) fails.push(`${r.wsRows} worksheet rows should take ${wsPages(r.wsRows)} page(s) but the packet has ${r.wsSheets}`) // + 2 again (2026-09-30, the second revision): point 4's Mark 5:18-20 and the Father's-heart list are back ON the // points page, so the back matter is the wheel and the points. -const expectedSheets = 1 + lifeSheets + funnelSheets + listSheets + setsSheets + 2 +// + 3 (2026-10-02): the wheel, and the points on TWO sheets (themes + points 1–6, then 7–12), read at print size. +if (r.pointsSheets !== 2) fails.push(`the prayer points should flow over 2 sheets, found ${r.pointsSheets}`) +const expectedSheets = 1 + lifeSheets + funnelSheets + listSheets + setsSheets + 3 // ── THE LIFE-CALENDAR PAGES, asserted rather than merely counted ────────────────────────────────── // A page that EXISTS is not a page that is RIGHT: an empty grid, a grid of the wrong size, or a // christian page whose shading silently fell to zero would all still be one sheet. 5,200 is the diff --git a/internal/check-profile-table.mjs b/internal/check-profile-table.mjs index 90f15404c..570f622ad 100644 --- a/internal/check-profile-table.mjs +++ b/internal/check-profile-table.mjs @@ -121,7 +121,9 @@ const probe = (txt) => { const words = (n) => 'word '.repeat(n).trim() ok(probe(words(10)).px === 17, 'a short description did not render at the full 17px') ok(probe(words(Math.floor(budget(17) / 5))).px === 17, 'a description just inside the 17px budget stepped down anyway') -ok(probe(words(Math.floor(budget(17) / 5) + 6)).px === 15, +// Between the 17px and the 15px budgets, wherever they fall for this row width: '+6 words' was 30 characters, and +// when the tally cells widened the gap between the two budgets shrank to 24, so the probe skipped 15 for 14. +ok(probe(words(Math.floor((budget(17) + budget(15)) / 2 / 5))).px === 15, 'a description over the 17px budget did not step to 15px') const long = probe(words(Math.floor(budget(14) / 5) + 40)) ok(long.px === 14, `a very long description did not clamp at the smallest size (got ${long.px}px)`) diff --git a/library/packet/packet-render.js b/library/packet/packet-render.js index f5bd72c07..34b900f26 100644 --- a/library/packet/packet-render.js +++ b/library/packet/packet-render.js @@ -131,13 +131,13 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de // The tally cells no longer carry their question under each box: the three questions head the table ONCE // per page (.pray-head), so a cell is all tally room. 84 -> 116 gives every box more of it and the heads a // readable 15px (three short lines; at 96px they were three tiny ones). - const PRAY_CELL = 116; + const PRAY_CELL = 160; // The question heads above the first row of every page, and the first page's explanation (the intro, // the blessing, the band) in a reserved block, so the first page holds FD_FIRST rows and the rest 20. // FD_TOP_H is MEASURED: with a 100-group list the tallest locales' block uses 612px (es fr pt de it ro ru hi; // en 591, ar 507, zh 513), so 640 keeps 28px; with the 62px head row the first page holds 11 rows. check:packet-render fails a block // whose content outgrows it (the block clips rather than overlap the rows, so only a measurement sees it). - const PRAY_HEAD_H = 62, FD_TOP_H = 640; + const PRAY_HEAD_H = 62, FD_TOP_H = 720; const FD_ROWS_TOP = M + HEAD + FD_TOP_H + PRAY_HEAD_H; const SET_PANEL = 660; // one square sets panel: two + a 40px gap fit 1380px const SET_SOLO = 960; // one chart per page: a large square, centred @@ -374,7 +374,7 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de /* The three questions, ONCE per page, over the tally cells' own columns. */ .pray-head{position:absolute;inset-inline-end:${M}px;display:grid;grid-template-columns:repeat(${PRAY_COLS.length},${PRAY_CELL}px); height:${PRAY_HEAD_H - 6}px;align-items:end;font:700 15px/1.2 Georgia,serif;color:#6f6a5c;text-align:center} - .pray-head span{text-wrap:balance;padding:0 3px 5px;border-bottom:2px solid #3b463d} + .pray-head span{text-wrap:balance;padding:0 0 5px;border-bottom:2px solid #3b463d} .fd-top{position:absolute;left:${M}px;top:${M + HEAD}px;width:${CW.toFixed(0)}px;height:${FD_TOP_H}px;overflow:hidden} .hd-r{position:absolute;right:${M}px;top:54px;font:400 19px Georgia,serif;color:#8c8170} /* ⚠ box-sizing:border-box IS LOAD-BEARING HERE, NOT HOUSEKEEPING. Without it the row's height @@ -1352,11 +1352,13 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de // Mark 5:18-20 and the Father's-heart list share this ONE sheet, in every locale: the sizes below are the // largest that fit the longest locale. class="points-body" is a TEST HOOK: the page must fit ONE sheet in // every locale (its scrollHeight within its clientHeight). - // ★ 2026-10-02: the prayer themes now head the page at a size that reads on paper (21px, ~8.6pt on letter) and - // the credit line sits 0.28in above the sheet's edge, so the points give back a little: 20 → 18px text, 10 → 5px - // between points, the Mark 5 passage 18 → 16px, and the body starts 14px higher (the heading stays 22). Measured - // in all 11 locales: the tightest (fr, hi) keep 46px under the last point; check:packet-render fails overflow. - const PT = { head: 22, sub: 19, text: 18, refs: 16, gap: 5, passage: 16 }; + // ★ READABLE ON PAPER, ON TWO SHEETS (2026-10-02): the point text at 22.5px (~9.2pt on letter), the themes at + // 21px (~8.6pt), the Mark 5 passage at 20px; that no longer fits one sheet, so the points FLOW onto a second: the + // themes and points 1–6 (point 4 carries its passage and the Father's-heart list) on the first, 7–12 on the + // second, which carries the credit line. Measured in all 11 locales: the fullest first sheet (fr, de) keeps + // ~350px; check:packet-render fails either sheet's overflow (pointsOver). + const PT = { head: 25, sub: 21, text: 22.5, refs: 18.5, gap: 18, passage: 20 }; + const POINTS_FIRST = 6; // ⚠ ISOLATE EACH REFERENCE FOR BIDI. Scripture references stay in English // (a reader looks them up in their own Bible), so on an RTL page they are // LTR runs inside an RTL paragraph. Without isolation the Unicode bidi @@ -1405,14 +1407,18 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de