From 3c1d38bdc87b1f84ecdfbb1f2d8aed3310db7e28 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Fri, 2 Oct 2026 21:43:24 +0000 Subject: [PATCH] Packet lists: the three questions head each page ONCE over one grid of tally cells, and both packets open their list with the explanation band, x11 Full detail: the question no longer repeats under every tally cell; the heads stand once per page over the cells' own columns, and each cell is all tally room (96px columns, taller boxes). The worksheet's boxes get a little more room too. Both lists' first page: the intro and the blessing (Numbers 6:24-26, its credit, one line of references, the wheel pointer) at full width, then ONE band like the poster's (ruled above and below, three columns between short rules): the pace table (strongest to slower, weekly marked, each load computed from the list), what the three columns ask, and an EXAMPLE row of tally cells under the table's own heads, exactly as wide as the tally block and directly over it. One code path (explainTop, explainBand) for both packets. Measured with a 100-group list in all 11 languages: the worksheet's first page holds 10 rows (the tallest, ru, keeps 67px), Full detail's 12 under a 640px block (the tallest uses 612); the example sits over the real cells to the pixel. Gates: packet-variants checks the band in BOTH packets (once, before the first row, computed paces, one example row, heads once per page); packet-render reads the page sizes from the renderer, fails a clipped first-page block, and measures text direction against the content box (a padded cell is still pinned left). New strings x11: howto_bless_col, example_title, example_note. --- internal/check-packet-render.mjs | 28 ++-- internal/check-packet-variants.mjs | 43 +++--- library/packet/packet-render.js | 142 +++++++++++++++----- library/scripts/posters/packet-strings.json | 55 ++++++-- 4 files changed, 196 insertions(+), 72 deletions(-) diff --git a/internal/check-packet-render.mjs b/internal/check-packet-render.mjs index 87fb02742..31aa433a4 100644 --- a/internal/check-packet-render.mjs +++ b/internal/check-packet-render.mjs @@ -117,8 +117,11 @@ const layout = await page.evaluate(() => { const tb = rng.getBoundingClientRect() if (tb.width < 20 || box.width < 50 || box.width - tb.width < 12) continue if (cs.direction === 'rtl') out.rtlComputed += 1 - if (Math.round(box.right - tb.right) <= 1) out.pinnedRight += 1 - else if (Math.round(tb.left - box.left) <= 1) out.pinnedLeft += 1 + // Against the CONTENT box: a padded table cell (the pace table's) starts its run at its padding, which is + // still the left (or right) edge the direction pins it to. + const pl = parseFloat(cs.paddingLeft) || 0, pr = parseFloat(cs.paddingRight) || 0 + if (Math.round(box.right - pr - tb.right) <= 1) out.pinnedRight += 1 + else if (Math.round(tb.left - box.left - pl) <= 1) out.pinnedLeft += 1 out.sampled += 1 if (out.sampled >= 25) break } @@ -252,6 +255,8 @@ const r = await page.evaluate(() => { christianSheets: document.querySelectorAll('.sheet-christian').length, lifeCells: document.querySelectorAll('.lc-frame .lc-c').length, prayBoxes: document.querySelectorAll('.pray-box').length, + fdTopOver: (() => { const b = document.querySelector('.fd-top'); if (!b) return 0; const top = b.getBoundingClientRect().top + return Math.round(Math.max(0, ...[...b.children].map((k) => k.getBoundingClientRect().bottom + parseFloat(getComputedStyle(k).marginBottom))) - top - b.clientHeight) })(), funnelTip: document.querySelectorAll('.funnel-tip').length, lifeShaded: document.querySelectorAll('.lc-frame .lc-reached').length, // ★ FILLED FROM THE TOP, in reading order (the packet's design), measured on the DOM order of the cells: @@ -730,17 +735,24 @@ if (!boxes.length) { // sheets — a 100-year life in weeks, then the SAME grid shaded by the evangelical share of these // groups' population — so they are DETECTED and added as known terms for exactly the reason the // funnel was, and for the reason stated above: a tolerance here would hide a dropped list page. -const PER_PAGE = 20 +// THE PAGE SIZES ARE THE RENDERER'S, read from it rather than retyped: the first list page of each packet +// gives rows to the explanation band (2026-10-02), so a typed number drifted the moment the band changed. +const { createPacketRenderer: makeR } = await import(resolve(dirname(fileURLToPath(import.meta.url)), '../library/packet/packet-render.js')) +const RR = makeR({ strings: catalog, lang: 'en' }) +const PER_PAGE = RR.PER_PAGE const funnelSheets = r.funnel ? 1 : 0 const lifeSheets = (r.lifeSheets || 0) + (r.christianSheets || 0) // ⚠ AND THE SETS PAGES (bounded, centered): back matter, DETECTED for the same reason as the rest. // Before they were counted the arithmetic only held when a fixture omitted set data. const setsSheets = r.setsSheets || 0 -// The worksheet paginates 11 rows on its first page and 18 after (packet-render WS_FIRST / WS_REST; the first page -// gave five rows to its description and pace table on 2026-10-02; 20 and 24 -// before its rows gained a photo and a description, 2026-09-30). -const wsPages = (n) => (n <= 0 ? 0 : 1 + Math.ceil(Math.max(0, n - 11) / 18)) -const listSheets = r.cards ? Math.ceil(r.cards / PER_PAGE) : wsPages(r.wsRows) +// Both lists give their first page to the explanation band: the worksheet holds WS_FIRST rows there and +// WS_REST after; the profile pages FD_FIRST, then PER_PAGE. +const wsPages = (n) => (n <= 0 ? 0 : 1 + Math.ceil(Math.max(0, n - RR.WS_FIRST) / RR.WS_REST)) +const fdPages = (n) => (n <= 0 ? 0 : 1 + Math.ceil(Math.max(0, n - RR.FD_FIRST) / PER_PAGE)) +const listSheets = r.cards ? fdPages(r.cards) : wsPages(r.wsRows) +// The Full detail's first page reserves a block for the explanation and CLIPS what outgrows it (rather than +// let it overlap the rows): only a measurement sees a clipped line, so measure it. +if (r.fdTopOver > 0) fails.push(`the Full detail's explanation block outgrows its reserved height by ${r.fdTopOver}px — text is clipped (raise FD_TOP_H)`) 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. diff --git a/internal/check-packet-variants.mjs b/internal/check-packet-variants.mjs index 781981a9d..66f55da3a 100644 --- a/internal/check-packet-variants.mjs +++ b/internal/check-packet-variants.mjs @@ -97,7 +97,7 @@ ok(count(light, /sheet-sets-(bounded|centered)/g) === 0, 'lightweight still carr // ⚠ THE DELTA IS DERIVED, never relaxed into a range: lightweight drops four back pages (life, % grid, // bounded, centered) AND lists the same people as a worksheet, whose pages hold more rows than profiles. { - const profilePages = Math.ceil(45 / R.PER_PAGE) + const profilePages = 1 + Math.ceil(Math.max(0, 45 - R.FD_FIRST) / R.PER_PAGE) const worksheetPages = count(light, /
/g], +]) { const E = strings.en - const before = page1.indexOf('class="ws-howto"') >= 0 && page1.indexOf('class="ws-howto"') < page1.indexOf('class="ws-row"') - ok(count(light, /class="ws-howto"/g) === 1 && before, 'the lightweight list should explain its three columns ONCE, on its first page, before the first row') - ok(count(full, /class="ws-howto"|class="ws-pace"/g) === 0, 'the full packet should not carry the worksheet description') - ok(page1.includes(E.blessing_text.replace(/&/g, '&')) && page1.includes(E.blessing_credit), 'the first page should print the blessing (Numbers 6:24-26) with its credit') - ok(count(page1, /class="ws-refs"/g) === 1 && count(page1, /Psalm 134:3/g) === 1, 'the first page should carry ONE line of blessing references') - ok(page1.includes(E.howto_wheel), 'the first page should point to the prayer wheel at the end of the packet') - const paceRows = (page1.match(/([\s\S]*?)<\/table>/) || [])[1] || '' - ok(count(paceRows, /` + + ``).join(''); + const item = (key, body) => `
  • ${esc(t(key))} ${esc(t(body))}
  • `; + const grid = `grid-template-columns:${tally.cols};column-gap:${tally.gap}px`; + return `
    +
    ${esc(t('pace_title'))}
    +

    ${esc(t('pace_lead'))}

    ]+>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 200)}`) - ok(!/no pace to keep/.test(light), 'the lightweight list should no longer say there is no pace to keep') - ok(count(light, /class="ws-box"/g) === 45 * 3, `every worksheet row should carry three tally cells, got ${count(light, /class="ws-box"/g)}`) + const band = html.indexOf('
    0 && band < html.search(rowRe), + `the ${name} list should explain itself ONCE, on its first list page, before the first row`) + ok(html.includes(E.blessing_text.replace(/&/g, '&')) && html.includes(E.blessing_credit), `the ${name} list should print the blessing (Numbers 6:24-26) with its credit`) + ok(count(html, /class="ws-refs"/g) === 1 && count(html, /Psalm 134:3/g) === 1, `the ${name} list should carry ONE line of blessing references`) + ok(html.includes(E.howto_wheel), `the ${name} list should point to the prayer wheel at the end of the packet`) + const pace = (html.match(/([\s\S]*?)<\/table>/) || [])[1] || '' + ok(count(pace, /]+>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 160)}`) + ok(count(html, /class="xb-cell /g) === 3 && count(html, /class="xb-marks"/g) === 3, `the ${name} band's example should be ONE row of three tally cells with marks`) + ok(count(html, rowRe) === 45 && count(html, cellRe) === 45 * 3, `every ${name} row should carry three tally cells, got ${count(html, cellRe)} for ${count(html, rowRe)} rows`) + ok(count(html, headRe) === count(html, pageRe), `the ${name} question heads should stand once per list page: ${count(html, headRe)} heads on ${count(html, pageRe)} pages`) } +ok(!/no pace to keep/.test(light), 'the lightweight list should no longer say there is no pace to keep') +ok(!/[^<]*<\/em><\/span>/.test(full.slice(full.indexOf('class="card"'))), 'the full packet repeats a question under each tally cell; the heads stand once per page') // ★ THE LIFE CALENDAR, HEADLESS — the grid's CONTENT, not just its page. An adversarial review mutated the // renderer to shade the LAST 33 years, to shade nothing, and to draw 90 rows; every packet gate stayed green // because none looked inside the grid. Read the cells of each frame, in order (the legend's sample boxes diff --git a/library/packet/packet-render.js b/library/packet/packet-render.js index 71d6fd3c7..00836b685 100644 --- a/library/packet/packet-render.js +++ b/library/packet/packet-render.js @@ -125,7 +125,16 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de const PHOTO = Math.min(CH - 8, 64); // the picture column: square, row-height-capped // The three checkbox cells. Each holds a box plus its short label; sized so all three fit beside // the description without the labels wrapping in the longest locale we ship. - const PRAY_CELL = 84; + // 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 -> 96 gives every box a little more of it. + const PRAY_CELL = 96; + // 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 and the first page 12 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 = 46, FD_TOP_H = 640; + 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 // ★ MEASURED FILL, 2026-09-29: at 960px the single-chart pages ended 60-69% of the way down the sheet @@ -247,11 +256,33 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de .ws-name small.ws-desc{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;white-space:normal; font:400 ${WS_DESC_PX}px/1.3 Georgia,serif;margin-top:2px} /* A TALLY cell: room for several groups of five marks, not a box for one tick. */ - .ws-box{justify-self:center;width:${WS_TALLY_W - 14}px;height:${WS_ROW - 30}px;box-sizing:border-box;border:1.5px solid #b0a993;border-radius:5px;background:#fff} + .ws-box{justify-self:center;width:${WS_TALLY_W - 10}px;height:${WS_ROW - 22}px;box-sizing:border-box;border:1.5px solid #b0a993;border-radius:6px;background:#fff} .ws-foot{position:absolute;left:0;right:0;bottom:22px;text-align:center;font:400 12px Georgia,serif;color:#a89f8c} /* The first page's description and pace table: quiet type, one rule per section, nothing boxed. */ .ws-sec{margin:0 0 6px;font:700 14px Georgia,serif;letter-spacing:.14em;text-transform:uppercase;color:#8a8577} - .ws-intro + .ws-howto{margin-top:-6px} + /* THE EXPLANATION BAND (explainBand): ruled above and below like the poster's header band, three columns + between short rules; the third is exactly the table's tally block, so its example sits over the cells. */ + .xb-bless{margin:0 0 14px} + .xb-lead{margin:0 0 4px;font:400 19px/1.45 Georgia,serif;color:#413d35} + .xb{display:grid;column-gap:40px;border-top:1.5px solid #c9bfa4;border-bottom:1.5px solid #c9bfa4;padding:14px 0 12px;margin:0 0 16px} + .xb-col{position:relative;min-width:0} + .xb-col + .xb-col::before{content:'';position:absolute;inset-inline-start:-20px;top:4px;bottom:4px;border-inline-start:1px solid #d6ccb4} + .xb-p{margin:0 0 6px;font:400 16px/1.4 Georgia,serif;color:#413d35} + .xb .ws-pace-t{font-size:15px;table-layout:auto} + .xb .ws-pace-t td.xb-word{white-space:nowrap} + .xb .ws-pace-t th,.xb .ws-pace-t td{padding:3px 6px;border-bottom:1px solid #e2ddd0;text-align:start;font-size:15px;line-height:1.3} + .xb .ws-pace-t th{font-weight:700;color:#1b1a17} + .xb .ws-pace-t .xb-word{font-weight:700;color:#6d766e} + .xb .ws-cols li{font-size:16px;line-height:1.4;margin:0 0 7px} + .xb-ex-head,.xb-ex-row{display:grid} + .xb-ex-head{align-items:end;padding:0 0 6px;border-bottom:2px solid #3b463d;font:700 13px/1.2 Georgia,serif;color:#6f6a5c;text-align:center} + .xb-ex-head span{text-wrap:balance;padding:0 2px} + .xb-ex-row{padding:8px 0} + .xb-cell{justify-self:center;display:flex;align-items:center;justify-content:center;box-sizing:border-box;border:1.5px solid #b0a993;border-radius:6px;background:#fff} + .xb-cell-ws{width:${WS_TALLY_W - 10}px;height:${WS_ROW - 22}px} + .xb-cell-fd{width:${PRAY_CELL - 12}px;height:${Math.round(CH - 22)}px} + .xb-marks{height:60%;max-width:92%;stroke:#3b463d;stroke-width:2.6;stroke-linecap:round;fill:none} + .xb-ex-note{margin-top:4px;font-size:14px;color:#6f6a5c} .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 b{color:#1b1a17} @@ -328,8 +359,12 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de .pray-col{width:${PRAY_CELL}px;flex:0 0 auto;display:flex;flex-direction:column;align-items:center; gap:3px;font:400 10px Georgia,serif;color:#6f6a5c;text-align:center;line-height:1.15} /* A tally cell above its question, as on the worksheet: wide and short, for a few groups of marks. */ - .pray-box{display:inline-block;width:${PRAY_CELL - 16}px;height:26px;box-sizing:border-box;border:1.5px solid #b0a993;border-radius:4px;background:#fff;flex:0 0 auto} - .pray-col em{font-style:normal;text-wrap:balance} + .pray-box{display:inline-block;width:${PRAY_CELL - 12}px;height:${Math.round(CH - 22)}px;box-sizing:border-box;border:1.5px solid #b0a993;border-radius:5px;background:#fff;flex:0 0 auto} + /* 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 12px/1.2 Georgia,serif;color:#6f6a5c;text-align:center} + .pray-head span{text-wrap:balance;padding:0 3px 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 is the CONTENT box and the padding + border are added OUTSIDE it, so the row renders taller @@ -1108,17 +1143,24 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de return `
    ${esc(f.text)}
    `; } + // The first profile page carries the explanation (the same block as the worksheet's) and FD_FIRST rows; + // every later page carries PER_PAGE. The question heads stand ONCE over each page's tally column. + const FD_FIRST = Math.floor((H - M - FD_ROWS_TOP + GUT) / (CH + GUT)); function listPages(zone, groups, photos) { const pages = []; - for (let start = 0; start < groups.length; start += PER_PAGE) { - const slice = groups.slice(start, start + PER_PAGE); + const prayHead = (top) => `
    ${ + PRAY_COLS.map((key) => `${esc(t(key))}`).join('')}
    `; + for (let start = 0, page = 0; start < groups.length; page += 1) { + const per = page === 0 ? FD_FIRST : PER_PAGE; + const rowsTop = page === 0 ? FD_ROWS_TOP : M + HEAD; + const slice = groups.slice(start, start + per); // ★ ONE FULL-WIDTH ROW PER GROUP. The column index is gone: `top` advances by the row, and the // three cells sit inside it. Absolute positioning is kept rather than switching to flow layout // because the print frame must not reflow — a row that moves between the browser preview and // the printed sheet is how a 20-row page becomes a 21-row page. const cards = slice.map((g, k) => { const i = start + k; - const top = M + HEAD + k * (CH + GUT); + const top = rowsTop + k * (CH + GUT); const src = photos.get(g.slug); // esc() on the URL too, not just on the name below it. src is photos.get(slug), and // usePosterPrints builds that Map straight from the API's image_url — so a quote in a @@ -1133,15 +1175,19 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de
    ${i + 1}. ${esc(g.name)}
    ${descCell(g)} -
    ${PRAY_COLS.map((key) => `${esc(t(key))}`).join('')}
    +
    ${PRAY_COLS.map(() => '').join('')}
    `; }).join(''); + const first = page === 0 + ? `
    ${explainTop()}${explainBand(groups.length, + { w: PRAY_COLS.length * PRAY_CELL, cols: `repeat(${PRAY_COLS.length},${PRAY_CELL}px)`, gap: 0, cell: 'xb-cell xb-cell-fd' })}
    ` + : ''; pages.push(sheet(`
    ${esc(zone.label || zone.id)}
    ${esc(fill(t('list_header'), - { from: num(start + 1), to: num(Math.min(start + PER_PAGE, groups.length)), count: num(groups.length) }))}
    - ${cards}`)); + { from: num(start + 1), to: num(Math.min(start + per, groups.length)), count: num(groups.length) }))} + ${first}${prayHead(rowsTop - PRAY_HEAD_H)}${cards}`)); + start += per; } return pages; } @@ -1157,12 +1203,11 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de // Without a describer (the Node gates) the row keeps its country · population line. // ⚠ STILL EVERY GROUP. A variant may change HOW people are listed, never WHETHER they are. // Sizes in sheet px (1500px = 8.5in, so 1px ~ 0.41pt): names 26px ~ 10.6pt, rows 92px ~ 0.52in. - // ★ THE FIRST PAGE EXPLAINS THE LIST BEFORE IT LISTS ANYONE (2026-10-02): what the three columns ask, the - // blessing to pray, the prayer wheel at the back, and a pace table. That costs five rows of the first page - // (16 → 11); later pages keep 18. Measured in all 11 locales with a 100-group list: the longest first pages - // (ru, hi) keep 77px under their eleventh row and the tightest later page 53px (de, its three-line tally - // heading); a twelfth or nineteenth row would not fit. - const WS_ROW = 92, WS_FIRST = 11, WS_REST = 18, WS_PHOTO = 76, WS_DESC_PX = 17; + // ★ THE FIRST PAGE EXPLAINS THE LIST BEFORE IT LISTS ANYONE (2026-10-02): the blessing, then the band (the + // pace table, what the three columns ask, an example row). That costs six rows of the first page (16 → 10); + // later pages keep 18. Measured in all 11 locales with a 100-group list: the tallest first page (ru) keeps + // 67px under its tenth row; an eleventh would not fit. + const WS_ROW = 92, WS_FIRST = 10, WS_REST = 18, WS_PHOTO = 76, WS_DESC_PX = 17; // A tally column is wide enough for a heading of two short lines ("What do you / want me to know?") and // for a few groups of tally marks in its cell. const WS_TALLY_W = 160; @@ -1205,21 +1250,49 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de if ((n * 7) / days >= 1) return fill(t('pace_week'), { n: num(Math.round((n * 7) / days)) }); return fill(t('pace_month'), { n: num(Math.max(1, Math.round((n * 30) / days))) }); } - function worksheetHowTo(n) { + // ── THE LIST'S FIRST PAGE: the blessing, then ONE band that explains the table below it ────────────── + // Shared by both packets (one code path): the intro and the blessing at full width, then a band built like + // the poster's header band (a rule above, three columns between short rules, a rule below): + // 1 the PACE table, strongest to slower, weekly marked, each pace's daily load computed from the list; + // 2 what the three columns ask, in plain words; + // 3 an EXAMPLE: the table's own question heads over one row of tally cells with marks in them. Its + // column is exactly as wide as the table's tally columns and sits over them, so it reads as a legend. + // `tally` describes the table this band sits over: { w } its tally block's width, { cols, gap } the grid + // of its cells, { cell } the class that draws a cell the size of the table's own. + function explainTop() { const refs = (Array.isArray(L.bless_refs) ? L.bless_refs : BLESS_REFS).map(refSpan).join(' · '); - const col = (key, body, extra = '') => `
  • ${esc(t(key))} ${esc(t(body))}${extra}
  • `; - const blessing = `
    ${esc(t('blessing_text'))}${esc(t('blessing_credit'))}
    ` - + `
    ${esc(t('howto_bless_more'))} ${refs}
    `; - const head = PACES.map((p) => `${esc(t(p.key))}`).join(''); - const word = PACES.map((p) => `${esc(t(p.word))}`).join(''); - const load = PACES.map((p) => `${esc(paceLoad(n, p.days))}`).join(''); - return `
    ${esc(t('howto_title'))}
    -
      ${col('pray_blessed', 'howto_bless', blessing)}${col('pray_know', 'howto_know')}${col('pray_do', 'howto_do')}
    -

    ${esc(t('howto_wheel'))}

    -
    ${esc(t('pace_title'))}
    -

    ${esc(t('pace_lead'))}

    -
    ${head}${word}${load}
    -

    ${esc(t('pace_note'))}

    `; + return `

    ${esc(t('light_intro'))}

    +

    ${esc(t('howto_bless'))}

    +
    ${esc(t('blessing_text'))}${esc(t('blessing_credit'))}
    +
    ${esc(t('howto_bless_more'))} ${refs}
    +

    ${esc(t('howto_wheel'))}

    `; + } + // Tally marks the way they are drawn by hand: four strokes, the fifth across them. + function tallySvg(n) { + const out = []; + for (let g = 0, x = 6; g < Math.ceil(n / 5); g += 1, x += 34) { + const k = Math.min(5, n - g * 5); + for (let i = 0; i < Math.min(k, 4); i += 1) out.push(``); + if (k === 5) out.push(``); + } + return ``; + } + function explainBand(n, tally) { + const pace = PACES.map((p) => `
    ${esc(t(p.key))}${esc(t(p.word))}${esc(paceLoad(n, p.days))}
    ${pace}
    +

    ${esc(t('pace_note'))}

    +
    ${esc(t('howto_title'))}
    +
      ${item('pray_blessed', 'howto_bless_col')}${item('pray_know', 'howto_know')}${item('pray_do', 'howto_do')}
    +
    ${esc(t('example_title'))}
    +
    ${PRAY_COLS.map((k) => `${esc(t(k))}`).join('')}
    +
    ${[7, 3, 2].map((m) => `${tallySvg(m)}`).join('')}
    +

    ${esc(t('example_note'))}

    + `; } function worksheetPages(zone, groups, photos = new Map()) { const pages = []; @@ -1241,7 +1314,8 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de const top = page === 0 ? `
    ${esc(zone.label || zone.id)}

    ${esc(fill(t('map_page_sub'), { count: num(groups.length) }))}

    -

    ${esc(t('light_intro'))}

    ${worksheetHowTo(groups.length)}` + ${explainTop()}${explainBand(groups.length, { w: PRAY_COLS.length * WS_TALLY_W + (PRAY_COLS.length - 1) * 8, + cols: `repeat(${PRAY_COLS.length},${WS_TALLY_W}px)`, gap: 8, cell: 'xb-cell xb-cell-ws' })}` : `
    ${esc(zone.label || zone.id)}${esc(fill(t('list_header'), { from: num(start + 1), to: num(Math.min(start + per, groups.length)), count: num(groups.length) }))}
    `; pages.push(sheet(`${top}${head}${rows}
    ${esc(t('privacy_note'))}
    `, 'sheet-worksheet sheet-flow')); @@ -1653,5 +1727,5 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de // VARIANTS is exported so the UI enumerates the packets it can print instead of hardcoding two // strings in a menu — add a third variant here and the caret gains an entry with no UI change. - return { buildPacket, mapPage, lifePage, christianPage, funnelPage, listPages, pointsPage, wheelPage, css, mapCss, liftMap, mapFromImage, mapSheetFiles, PER_PAGE, W, H, VARIANTS }; + return { buildPacket, mapPage, lifePage, christianPage, funnelPage, listPages, pointsPage, wheelPage, css, mapCss, liftMap, mapFromImage, mapSheetFiles, PER_PAGE, FD_FIRST, WS_FIRST, WS_REST, W, H, VARIANTS }; } diff --git a/library/scripts/posters/packet-strings.json b/library/scripts/posters/packet-strings.json index da826257a..625fd45c7 100644 --- a/library/scripts/posters/packet-strings.json +++ b/library/scripts/posters/packet-strings.json @@ -318,7 +318,10 @@ "pace_month": "about {n} a month", "pace_note": "Praying for every group daily is for gifted intercessors who give about three hours a day to prayer. For most of us, weekly is a healthy, steady pace.", "blessing_text": "“The LORD bless you, and keep you. The LORD make his face to shine on you, and be gracious to you. The LORD lift up his face towards you, and give you peace.”", - "blessing_credit": "Numbers 6:24–26 · World English Bible, British Edition (public domain) · ebible.org" + "blessing_credit": "Numbers 6:24–26 · World English Bible, British Edition (public domain) · ebible.org", + "howto_bless_col": "Bless them by name, with the blessing above or another from Scripture.", + "example_title": "Example", + "example_note": "One mark each time you pray it; the fifth mark crosses the four before it." }, "es": { "map_page_kicker": "GRUPOS ÉTNICOS NO EVANGELIZADOS", @@ -623,7 +626,10 @@ "pace_month": "unos {n} al mes", "pace_note": "Orar cada día por todos los pueblos es para intercesores con ese don, que dedican unas tres horas diarias a la oración. Para la mayoría de nosotros, cada semana es un ritmo sano y constante.", "blessing_text": "«Jehová te bendiga, y te guarde: Haga resplandecer Jehová su rostro sobre ti, y haya de ti misericordia: Jehová alce á ti su rostro, y ponga en ti paz.»", - "blessing_credit": "Números 6:24–26 · Reina Valera 1909 (dominio público) · ebible.org" + "blessing_credit": "Números 6:24–26 · Reina Valera 1909 (dominio público) · ebible.org", + "howto_bless_col": "Bendícelos por su nombre, con la bendición de arriba u otra de las Escrituras.", + "example_title": "Ejemplo", + "example_note": "Una marca cada vez que lo ores; la quinta cruza las cuatro anteriores." }, "fr": { "map_page_kicker": "GROUPES ETHNIQUES NON ATTEINTS", @@ -928,7 +934,10 @@ "pace_month": "environ {n} par mois", "pace_note": "Prier chaque jour pour tous ces peuples convient aux intercesseurs qui en ont reçu le don et consacrent environ trois heures par jour à la prière. Pour la plupart d’entre nous, chaque semaine est un rythme sain et régulier.", "blessing_text": "« L'Éternel te bénisse et te garde! L'Éternel fasse luire sa face sur toi et te fasse grâce! L'Éternel tourne sa face vers toi et te donne la paix! »", - "blessing_credit": "Nombres 6:24–26 · French Ostervald Bible (domaine public) · ebible.org" + "blessing_credit": "Nombres 6:24–26 · French Ostervald Bible (domaine public) · ebible.org", + "howto_bless_col": "Bénissez-les par leur nom, avec la bénédiction ci-dessus ou une autre tirée des Écritures.", + "example_title": "Exemple", + "example_note": "Un trait chaque fois que vous le priez ; le cinquième barre les quatre précédents." }, "pt": { "map_page_kicker": "GRUPOS ÉTNICOS NÃO ALCANÇADOS", @@ -1230,7 +1239,10 @@ "pace_month": "cerca de {n} por mês", "pace_note": "Orar todo dia por todos os povos é para intercessores com esse dom, que dedicam cerca de três horas por dia à oração. Para a maioria de nós, toda semana é um ritmo saudável e constante.", "blessing_text": "“Yahweh te abençoe, e te guarde. Yahweh faz o seu rosto para brilhar em você, e seja gracioso com você. Yahweh levanta o rosto na sua direção, e lhe dar paz.”", - "blessing_credit": "Números 6:24–26 · World Portuguese Bible (domínio público) · ebible.org" + "blessing_credit": "Números 6:24–26 · World Portuguese Bible (domínio público) · ebible.org", + "howto_bless_col": "Abençoe-os pelo nome, com a bênção acima ou outra das Escrituras.", + "example_title": "Exemplo", + "example_note": "Um risco cada vez que você orar; o quinto cruza os quatro anteriores." }, "de": { "map_page_kicker": "UNERREICHTE VOLKSGRUPPEN", @@ -1535,7 +1547,10 @@ "pace_month": "etwa {n} im Monat", "pace_note": "Täglich für jede Volksgruppe zu beten ist für begabte Fürbitter, die dem Gebet etwa drei Stunden am Tag widmen. Für die meisten von uns ist wöchentlich ein gesunder, beständiger Rhythmus.", "blessing_text": "„Der HERR segne dich und behüte dich; der HERR lasse sein Angesicht leuchten über dir und sei dir gnädig; der HERR hebe sein Angesicht über dich und gebe dir Frieden.“", - "blessing_credit": "4. Mose 6,24–26 · German Luther Bible 1912 (gemeinfrei) · ebible.org" + "blessing_credit": "4. Mose 6,24–26 · German Luther Bible 1912 (gemeinfrei) · ebible.org", + "howto_bless_col": "Segnen Sie sie mit Namen, mit dem Segen oben oder einem anderen aus der Schrift.", + "example_title": "Beispiel", + "example_note": "Ein Strich für jedes Gebet; der fünfte Strich kreuzt die vier davor." }, "it": { "map_page_kicker": "GRUPPI ETNICI NON RAGGIUNTI", @@ -1837,7 +1852,10 @@ "pace_month": "circa {n} al mese", "pace_note": "Pregare ogni giorno per tutti i popoli è per gli intercessori che ne hanno il dono e dedicano circa tre ore al giorno alla preghiera. Per la maggior parte di noi, ogni settimana è un ritmo sano e costante.", "blessing_text": "«Il Signore ti benedica e ti guardi. Il Signore faccia risplendere le sua faccia verso te, e ti sia propizio. Alzi il Signore la sua faccia verso te, e ti stabilisca la pace.»", - "blessing_credit": "Numeri 6:24–26 · Italian Diodati Bible (pubblico dominio) · ebible.org" + "blessing_credit": "Numeri 6:24–26 · Italian Diodati Bible (pubblico dominio) · ebible.org", + "howto_bless_col": "Benedicili chiamandoli per nome, con la benedizione qui sopra o un'altra dalle Scritture.", + "example_title": "Esempio", + "example_note": "Un trattino ogni volta che lo preghi; il quinto attraversa i quattro precedenti." }, "ro": { "map_page_kicker": "GRUPURI ETNICE NEATINSE", @@ -2137,7 +2155,10 @@ "pace_day": "cam {n} pe zi", "pace_week": "cam {n} pe săptămână", "pace_month": "cam {n} pe lună", - "pace_note": "Rugăciunea zilnică pentru toate popoarele este pentru mijlocitorii care au acest dar și dedică rugăciunii cam trei ore pe zi. Pentru cei mai mulți dintre noi, săptămânal este un ritm sănătos și constant." + "pace_note": "Rugăciunea zilnică pentru toate popoarele este pentru mijlocitorii care au acest dar și dedică rugăciunii cam trei ore pe zi. Pentru cei mai mulți dintre noi, săptămânal este un ritm sănătos și constant.", + "howto_bless_col": "Binecuvântează-i pe nume, cu binecuvântarea de mai sus sau cu alta din Scriptură.", + "example_title": "Exemplu", + "example_note": "O liniuță de fiecare dată când te rogi; a cincea le taie pe cele patru dinainte." }, "ru": { "map_page_kicker": "НЕОХВАЧЕННЫЕ НАРОДЫ", @@ -2449,7 +2470,10 @@ "Psalm 28:11", "Psalm 120:7", "Genesis 12:3" - ] + ], + "howto_bless_col": "Благословляйте их по имени — словами выше или другими словами из Писания.", + "example_title": "Пример", + "example_note": "Одна черточка за каждую молитву; пятая перечёркивает четыре предыдущие." }, "ar": { "map_page_kicker": "شعوب غير مبلَّغة", @@ -2754,7 +2778,10 @@ "pace_month": "نحو {n} في الشهر", "pace_note": "الصلاة اليومية لأجل كل الشعوب للمتشفّعين الذين نالوا هذه الموهبة ويخصّصون للصلاة نحو ثلاث ساعات يوميًا. ولمعظمنا، الصلاة الأسبوعية إيقاع صحّي وثابت.", "blessing_text": "«يُبَارِكُكَ الرَّبُّ وَيَحْرُسُكَ. يُضِيءُ الرَّبُّ بِوَجْهِهِ عَلَيْكَ وَيَرْحَمُكَ. يَرْفَعُ الرَّبُّ وَجْهَهُ عَلَيْكَ وَيَمْنَحُكَ سَلَامًا.»", - "blessing_credit": "العدد ٦:‏٢٤–٢٦ · Arabic Van Dyck Bible (ملك عام) · ebible.org" + "blessing_credit": "العدد ٦:‏٢٤–٢٦ · Arabic Van Dyck Bible (ملك عام) · ebible.org", + "howto_bless_col": "باركهم بأسمائهم، بالبركة التي في الأعلى أو بغيرها من الكتاب المقدس.", + "example_title": "مثال", + "example_note": "خطّ واحد كلما صلّيت؛ والخطّ الخامس يقطع الأربعة التي قبله." }, "hi": { "map_page_kicker": "अनछुए जनसमूह", @@ -3054,7 +3081,10 @@ "pace_day": "लगभग {n} प्रतिदिन", "pace_week": "लगभग {n} प्रति सप्ताह", "pace_month": "लगभग {n} प्रति माह", - "pace_note": "हर जनसमूह के लिए प्रतिदिन प्रार्थना उन मध्यस्थों के लिए है जिन्हें यह वरदान मिला है और जो प्रार्थना में दिन के लगभग तीन घंटे लगाते हैं। हममें से अधिकांश के लिए साप्ताहिक प्रार्थना एक स्वस्थ, स्थिर गति है।" + "pace_note": "हर जनसमूह के लिए प्रतिदिन प्रार्थना उन मध्यस्थों के लिए है जिन्हें यह वरदान मिला है और जो प्रार्थना में दिन के लगभग तीन घंटे लगाते हैं। हममें से अधिकांश के लिए साप्ताहिक प्रार्थना एक स्वस्थ, स्थिर गति है।", + "howto_bless_col": "उनके नाम लेकर उन्हें आशीष दें — ऊपर दी गई आशीष से या पवित्रशास्त्र की किसी और आशीष से।", + "example_title": "उदाहरण", + "example_note": "हर बार प्रार्थना करने पर एक लकीर; पाँचवीं लकीर पिछली चार को काटती है।" }, "zh": { "map_page_kicker": "未得之民", @@ -3356,6 +3386,9 @@ "pace_month": "每月约 {n} 个", "pace_note": "每天为每一个族群祷告,适合有代祷恩赐、每天约用三小时祷告的人。对我们大多数人来说,每周一次是健康而稳定的节奏。", "blessing_text": "“愿耶和华赐福给你,保护你。愿耶和华使他的脸光照你,赐恩给你。愿耶和华向你仰脸,赐你平安。”", - "blessing_credit": "民数记 6:24–26 · Chinese Union Version (公有领域) · ebible.org" + "blessing_credit": "民数记 6:24–26 · Chinese Union Version (公有领域) · ebible.org", + "howto_bless_col": "按着名字祝福他们,可以用上面的祝福,或圣经中的其他祝福。", + "example_title": "示例", + "example_note": "每祷告一次画一道;第五道横穿前面四道。" } }