Wall of People Groups: the poster tells the story, a prayer-themes chart of treasure, mountain and sea above the people groups and the priestly blessing and the priesthood of believers beneath them
This commit is contained in:
parent
317f33c15e
commit
4b299bad8f
3 changed files with 108 additions and 26 deletions
|
|
@ -16,7 +16,7 @@
|
|||
*
|
||||
* Pure: plan maths + an HTML string for the shared in-place print frame (library printInPlace). No Vue.
|
||||
*/
|
||||
import { blessingFor, POSTER } from './blessings.js'
|
||||
import { blessingFor, POSTER, THEMES } from './blessings.js'
|
||||
|
||||
// Sheet sizes in inches (w × h of ONE printed page), how many stack vertically, and the border.
|
||||
export const PRESETS = {
|
||||
|
|
@ -40,7 +40,10 @@ export function planPoster(presetId, n) {
|
|||
const P = PRESETS[presetId] || PRESETS[DEFAULT_PRESET]
|
||||
const b = P.border
|
||||
const W = P.sheetW - 2 * b
|
||||
const bannerH = Math.max(1.6, W * 0.11), footerH = Math.max(0.7, W * 0.045)
|
||||
// Top of sheet 1: the title + DOXA line, then the prayer-themes table. Bottom of the last sheet: the
|
||||
// priestly blessing, the priesthood of believers and the closing line. All scale with the width.
|
||||
const headH = W * 0.075, tableH = W * 0.17
|
||||
const bannerH = headH + tableH, footerH = W * 0.16
|
||||
// Each sheet's content band, in the sheet's own coordinates: y0..y1.
|
||||
const bands = []
|
||||
for (let s = 0; s < P.sheets; s++) {
|
||||
|
|
@ -67,15 +70,22 @@ export function planPoster(presetId, n) {
|
|||
}
|
||||
}
|
||||
// Text sizes from the card: the verse gets ~4 lines of ~26 characters across the card width.
|
||||
const versePt = (cw / 13.5) / PT // character width ≈ 0.5 em, 26 chars per line
|
||||
const versePt = (cw / 12.5) / PT // character width ≈ 0.5 em, ~24 chars per line
|
||||
return {
|
||||
preset: P, presetId: PRESETS[presetId] ? presetId : DEFAULT_PRESET, n, cols, rowsPer, cw, ch, bands, cards,
|
||||
bannerH, footerH, contentW: W,
|
||||
bannerH, footerH, headH, tableH, contentW: W,
|
||||
posterW: P.sheetW, posterH: P.sheetH * P.sheets - (P.sheets - 1) * b, // after gluing: 60 × 96 for 5x8ft
|
||||
text: { versePt: +versePt.toFixed(1), namePt: +(versePt * 1.25).toFixed(1), readable: versePt >= MIN_READABLE_PT },
|
||||
}
|
||||
}
|
||||
|
||||
// Three simple line icons for the prayer themes (stroke = currentColor).
|
||||
const ICONS = {
|
||||
treasure: '<svg viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linejoin="round"><path d="M8 30h48v24H8z"/><path d="M8 30c0-11 9-17 24-17s24 6 24 17"/><path d="M8 38h48"/><rect x="27.5" y="34" width="9" height="10" rx="1.5"/><path d="M20 16v14M44 16v14"/></svg>',
|
||||
mountain: '<svg viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linejoin="round"><path d="M3 54 24 18l11 18 7-10 19 28z"/><path d="M18 28l6 5 5-5"/></svg>',
|
||||
sea: '<svg viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><path d="M4 24c5-5 10-5 15 0s10 5 15 0 10-5 15 0 10 5 11 2"/><path d="M4 36c5-5 10-5 15 0s10 5 15 0 10-5 15 0 10 5 11 2"/><path d="M4 48c5-5 10-5 15 0s10 5 15 0 10-5 15 0 10 5 11 2"/></svg>',
|
||||
}
|
||||
|
||||
const esc = (v) => String(v ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]))
|
||||
|
||||
/**
|
||||
|
|
@ -87,8 +97,10 @@ export function buildBlessPosterHtml(groups, presetId = DEFAULT_PRESET, texts =
|
|||
const P = plan.preset, b = P.border
|
||||
const banner = texts.banner || POSTER.banner
|
||||
const subtitle = texts.subtitle || ''
|
||||
const sub = texts.subBanner || POSTER.subBanner
|
||||
const footer = texts.footer || POSTER.footer
|
||||
const L = texts.labels || {}
|
||||
const lab = (k, en) => L[k] || en
|
||||
const dir = texts.dir === 'rtl' ? 'rtl' : 'ltr'
|
||||
const upright = /^(zh|ja|ko)/.test(String(texts.lang || '')) // CJK sidebar labels stand upright
|
||||
const glueHere = texts.glueHere || 'glue here'
|
||||
const f = (v) => v.toFixed(3) + 'in'
|
||||
const vPt = plan.text.versePt, nPt = plan.text.namePt
|
||||
|
|
@ -103,12 +115,28 @@ export function buildBlessPosterHtml(groups, presetId = DEFAULT_PRESET, texts =
|
|||
const pages = plan.bands.map((bd) => {
|
||||
const s = bd.s, last = s === plan.bands.length - 1
|
||||
const glue = s > 0 ? `<div class="glue" style="top:0;height:${f(b)}">${esc(glueHere)} ▲ ${s} / ${P.sheets}</div>` : ''
|
||||
// THE STORY, top to bottom: in Jesus' name → three prayer themes (OT + NT) → every people group →
|
||||
// the priestly blessing → the priesthood of believers → the closing line, at the foundation.
|
||||
const cell = (v) => `<p class="tv">${esc(v.text)}</p><p class="tr">${esc(v.ref)}</p>`
|
||||
const table = `<div class="themes" dir="${dir}">`
|
||||
+ `<div class="rl rl-h"><span>${esc(lab('themesLabel', 'Prayer themes'))}</span></div>`
|
||||
+ THEMES.map((t) => `<div class="th"><div class="ic">${ICONS[t.id]}</div><div class="tn">${esc(lab('theme_' + t.id, t.id))}</div></div>`).join('')
|
||||
+ `<div class="rl"><span>${esc(lab('oldTestament', 'Old Testament'))}</span></div>`
|
||||
+ THEMES.map((t) => `<div class="td">${cell(t.ot)}</div>`).join('')
|
||||
+ `<div class="rl"><span>${esc(lab('newTestament', 'New Testament'))}</span></div>`
|
||||
+ THEMES.map((t) => `<div class="td">${cell(t.nt)}</div>`).join('')
|
||||
+ '</div>'
|
||||
const head = bd.bannerY != null
|
||||
? `<header class="ban" style="top:${f(bd.bannerY)};height:${f(plan.bannerH)}"><h1>${esc(banner)}</h1>`
|
||||
+ (subtitle ? `<p class="subt">${esc(subtitle)}</p>` : '')
|
||||
+ `<p class="sb">${esc(sub.text)} <span>${esc(sub.ref)}</span></p></header>` : ''
|
||||
? `<header class="ban" dir="${dir}" style="top:${f(bd.bannerY)};height:${f(plan.bannerH)}">`
|
||||
+ `<div class="hd" style="height:${f(plan.headH)}"><h1>${esc(banner)}</h1>`
|
||||
+ (subtitle ? `<p class="subt">${esc(subtitle)}</p>` : '') + `</div>`
|
||||
+ `<div style="height:${f(plan.tableH)}">${table}</div></header>` : ''
|
||||
const foot = bd.footerY != null
|
||||
? `<footer class="ftr" style="top:${f(bd.footerY)};height:${f(plan.footerH)}"><p>${esc(footer.text)} <span>${esc(footer.ref)}</span></p></footer>` : ''
|
||||
? `<footer class="ftr" dir="${dir}" style="top:${f(bd.footerY)};height:${f(plan.footerH)}">`
|
||||
+ `<div class="bless">${POSTER.blessing.lines.map((l) => `<p>${esc(l)}</p>`).join('')}<p class="br">${esc(POSTER.blessing.ref)}</p></div>`
|
||||
+ `<div class="priest">${POSTER.priesthood.map((v) => `<p>${esc(v.text)} <span>${esc(v.ref)}</span></p>`).join('')}</div>`
|
||||
+ (L.closing ? `<p class="close">${esc(L.closing)}</p>` : '')
|
||||
+ `</footer>` : ''
|
||||
const trim = !last && P.sheets > 1 ? '<div class="trim"></div>' : ''
|
||||
return `<section class="sheet">${glue}${head}${plan.cards.filter((c) => c.s === s).map(card).join('')}${foot}${trim}`
|
||||
+ `<div class="tag">${s + 1} / ${P.sheets}</div></section>`
|
||||
|
|
@ -118,18 +146,32 @@ export function buildBlessPosterHtml(groups, presetId = DEFAULT_PRESET, texts =
|
|||
*{box-sizing:border-box}html,body{margin:0;padding:0}
|
||||
body{font-family:Georgia,'Times New Roman',serif;color:#1c1c1c;-webkit-print-color-adjust:exact;print-color-adjust:exact}
|
||||
.sheet{position:relative;width:${P.sheetW}in;height:${P.sheetH}in;overflow:hidden;page-break-after:always;break-after:page}
|
||||
.ban{position:absolute;left:${f(b)};width:${f(W)};text-align:center;display:flex;flex-direction:column;justify-content:center}
|
||||
.ban h1{margin:0;font-size:${f(plan.bannerH * 0.42)};line-height:1.05;font-weight:700}
|
||||
.ban .subt{margin:${f(plan.bannerH * 0.04)} auto 0;max-width:80%;font:${f(plan.bannerH * 0.075)}/1.3 -apple-system,'Segoe UI',Arial,sans-serif;color:#333}
|
||||
.ban .sb{margin:${f(plan.bannerH * 0.04)} 0 0;font-size:${f(plan.bannerH * 0.09)};font-style:italic;color:#444}
|
||||
.ban .sb span,.ftr span{font-style:normal;font-size:.7em;color:#777}
|
||||
.ban{position:absolute;left:${f(b)};width:${f(W)};text-align:center}
|
||||
.hd{display:flex;flex-direction:column;justify-content:center;align-items:center}
|
||||
.hd h1{margin:0;font-size:${f(plan.headH * 0.44)};line-height:1.05;font-weight:700}
|
||||
.hd .subt{margin:${f(plan.headH * 0.06)} auto 0;max-width:78%;font:${f(plan.headH * 0.085)}/1.35 -apple-system,'Segoe UI',Arial,sans-serif;color:#333}
|
||||
.themes{display:grid;grid-template-columns:${f(W * 0.06)} repeat(3,1fr);grid-template-rows:34% 33% 33%;height:100%;border:.03in solid #1c1c1c;border-radius:${f(W * 0.006)};overflow:hidden}
|
||||
.themes > div{border:.015in solid #cfcfcf;padding:${f(W * 0.006)} ${f(W * 0.008)};overflow:hidden}
|
||||
.rl{display:flex;align-items:center;justify-content:center;background:#1c1c1c;color:#fff}
|
||||
.rl span{writing-mode:vertical-rl;${upright ? 'text-orientation:upright;' : 'transform:rotate(180deg);'}font:700 ${f(W * 0.0105)} -apple-system,'Segoe UI',Arial,sans-serif;letter-spacing:.06em;text-transform:uppercase;white-space:nowrap}
|
||||
.th{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:${f(W * 0.004)};background:#faf8f2}
|
||||
.th .ic{width:${f(W * 0.05)};height:${f(W * 0.05)};color:#6b4f1d}.th .ic svg{width:100%;height:100%}
|
||||
.th .tn{font:700 ${f(W * 0.014)} Georgia,serif;letter-spacing:.04em;text-transform:uppercase}
|
||||
.td{display:flex;flex-direction:column;justify-content:center;text-align:center}
|
||||
.td .tv{margin:0;font-size:${f(W * 0.0078)};line-height:1.3;font-style:italic}
|
||||
.td .tr{margin:${f(W * 0.002)} 0 0;font-size:${f(W * 0.0062)};color:#666}
|
||||
.ftr{position:absolute;left:${f(b)};width:${f(W)};display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;border-top:.03in solid #1c1c1c}
|
||||
.bless p{margin:0;font-size:${f(W * 0.0145)};line-height:1.25;font-weight:600}
|
||||
.bless .br{font-size:${f(W * 0.0075)};font-weight:400;color:#666;margin-top:${f(W * 0.002)}}
|
||||
.priest{margin-top:${f(W * 0.008)};max-width:88%}.priest p{margin:${f(W * 0.002)} 0;font-size:${f(W * 0.0068)};font-style:italic;line-height:1.3}
|
||||
.priest span{font-style:normal;color:#666;font-size:.8em}
|
||||
.close{margin:${f(W * 0.008)} auto 0;max-width:80%;font:700 ${f(W * 0.0085)}/1.35 -apple-system,'Segoe UI',Arial,sans-serif}
|
||||
.card{position:absolute;padding:${f(plan.cw * 0.035)};display:flex;flex-direction:column;align-items:center;text-align:center;overflow:hidden}
|
||||
.card .nm{font:700 ${nPt}pt/1.1 -apple-system,'Segoe UI',Arial,sans-serif;width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:none}
|
||||
.card img,.card .noimg{width:100%;height:${plan.text.readable ? 52 : 78}%;object-fit:cover;margin:${f(plan.cw * 0.02)} 0;background:#eee;flex:none}
|
||||
.card .vs{font-size:${vPt}pt;line-height:1.15;font-style:italic}
|
||||
.card .rf{font-size:${(vPt * 0.8).toFixed(1)}pt;color:#777;margin-top:1pt}
|
||||
.ftr{position:absolute;left:${f(b)};width:${f(W)};display:flex;align-items:center;justify-content:center;text-align:center}
|
||||
.ftr p{margin:0;font-size:${f(plan.footerH * 0.32)};font-style:italic}
|
||||
.glue{position:absolute;left:0;width:100%;border-bottom:.02in dashed #b00;display:flex;align-items:center;justify-content:center;font:700 ${f(Math.min(0.5, b * 0.08))} sans-serif;color:#b00;background:rgba(176,0,0,.035);letter-spacing:.05em;text-transform:uppercase}
|
||||
.trim{position:absolute;left:0;bottom:0;width:100%;border-bottom:.02in solid #999}
|
||||
.tag{position:absolute;right:${f(Math.max(0.15, b * 0.1))};top:${f(Math.max(0.1, b * 0.08))};font:600 ${f(Math.max(0.12, b * 0.035))} sans-serif;color:#aaa}`
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* SOURCE: the World English Bible, PUBLIC DOMAIN ("The World English Bible is in the Public Domain. You
|
||||
* may copy and share it freely." — ebible.org/eng-web). Every text below was extracted mechanically
|
||||
* from ebible.org/eng-web chapter pages (2026-09-28), never retyped or paraphrased. A card quotes a
|
||||
* whole verse, or one whole sentence of it (marked "b" in the reference), always with its reference.
|
||||
* whole verse, or one whole sentence of it (marked "a" = the first sentence, "b" = the last, in the reference); cards hold at most 4 lines, so longer verses are quoted by one sentence, always with its reference.
|
||||
*
|
||||
* THE DIVINE NAME IS ONE SWITCH. The WEB prints "Yahweh"; the WEB British Edition (also public domain)
|
||||
* prints "the LORD" / "The LORD" in the same places. Texts carry {NAME} (mid-sentence) and {Name}
|
||||
|
|
@ -26,11 +26,10 @@ const VERSES = [
|
|||
{ ref: 'Psalm 115:15', text: 'Blessed are you by {NAME}, who made heaven and earth.' },
|
||||
{ ref: 'Psalm 29:11', text: '{Name} will give strength to his people. {Name} will bless his people with peace.' },
|
||||
{ ref: 'Psalm 121:7', text: '{Name} will keep you from all evil. He will keep your soul.' },
|
||||
{ ref: 'Psalm 121:8', text: '{Name} will keep your going out and your coming in, from this time forward, and forever more.' },
|
||||
{ ref: 'Psalm 67:1', text: 'May God be merciful to us, bless us, and cause his face to shine on us.' },
|
||||
{ ref: 'Psalm 67:7', text: 'God will bless us. All the ends of the earth shall fear him.' },
|
||||
{ ref: 'Psalm 22:27', text: 'All the ends of the earth shall remember and turn to {NAME}. All the relatives of the nations shall worship before you.' },
|
||||
{ ref: 'Psalm 86:9', text: 'All nations you have made will come and worship before you, Lord. They shall glorify your name.' },
|
||||
{ ref: 'Psalm 22:27a', text: 'All the ends of the earth shall remember and turn to {NAME}.' },
|
||||
{ ref: 'Psalm 86:9a', text: 'All nations you have made will come and worship before you, Lord.' },
|
||||
{ ref: 'Psalm 117:1', text: 'Praise {NAME}, all you nations! Extol him, all you peoples!' },
|
||||
{ ref: 'Psalm 72:17b', text: 'All nations will call him blessed.' },
|
||||
{ ref: 'Genesis 12:3b', text: 'All the families of the earth will be blessed through you.' },
|
||||
|
|
@ -43,14 +42,45 @@ export function blessingFor(i) {
|
|||
}
|
||||
export const BLESSING_COUNT = VERSES.length
|
||||
|
||||
// The poster. The banner is the Driver's own title; the footer verse is his DECIDE (sheet row 99):
|
||||
// Proverbs 11:11 is recommended (the blessing verse; it says "city"), and Proverbs 14:34 is the
|
||||
// alternative (the "nation" verse, about righteousness).
|
||||
// THE POSTER, top to bottom as the Driver designed it (2026-09-28): the title "Bless the nations in
|
||||
// Jesus' name", a short line on what DOXA means, a table of THREE PRAYER THEMES (treasure, mountain, sea),
|
||||
// each with an Old Testament and a New Testament verse, then every people group, then the FOOTER: the
|
||||
// priestly blessing (Numbers 6:24-26, the blessing words only) and the priesthood of believers (1 Peter 2).
|
||||
// Verses are WEB, verbatim; {NAME}/{Name} follow DIVINE_NAME. The sea's New Testament verse (Matthew
|
||||
// 13:47) is the agent's proposal, pending the Driver (Mark 11:23 is the alternative).
|
||||
const THEMES_RAW = [
|
||||
{ id: 'treasure',
|
||||
ot: { ref: 'Haggai 2:7', text: 'and I will shake all nations. The treasure of all nations will come, and I will fill this house with glory, says {Name} of Armies.' },
|
||||
nt: { ref: 'Matthew 13:44', text: 'Again, the Kingdom of Heaven is like treasure hidden in the field, which a man found and hid. In his joy, he goes and sells all that he has and buys that field.' } },
|
||||
{ id: 'mountain',
|
||||
ot: { ref: 'Isaiah 2:2', text: 'It shall happen in the latter days, that the mountain of {NAME}’s house shall be established on the top of the mountains, and shall be raised above the hills; and all nations shall flow to it.' },
|
||||
nt: { ref: 'Matthew 21:21', text: 'Jesus answered them, “Most certainly I tell you, if you have faith and don’t doubt, you will not only do what was done to the fig tree, but even if you told this mountain, ‘Be taken up and cast into the sea,’ it would be done.' } },
|
||||
{ id: 'sea',
|
||||
ot: { ref: 'Habakkuk 2:14', text: 'For the earth will be filled with the knowledge of {NAME}’s glory, as the waters cover the sea.' },
|
||||
nt: { ref: 'Matthew 13:47', text: 'Again, the Kingdom of Heaven is like a dragnet that was cast into the sea and gathered some fish of every kind,' } },
|
||||
]
|
||||
const verse = (v) => ({ ref: v.ref, text: withName(v.text) })
|
||||
export const THEMES = THEMES_RAW.map((t) => ({ id: t.id, ot: verse(t.ot), nt: verse(t.nt) }))
|
||||
|
||||
export const POSTER = {
|
||||
banner: 'Bless the nations in Jesus’ name',
|
||||
// The large footer: the priestly blessing, the blessing words alone (Numbers 6:24-26).
|
||||
blessing: {
|
||||
ref: 'Numbers 6:24–26',
|
||||
lines: [
|
||||
withName('{Name} bless you, and keep you.'),
|
||||
withName('{Name} make his face to shine on you, and be gracious to you.'),
|
||||
withName('{Name} lift up his face toward you, and give you peace.'),
|
||||
],
|
||||
},
|
||||
// The second footer: the priesthood of believers.
|
||||
priesthood: [
|
||||
{ ref: '1 Peter 2:5', text: 'You also as living stones are built up as a spiritual house, to be a holy priesthood, to offer up spiritual sacrifices, acceptable to God through Jesus Christ.' },
|
||||
{ ref: '1 Peter 2:9', text: 'But you are a chosen race, a royal priesthood, a holy nation, a people for God’s own possession, that you may proclaim the excellence of him who called you out of darkness into his marvelous light.' },
|
||||
],
|
||||
// Kept for reference: the earlier footer candidates (sheet rows 99), superseded by the Driver's choice.
|
||||
subBanner: { ref: 'Proverbs 18:21', text: 'Death and life are in the power of the tongue; those who love it will eat its fruit.' },
|
||||
footer: { ref: 'Proverbs 11:11', text: 'By the blessing of the upright, the city is exalted, but it is overthrown by the mouth of the wicked.' },
|
||||
footerAlt: { ref: 'Proverbs 14:34', text: 'Righteousness exalts a nation, but sin is a disgrace to any people.' },
|
||||
}
|
||||
|
||||
/** Greedy word wrap to at most maxLines lines of ~perLine characters (for SVG/canvas text). */
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@
|
|||
<script>
|
||||
import { WallCanvas } from '../wallCanvas.js'
|
||||
import { buildBlessPosterHtml, PRESETS, DEFAULT_PRESET } from '../blessPoster.js'
|
||||
import { POSTER } from '../blessings.js'
|
||||
import { openPrintFrame } from '@map/composables/printInPlace.js'
|
||||
// Wall of People Groups: a zoomable canvas wall (wallCanvas.js), Bless or Social cards, and the poster print.
|
||||
export default {
|
||||
|
|
@ -149,7 +148,18 @@ export default {
|
|||
banner: this.$t('wall.blessBanner'),
|
||||
subtitle: this.$t('wall.posterSubtitle', { n: groups.length.toLocaleString('en-US') }),
|
||||
glueHere: this.$t('wall.glueHere'),
|
||||
subBanner: POSTER.subBanner, footer: POSTER.footer,
|
||||
// The prayer-themes chart and the closing line, in the reader's language; the verses stay WEB.
|
||||
labels: {
|
||||
themesLabel: this.$t('wall.themesLabel'),
|
||||
oldTestament: this.$t('wall.oldTestament'),
|
||||
newTestament: this.$t('wall.newTestament'),
|
||||
theme_treasure: this.$t('wall.themeTreasure'),
|
||||
theme_mountain: this.$t('wall.themeMountain'),
|
||||
theme_sea: this.$t('wall.themeSea'),
|
||||
closing: this.$t('wall.posterClosing'),
|
||||
},
|
||||
lang: String(this.$i18n?.locale || 'en'),
|
||||
dir: /^(ar|he|fa|ur)/.test(String(this.$i18n?.locale || '')) ? 'rtl' : 'ltr',
|
||||
};
|
||||
const win = openPrintFrame(buildBlessPosterHtml(groups, this.posterPreset, texts), { autoprint: true });
|
||||
// Busy until the photos have loaded and the print dialog is up (the frame prints on its own 'load').
|
||||
|
|
|
|||
Loading…
Reference in a new issue