From 4d4fa4f6d07b2ed206fa551fcee97a006693daed Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Fri, 2 Oct 2026 22:50:16 +0000 Subject: [PATCH] Packet print at reading size: the prayer points flow over two sheets at ~9pt, and Full detail's tally heads take two lines over the worksheet's 160px columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Points: text 22.5px (~9.2pt on letter), headings 25px, the Mark 5 passage 20px, the themes 21px; the themes and points 1–6 on the first sheet, 7–12 and the credit on the second, each titled with its range. Measured in all 11 languages: the fullest first sheet keeps ~330px. Full detail: tally columns 160px (the worksheet's), heads 15px in two lines in every language, the first-page block 720px (the tallest, ru, uses 692), 11 rows on the first page. Gates: the render gate measures BOTH points sheets (pointsOver), counts the points by .pt-h across the document, expects 2 points sheets; the structure gate reads consecutive points sheets as one entry; profile-table probes the 15px tier between the two budgets wherever they fall. --- internal/check-packet-a1-structure.mjs | 2 +- internal/check-packet-render.mjs | 13 +++++++--- internal/check-profile-table.mjs | 4 ++- library/packet/packet-render.js | 36 +++++++++++++++----------- 4 files changed, 34 insertions(+), 21 deletions(-) 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
${refs}
${p.print_passage ? passageBlock() + heartBlock() : ''} `; - }).join(''); - return sheet(` + }); + // Each sheet names its range (1–5, 6–12) beside the title, in the language's digits. + const head = (from, to, intro) => `
-
${esc(t('points_title'))}
-
${esc(t('points_intro'))}
-
-
${themesBlock()}${rows}
-
${esc(t('points_attrib'))}
`); +
${esc(t('points_title'))} + ${num(from)}–${num(to)}
+ ${intro ? `
${esc(t('points_intro'))}
` : ''} + `; + const body = (html, top) => `
${html}
`; + return sheet(`${head(1, POINTS_FIRST, true)}${body(themesBlock() + rows.slice(0, POINTS_FIRST).join(''), M + 96)}`, 'sheet-points') + + '\n' + sheet(`${head(POINTS_FIRST + 1, POINTS.length, false)}${body(rows.slice(POINTS_FIRST).join(''), M + 70)} +
${esc(t('points_attrib'))}
`, 'sheet-points'); } // ★ THE HEAVENLY FATHER'S HEART (2026-09-30): a plain statement of what they need to know (point4_heart_lede,