UPG100 packet: points on one sheet, page-1 columns reordered
- The twelve prayer points and the themes fit one sheet again, as large as the page allows (points 18.5px, themes 19px); measured in all 11 languages, the tightest keeps 75px under the last point. - Page 1 header columns read: Jesus' Strategy to Feed the 5000 | the facts | What is a UPG100 List? The facts' labels are bold. - The header columns are measured for a fallback font too, so the right column no longer runs to the border when Lato is absent. - check:packet-render expects one points sheet.
This commit is contained in:
parent
789c3b8744
commit
f22dc7db00
3 changed files with 32 additions and 33 deletions
|
|
@ -279,7 +279,7 @@ 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) })(),
|
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;
|
// 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.
|
// measure its content against its box (studio's ask), as fdTopOver does for the list's first page.
|
||||||
// The points flow over TWO sheets (2026-10-02); each sheet's body is measured, the worst one reported.
|
// Each points sheet's body is measured, the worst one reported (one sheet today).
|
||||||
pointsOver: (() => { const bs = [...document.querySelectorAll('.points-body')]; if (!bs.length) return null
|
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.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))) })) })(),
|
return Math.round(Math.max(b.scrollHeight - b.clientHeight, ...[...b.children].map((k) => k.getBoundingClientRect().bottom - top - b.clientHeight))) })) })(),
|
||||||
|
|
@ -786,9 +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}`)
|
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
|
// + 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.
|
// points page, so the back matter is the wheel and the points.
|
||||||
// + 3 (2026-10-02): the wheel, and the points on TWO sheets (themes + points 1–6, then 7–12), read at print size.
|
// + 2: the wheel and the points, the twelve points and the themes on ONE sheet (2026-10-02, at the demo).
|
||||||
if (r.pointsSheets !== 2) fails.push(`the prayer points should flow over 2 sheets, found ${r.pointsSheets}`)
|
if (r.pointsSheets !== 1) fails.push(`the twelve prayer points and the themes should fit ONE sheet, found ${r.pointsSheets}`)
|
||||||
const expectedSheets = 1 + lifeSheets + funnelSheets + listSheets + setsSheets + 3
|
const expectedSheets = 1 + lifeSheets + funnelSheets + listSheets + setsSheets + 2
|
||||||
// ── THE LIFE-CALENDAR PAGES, asserted rather than merely counted ──────────────────────────────────
|
// ── 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
|
// 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
|
// christian page whose shading silently fell to zero would all still be one sheet. 5,200 is the
|
||||||
|
|
|
||||||
|
|
@ -287,12 +287,12 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de
|
||||||
.xb-ex-note{margin-top:4px;font-size:14px;color:#6f6a5c}
|
.xb-ex-note{margin-top:4px;font-size:14px;color:#6f6a5c}
|
||||||
/* The prayer themes and scenes above the twelve points: a thin numbered list, ruled off below. */
|
/* The prayer themes and scenes above the twelve points: a thin numbered list, ruled off below. */
|
||||||
.pt-themes{margin:0 0 10px;padding:0 0 6px;border-bottom:1.5px solid #c9bfa4}
|
.pt-themes{margin:0 0 10px;padding:0 0 6px;border-bottom:1.5px solid #c9bfa4}
|
||||||
.pt-th-h{font:400 21px/1.3 Georgia,serif;color:#413d35}
|
.pt-th-h{font:400 19px/1.3 Georgia,serif;color:#413d35}
|
||||||
.pt-th-h b,.pt-sc-h b{font:700 22px Georgia,serif;color:#1b1a17;letter-spacing:.02em}
|
.pt-th-h b,.pt-sc-h b{font:700 20px Georgia,serif;color:#1b1a17;letter-spacing:.02em}
|
||||||
.pt-th-i{font-style:italic;color:#6d766e}
|
.pt-th-i{font-style:italic;color:#6d766e}
|
||||||
.pt-th-list{display:flex;flex-wrap:wrap;gap:1px 20px;margin:2px 0 0;padding:0;list-style:none;font:400 21px/1.3 Georgia,serif;color:#3b3226}
|
.pt-th-list{display:flex;flex-wrap:wrap;gap:1px 20px;margin:2px 0 0;padding:0;list-style:none;font:400 19px/1.3 Georgia,serif;color:#3b3226}
|
||||||
.pt-n{display:inline-block;min-width:1.3em;font-weight:700;color:#7a3b2e}
|
.pt-n{display:inline-block;min-width:1.3em;font-weight:700;color:#7a3b2e}
|
||||||
.pt-sc-r{font:600 19px Georgia,serif;color:#7a3b2e}
|
.pt-sc-r{font:600 18px Georgia,serif;color:#7a3b2e}
|
||||||
.ws-cols{margin:0;padding:0;list-style:none}
|
.ws-cols{margin:0;padding:0;list-style:none}
|
||||||
.ws-cols li{margin:0 0 6px;font:400 19px/1.45 Georgia,serif;color:#413d35}
|
.ws-cols li{margin:0 0 6px;font:400 19px/1.45 Georgia,serif;color:#413d35}
|
||||||
.ws-cols li b{color:#1b1a17}
|
.ws-cols li b{color:#1b1a17}
|
||||||
|
|
@ -1352,13 +1352,11 @@ 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
|
// 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
|
// 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).
|
// every locale (its scrollHeight within its clientHeight).
|
||||||
// ★ READABLE ON PAPER, ON TWO SHEETS (2026-10-02): the point text at 22.5px (~9.2pt on letter), the themes at
|
// ★ ONE SHEET AGAIN (2026-10-02): the twelve points and the themes fit ONE page, as large as one page allows:
|
||||||
// 21px (~8.6pt), the Mark 5 passage at 20px; that no longer fits one sheet, so the points FLOW onto a second: the
|
// the point text 18.5px (~7.5pt on letter), the themes 19px, 5px between points, the Mark 5 passage 16px; the
|
||||||
// themes and points 1–6 (point 4 carries its passage and the Father's-heart list) on the first, 7–12 on the
|
// credit line stays 0.28in above the edge. Measured in all 11 locales: the tightest (fr 75px, hi 79px) keep
|
||||||
// second, which carries the credit line. Measured in all 11 locales: the fullest first sheet (fr, de) keeps
|
// that much under the last point; check:packet-render fails overflow (pointsOver).
|
||||||
// ~350px; check:packet-render fails either sheet's overflow (pointsOver).
|
const PT = { head: 22, sub: 19, text: 18.5, refs: 16, gap: 5, passage: 16, top: 82 };
|
||||||
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
|
// ⚠ 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
|
// (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
|
// LTR runs inside an RTL paragraph. Without isolation the Unicode bidi
|
||||||
|
|
@ -1408,16 +1406,12 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de
|
||||||
${p.print_passage ? passageBlock() + heartBlock() : ''}
|
${p.print_passage ? passageBlock() + heartBlock() : ''}
|
||||||
</div>`;
|
</div>`;
|
||||||
});
|
});
|
||||||
// Each sheet names its range (1–5, 6–12) beside the title, in the language's digits.
|
return sheet(`
|
||||||
const head = (from, to, intro) => `
|
|
||||||
<div style="position:absolute;left:${M}px;top:44px;right:${M}px">
|
<div style="position:absolute;left:${M}px;top:44px;right:${M}px">
|
||||||
<div class="points-title" style="font:700 40px Georgia,serif;color:#1b1a17">${esc(t('points_title'))}
|
<div class="points-title" style="font:700 40px Georgia,serif;color:#1b1a17">${esc(t('points_title'))}</div>
|
||||||
<span class="pt-range" style="font:400 24px Georgia,serif;color:#8c8170">${num(from)}–${num(to)}</span></div>
|
<div style="font:italic 18px Georgia,serif;color:#6d766e;margin-top:6px">${esc(t('points_intro'))}</div>
|
||||||
${intro ? `<div style="font:italic 18px Georgia,serif;color:#6d766e;margin-top:6px">${esc(t('points_intro'))}</div>` : ''}
|
</div>
|
||||||
</div>`;
|
<div class="points-body" style="position:absolute;left:${M}px;top:${M + PT.top}px;right:${M}px;bottom:80px">${themesBlock()}${rows.join('')}</div>
|
||||||
const body = (html, top) => `<div class="points-body" style="position:absolute;left:${M}px;top:${top}px;right:${M}px;bottom:80px">${html}</div>`;
|
|
||||||
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)}
|
|
||||||
<div class="fine">${esc(t('points_attrib'))}</div>`, 'sheet-points');
|
<div class="fine">${esc(t('points_attrib'))}</div>`, 'sheet-points');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -288,7 +288,7 @@ const HEAD_COLUMNS_CSS = `
|
||||||
// series-wide rules above stay as they were, so the series' SPARSE posters are byte-identical.
|
// series-wide rules above stay as they were, so the series' SPARSE posters are byte-identical.
|
||||||
// Print size = sheet px x 0.92 (the packet's margins) x 0.408 (1500px = 8.5in): 22px ≈ 8.3pt, 19px ≈ 7.1pt.
|
// Print size = sheet px x 0.92 (the packet's margins) x 0.408 (1500px = 8.5in): 22px ≈ 8.3pt, 19px ≈ 7.1pt.
|
||||||
const HEAD_COLUMNS_FULL_CSS = `
|
const HEAD_COLUMNS_FULL_CSS = `
|
||||||
.hcolk{font-size:19px;fill:#6b6553}
|
.hcolk{font-size:19px;fill:#2f2a22;font-weight:800;letter-spacing:.1em}
|
||||||
.hcolt{font-size:22px;fill:#3a362c}
|
.hcolt{font-size:22px;fill:#3a362c}
|
||||||
.hcolh{font-family:Georgia,'Times New Roman',serif;font-size:23px;font-weight:700;fill:#2f2a22;letter-spacing:.04em;font-variant:small-caps}
|
.hcolh{font-family:Georgia,'Times New Roman',serif;font-size:23px;font-weight:700;fill:#2f2a22;letter-spacing:.04em;font-variant:small-caps}
|
||||||
.hvref{font-size:19px}
|
.hvref{font-size:19px}
|
||||||
|
|
@ -827,23 +827,28 @@ function paraInto(push, text, { x, y, w, fs, lh, cls, lead }) {
|
||||||
// its text. A second copy of the arithmetic is how the full header's prediction once ran 53px short.
|
// its text. A second copy of the arithmetic is how the full header's prediction once ran 53px short.
|
||||||
// Built like the footer's three networks: equal columns, a text measure narrower than the column, each block
|
// Built like the footer's three networks: equal columns, a text measure narrower than the column, each block
|
||||||
// CENTRED on its column with its lines sharing one left edge, and short rules between the columns.
|
// CENTRED on its column with its lines sharing one left edge, and short rules between the columns.
|
||||||
// 1 the facts: a small key above each value (countries, administrative areas, people groups, series)
|
// 1 Jesus' strategy to feed the 5000
|
||||||
// 2 Jesus' strategy to feed the 5000
|
// 2 the facts: a bold key above each value (countries, administrative areas, people groups, series)
|
||||||
// 3 what the series is
|
// 3 what the series is
|
||||||
const HC = { gut: 32, fs: 22, lh: 27, keyFs: 19, keyLh: 24, headLh: 30, gap: 8, headFs: 23 };
|
const HC = { gut: 32, fs: 22, lh: 27, keyFs: 19, keyLh: 24, headLh: 30, gap: 8, headFs: 23 };
|
||||||
|
// textW is calibrated for Lato. Without Lato the browser falls back to a system sans that sets
|
||||||
|
// these columns 2-6% wider (measured 1.05-1.06x in DejaVu Sans). Each block is centred on its
|
||||||
|
// width at 1.03x, halfway, so any overrun splits across both sides instead of all running right.
|
||||||
|
const HC_FALLBACK = 1.03;
|
||||||
function headColumnsBand(top, facts, approxLists) {
|
function headColumnsBand(top, facts, approxLists) {
|
||||||
const GRID_L = BAND_MARGIN_G, GRID_W = PW - 2 * BAND_MARGIN_G;
|
const GRID_L = BAND_MARGIN_G, GRID_W = PW - 2 * BAND_MARGIN_G;
|
||||||
const CW = (GRID_W - 2 * HC.gut) / 3;
|
const CW = (GRID_W - 2 * HC.gut) / 3;
|
||||||
// The measure nearly fills the column (its inset is BAND_TEXT_PAD), so the larger type takes fewer lines.
|
// The measure leaves room for a fallback font (see HC_FALLBACK): a block set to it stays inside its column.
|
||||||
const TW = (CW - 10 - BAND_TEXT_PAD / 2) * 0.96;
|
const TW = (CW - 10 - BAND_TEXT_PAD / 2) * 0.92;
|
||||||
// Uppercase, letterspaced keys: measured as capitals plus the .14em tracking.
|
// Uppercase, letterspaced keys: measured as capitals plus the .14em tracking.
|
||||||
const keyW = (k) => textW(k.toUpperCase(), HC.keyFs) + k.length * HC.keyFs * 0.14;
|
const keyW = (k) => textW(k.toUpperCase(), HC.keyFs) + k.length * HC.keyFs * 0.14;
|
||||||
|
// Column order (2026-10-02): Jesus' strategy | the facts | what the series is.
|
||||||
const cols = [
|
const cols = [
|
||||||
facts.map(([key, value]) => ({ key, lines: wrapPx(value, HC.fs, TW) })),
|
|
||||||
[{ head: 'Jesus’ Strategy to Feed the 5000', paras: [
|
[{ head: 'Jesus’ Strategy to Feed the 5000', paras: [
|
||||||
'Jesus’ disciples faced a similar challenge: “How can we feed so many with only five loaves and two fish?” Jesus fed the 5,000 by supernaturally multiplying that small offering — and His answer holds the key to our people-groups problem.',
|
'Jesus’ disciples faced a similar challenge: “How can we feed so many with only five loaves and two fish?” Jesus fed the 5,000 by supernaturally multiplying that small offering — and His answer holds the key to our people-groups problem.',
|
||||||
'Through radical faith and miracle-expectant prayer, He focused on the few in front of Him — dividing the crowd and entrusting faithful men and women to serve the most strategic groups of fifties and hundreds (Mark 6:40).',
|
'Through radical faith and miracle-expectant prayer, He focused on the few in front of Him — dividing the crowd and entrusting faithful men and women to serve the most strategic groups of fifties and hundreds (Mark 6:40).',
|
||||||
] }],
|
] }],
|
||||||
|
facts.map(([key, value]) => ({ key, lines: wrapPx(value, HC.fs, TW) })),
|
||||||
[{ head: POSTER_TEXT.sideBoxHeading || 'What is a UPG100 List?', paras: [
|
[{ head: POSTER_TEXT.sideBoxHeading || 'What is a UPG100 List?', paras: [
|
||||||
(POSTER_TEXT.sideBoxBody
|
(POSTER_TEXT.sideBoxBody
|
||||||
|| 'This map is one of a {approxLists}-map set called the {seriesName} — a large, overwhelming target in the thousands, organised into clear groupings and displayed on maps and prayer lists.')
|
|| 'This map is one of a {approxLists}-map set called the {seriesName} — a large, overwhelming target in the thousands, organised into clear groupings and displayed on maps and prayer lists.')
|
||||||
|
|
@ -854,9 +859,9 @@ function headColumnsBand(top, facts, approxLists) {
|
||||||
let frags = '', bottom = top;
|
let frags = '', bottom = top;
|
||||||
cols.forEach((col, i) => {
|
cols.forEach((col, i) => {
|
||||||
const centre = GRID_L + i * (CW + HC.gut) + CW / 2;
|
const centre = GRID_L + i * (CW + HC.gut) + CW / 2;
|
||||||
if (i === 0) {
|
if (col[0] && col[0].key !== undefined) { // the facts column: a bold key above each value
|
||||||
const blockW = Math.min(TW, Math.max(...col.map((r) => Math.max(keyW(r.key), ...r.lines.map((ln) => textW(ln, HC.fs))))));
|
const blockW = Math.min(TW, Math.max(...col.map((r) => Math.max(keyW(r.key), ...r.lines.map((ln) => textW(ln, HC.fs))))));
|
||||||
const x = (centre - blockW / 2).toFixed(1);
|
const x = (centre - blockW * HC_FALLBACK / 2).toFixed(1);
|
||||||
let y = top;
|
let y = top;
|
||||||
col.forEach((r, k) => {
|
col.forEach((r, k) => {
|
||||||
if (k) y += HC.gap;
|
if (k) y += HC.gap;
|
||||||
|
|
@ -869,7 +874,7 @@ function headColumnsBand(top, facts, approxLists) {
|
||||||
const { head, paras } = col[0];
|
const { head, paras } = col[0];
|
||||||
const wrapped = paras.map((t) => wrapPx(t, HC.fs, TW));
|
const wrapped = paras.map((t) => wrapPx(t, HC.fs, TW));
|
||||||
const blockW = Math.min(TW, Math.max(textW(head, HC.headFs), ...wrapped.flat().map((ln) => textW(ln, HC.fs))));
|
const blockW = Math.min(TW, Math.max(textW(head, HC.headFs), ...wrapped.flat().map((ln) => textW(ln, HC.fs))));
|
||||||
const x = (centre - blockW / 2).toFixed(1);
|
const x = (centre - blockW * HC_FALLBACK / 2).toFixed(1);
|
||||||
frags += `<text x="${x}" y="${top}" class="hcolh">${esc(head)}</text>`;
|
frags += `<text x="${x}" y="${top}" class="hcolh">${esc(head)}</text>`;
|
||||||
let y = top + HC.headLh;
|
let y = top + HC.headLh;
|
||||||
wrapped.forEach((lines, k) => {
|
wrapped.forEach((lines, k) => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue