Merge remote-tracking branch 'origin/development' into regions-plugin/view-address
This commit is contained in:
commit
da14272f96
22 changed files with 258 additions and 370 deletions
4
library/i18n/locales/ar/wallSocial.json
Normal file
4
library/i18n/locales/ar/wallSocial.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"signUpToPray": "سجّل للصلاة من أجل {name}",
|
||||
"commentsSoon": "التعليقات قادمة قريبًا."
|
||||
}
|
||||
4
library/i18n/locales/de/wallSocial.json
Normal file
4
library/i18n/locales/de/wallSocial.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"signUpToPray": "Melden Sie sich an, um für {name} zu beten",
|
||||
"commentsSoon": "Kommentare folgen in Kürze."
|
||||
}
|
||||
4
library/i18n/locales/en/wallSocial.json
Normal file
4
library/i18n/locales/en/wallSocial.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"signUpToPray": "Sign up to pray for {name}",
|
||||
"commentsSoon": "Comments are coming soon."
|
||||
}
|
||||
4
library/i18n/locales/es/wallSocial.json
Normal file
4
library/i18n/locales/es/wallSocial.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"signUpToPray": "Inscríbase para orar por {name}",
|
||||
"commentsSoon": "Los comentarios llegarán pronto."
|
||||
}
|
||||
4
library/i18n/locales/fr/wallSocial.json
Normal file
4
library/i18n/locales/fr/wallSocial.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"signUpToPray": "Inscrivez-vous pour prier pour {name}",
|
||||
"commentsSoon": "Les commentaires arrivent bientôt."
|
||||
}
|
||||
4
library/i18n/locales/hi/wallSocial.json
Normal file
4
library/i18n/locales/hi/wallSocial.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"signUpToPray": "{name} के लिए प्रार्थना करने हेतु पंजीकरण करें",
|
||||
"commentsSoon": "टिप्पणियाँ जल्द ही आ रही हैं।"
|
||||
}
|
||||
4
library/i18n/locales/it/wallSocial.json
Normal file
4
library/i18n/locales/it/wallSocial.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"signUpToPray": "Iscriviti per pregare per {name}",
|
||||
"commentsSoon": "I commenti arriveranno presto."
|
||||
}
|
||||
4
library/i18n/locales/pt/wallSocial.json
Normal file
4
library/i18n/locales/pt/wallSocial.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"signUpToPray": "Inscreva-se para orar por {name}",
|
||||
"commentsSoon": "Os comentários chegarão em breve."
|
||||
}
|
||||
4
library/i18n/locales/ro/wallSocial.json
Normal file
4
library/i18n/locales/ro/wallSocial.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"signUpToPray": "Înscrieți-vă să vă rugați pentru {name}",
|
||||
"commentsSoon": "Comentariile vor fi disponibile în curând."
|
||||
}
|
||||
4
library/i18n/locales/ru/wallSocial.json
Normal file
4
library/i18n/locales/ru/wallSocial.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"signUpToPray": "Запишитесь, чтобы молиться за {name}",
|
||||
"commentsSoon": "Комментарии скоро появятся."
|
||||
}
|
||||
4
library/i18n/locales/zh/wallSocial.json
Normal file
4
library/i18n/locales/zh/wallSocial.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"signUpToPray": "报名为{name}祷告",
|
||||
"commentsSoon": "评论功能即将推出。"
|
||||
}
|
||||
|
|
@ -55,12 +55,12 @@ export function planPoster(presetId, n) {
|
|||
const P = PRESETS[presetId] || PRESETS[DEFAULT_PRESET]
|
||||
const b = P.border
|
||||
const W = P.sheetW - 2 * b
|
||||
// Top of sheet 1: the title + DOXA line, then the prayer-themes table. Bottom of the last sheet: the
|
||||
// priestly blessing, the journey from Abraham to the church and the closing line. All scale with the width.
|
||||
// Top of sheet 1: the title and the DOXA line, then the prayer-themes table. Bottom of the last sheet: the
|
||||
// call to action (a ruled band), the journey's table, a rule, and the benediction (Numbers 6:24-26). All scale with the width.
|
||||
// gapH: air between the chart and the first row of cards, and between the last row and the footer (the grid
|
||||
// used to butt against the chart's border).
|
||||
const headH = W * 0.075, tableH = W * 0.17, gapH = W * 0.012
|
||||
const bannerH = headH + tableH + gapH, footerH = footerHeight(W)
|
||||
const titleH = W * 0.075, headH = titleH, tableH = W * 0.17, gapH = W * 0.012
|
||||
const bannerH = headH + tableH + gapH, footerH = footerHeight(W, BENEDICTION_FILL[PRESETS[presetId] ? presetId : DEFAULT_PRESET] ?? 1)
|
||||
// Each sheet's content band, in the sheet's own coordinates: y0..y1. The panels give each sheet's content box:
|
||||
// sheet 1 starts below its outer border, the others below their glue band; only the last has a bottom border
|
||||
// (the others' bottom edge is trimmed flat and glued over the next sheet's band).
|
||||
|
|
@ -84,16 +84,18 @@ export function planPoster(presetId, n) {
|
|||
const stripLayout = versePtOf(grid.cw) < MIN_READABLE_PT
|
||||
if (stripLayout) grid = fit((STRIP_PT * 1.35 + 4) * PT) // one line at STRIP_PT, plus air
|
||||
const { cols, cw, ch, rowsPer, strip } = grid
|
||||
// Place cards band by band, row by row; in the strip layout each row's strip sits under it. The rows a band
|
||||
// actually uses are centred in it, so its spare height is shared equally above and below the grid (it used
|
||||
// to pool as one wide gap above the footer).
|
||||
// Place cards band by band, row by row; in the strip layout each row's strip sits under it.
|
||||
const cards = [], strips = []
|
||||
let i = 0, row = 0
|
||||
for (let s = 0; s < bands.length && i < n; s++) {
|
||||
// The spare height goes BETWEEN the rows first (up to a quarter of a row each), so the photos spread to
|
||||
// fill the sheet, and what remains is shared equally above and below.
|
||||
const used = Math.min(rowsPer[s], Math.ceil((n - i) / cols))
|
||||
const top = bands[s].y0 + Math.max(0, (bands[s].y1 - bands[s].y0) - used * (ch + strip)) / 2
|
||||
const slack = Math.max(0, (bands[s].y1 - bands[s].y0) - used * (ch + strip))
|
||||
const rowGap = used > 1 ? Math.min(slack / (used + 1), (ch + strip) * 0.25) : 0
|
||||
const top = bands[s].y0 + (slack - rowGap * (used - 1)) / 2
|
||||
for (let r = 0; r < rowsPer[s] && i < n; r++, row++) {
|
||||
const y = top + r * (ch + strip)
|
||||
const y = top + r * (ch + strip + rowGap)
|
||||
for (let c = 0; c < cols && i < n; c++, i++) cards.push({ i, s, x: b + c * cw, y, w: cw, h: ch })
|
||||
if (strip) strips.push({ row, s, x: b, y: y + ch, w: W, h: strip })
|
||||
}
|
||||
|
|
@ -102,39 +104,54 @@ export function planPoster(presetId, n) {
|
|||
return {
|
||||
preset: P, presetId: PRESETS[presetId] ? presetId : DEFAULT_PRESET, n, cols, rowsPer, cw, ch, bands, cards,
|
||||
layout: stripLayout ? 'strip' : 'cards', strips,
|
||||
bannerH, footerH, headH, tableH, gapH, contentW: W,
|
||||
bannerH, footerH, headH, titleH, tableH, gapH, contentW: W,
|
||||
posterW: panels.finished.w, posterH: panels.finished.h, // after gluing: 60 × 96 for 5x8ft
|
||||
text: { versePt: +versePt.toFixed(1), namePt: +(versePt * 1.25).toFixed(1), readable: !stripLayout, stripPt: STRIP_PT },
|
||||
}
|
||||
}
|
||||
|
||||
// The footer's height at content width W (inches), FROM ITS TEXT: the priestly blessing (the small
|
||||
// instruction, three large lines, the small promise), the journey's seven columns (as tall as its longest
|
||||
// passage wraps), and the closing line. Characters are counted at ≈ 0.5 em each, with 12% slack for
|
||||
// word breaks; the footer is never shallower than a fifth of the width. The same font sizes as the CSS.
|
||||
// The footer's height at content width W (inches), FROM ITS TEXT: the journey's seven columns (as tall as
|
||||
// its longest passage wraps), the rule, the benediction's two lines (sized to fill the width), its reference
|
||||
// and the scripture credit. Characters are counted at ≈ 0.5 em each, with 12% slack for word breaks. The
|
||||
// same font sizes as the CSS.
|
||||
// The journey's passages are the long text: never below 6 pt (2 × 3 ft), ~12 pt on the 5 × 8 ft poster, which
|
||||
// keeps the 5 × 8's verse under every card at 6 pt (a larger footer would shrink the cards below it).
|
||||
// ONE inner padding for every block (header, chart cells, footer), so all the text shares the same left and
|
||||
// right edges across the full content width.
|
||||
const padOf = (W) => W * 0.008
|
||||
export const FOOTER_FONTS = (W) => ({ small: W * 0.0065, line: W * 0.012, ref: W * 0.0075, name: W * 0.0062, station: Math.max(6 / 72, W * 0.0034), close: W * 0.0085 })
|
||||
function footerHeight(W) {
|
||||
const F = FOOTER_FONTS(W), gap = W * 0.006
|
||||
// THE BENEDICTION'S WIDTH, per size: the share of the footer's text width its longer clause fills. 1 = the full
|
||||
// width (the brief's "two lines, large, filling the footer width"). The trade-off at 5 × 8 ft, measured with
|
||||
// 2,107 groups: at 1 the footer is 17.3 in deep and the cards 0.87 in, one verse per ROW; at 0.3 the cards
|
||||
// keep a verse UNDER EVERY CARD (1.04 in). Change a size's number here to choose; nothing else needs to move.
|
||||
export const BENEDICTION_FILL = { '3x4ft': 1, '2x3ft': 1, '5x8ft': 1 }
|
||||
// Georgia bold italic, em per character of the longer line, MEASURED on the rendered print of these two lines:
|
||||
// 0.61 filled 0.911 of the width, so ≈ 0.555; 0.56 fills ~0.99 and leaves a hair of the padding clear.
|
||||
const BENEDICTION_EM = 0.56
|
||||
// MEASURED: at 0.47 the English line needed 1.057 of its band (it wrapped), so ≈ 0.50; 0.51 keeps it on one line
|
||||
const CTA_EM = 0.51
|
||||
export const FOOTER_FONTS = (W, fill = 1) => ({
|
||||
ref: W * 0.0075, name: W * 0.0062, station: Math.max(6 / 72, W * 0.0034), credit: Math.max(6 / 72, W * 0.0042),
|
||||
// the call to action fills its band on one line in English (CTA_EM, Georgia italic); a longer
|
||||
// translation wraps to a second line, which footerHeight() budgets for
|
||||
cta: (W - 2 * padOf(W)) / (CLOSING_EN.length * CTA_EM),
|
||||
verse: (fill * (W - 2 * padOf(W))) / (Math.max(...POSTER.benediction.lines.map((l) => l.length)) * BENEDICTION_EM),
|
||||
})
|
||||
function footerHeight(W, fill) {
|
||||
const F = FOOTER_FONTS(W, fill), gap = W * 0.006
|
||||
const lines = (text, width, fs) => Math.max(1, Math.ceil((String(text).length * 0.5 * fs * 1.12) / width))
|
||||
const B = POSTER.blessing
|
||||
const blessH = [...B.intro, ...B.outro].reduce((a, t) => a + lines(t, W - 2 * padOf(W), F.small) * F.small * 1.35, 0)
|
||||
+ B.lines.reduce((a, t) => a + lines(t, W, F.line) * F.line * 1.25, 0) + F.ref * 1.6
|
||||
const colW = (W - 6 * gap) / 7 - W * 0.008
|
||||
const stationH = Math.max(...POSTER.journey.map((v) => lines(v.text, colW, F.station) * F.station * 1.3)) + F.name * 1.6 + F.ref * 1.6 + W * 0.006
|
||||
// the closing line's English sizes its row (its translations run within a few words of it); CLOSING_EN is
|
||||
// declared below, which is fine: this runs when a poster is planned, long after the module has loaded
|
||||
const closeH = lines(CLOSING_EN, W - 2 * padOf(W), F.close) * F.close * 1.35
|
||||
return Math.max(W * 0.2, (blessH + stationH + closeH + W * 0.03) * 1.05)
|
||||
// the call to action's band (two lines budgeted), the seven stations, the rule, the benediction's two lines,
|
||||
// its reference and the credit line
|
||||
const ctaH = 2 * F.cta * 1.3 + W * 0.02
|
||||
const verseH = POSTER.benediction.lines.length * F.verse * 1.12 + F.ref * 1.8 + F.credit * 1.6
|
||||
return (ctaH + stationH + verseH + W * 0.04) * 1.05
|
||||
}
|
||||
|
||||
// The closing line under the footer (agent-written); translated via texts.labels.closing when available.
|
||||
const CLOSING_EN = 'Jesus Christ is the cornerstone, and we are living stones. These people groups are the reward of his suffering. Take up our priestly calling: bless them in his name, and join the global family in prayer.'
|
||||
// The journey's station names (agent-written); translated via texts.labels.journey_<id> when available.
|
||||
// The call to action above the journey (agent-written; translated via texts.labels.closing): the poster's one line
|
||||
// of its own words, in a ruled band as the UPG 100 List posters set theirs, while the footer itself is scripture.
|
||||
const CLOSING_EN = 'Jesus Christ is the cornerstone, and we are living stones. These people groups are the reward of his suffering. Take up our priestly calling: bless them in his name, and join the global family in prayer.'
|
||||
const JOURNEY_EN = { called: 'Called', promised: 'Promised', redeemed: 'Redeemed', cornerstone: 'The cornerstone', children: 'Children of Abraham', priesthood: 'A royal priesthood', sent: 'Sent to the lost' }
|
||||
const THEME_EN = { treasure: 'Treasure', mountain: 'Mountain', sea: 'Sea' }
|
||||
// Three simple line icons for the prayer themes (stroke = currentColor).
|
||||
|
|
@ -231,7 +248,7 @@ function posterParts(groups, presetId, texts, withCards = true) {
|
|||
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 W = plan.contentW, FF = FOOTER_FONTS(W) // the footer's sizes: the same numbers footerHeight() plans with
|
||||
const W = plan.contentW, FF = FOOTER_FONTS(W, BENEDICTION_FILL[plan.presetId] ?? 1) // the footer's sizes: the same numbers footerHeight() plans with
|
||||
const vPt = plan.text.versePt, rPt = cardText(plan, '').refPt
|
||||
// The rotated sidebar labels of the chart are sized from their text: the longest word must fit the row's
|
||||
// height (a CJK label stands upright, one em a character; other scripts ≈ 0.84 em a capital: 0.72 for the
|
||||
|
|
@ -268,10 +285,10 @@ function posterParts(groups, presetId, texts, withCards = true) {
|
|||
const s = bd.s
|
||||
const marks = panelChromeHtml(panels, s, { glueHere }) // the glue band (before), the trim line + s / N tag (after)
|
||||
// THE STORY, top to bottom: in Jesus' name → three prayer themes (OT + NT) → every people group →
|
||||
// the priestly blessing → the calling to bless, Abraham to the church with Christ at its centre →
|
||||
// the closing line, at the foundation. Each station's text takes dir="auto": the grid runs right to left
|
||||
// in an RTL locale, while an English verse inside it keeps its punctuation at the end. The same holds for
|
||||
// every scripture, name and reference on the sheet (cards, chart, blessing, closing).
|
||||
// the calling to bless, Abraham to the church with Christ at its centre → the benediction, at the
|
||||
// foundation. Each station's text takes dir="auto": the grid runs right to left in an RTL locale, while an
|
||||
// English verse inside it keeps its punctuation at the end. The same holds for every scripture, name and
|
||||
// reference on the sheet; the benediction and its credit are set left to right, as the English they are.
|
||||
const cell = (v) => `<p class="tv" dir="auto">${esc(v.text)}</p><p class="tr" dir="auto">${esc(v.ref)}</p>`
|
||||
const table = `<div class="themes" dir="${dir}">`
|
||||
+ `<div class="rl rl-h">${railLabel(lab('themesLabel', 'Prayer themes'))}</div>`
|
||||
|
|
@ -285,14 +302,15 @@ function posterParts(groups, presetId, texts, withCards = true) {
|
|||
? `<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>` : ''
|
||||
+ `<div class="tblw" style="height:${f(plan.tableH)}">${table}</div></header>` : ''
|
||||
const foot = bd.footerY != null
|
||||
? `<footer class="ftr" dir="${dir}" style="top:${f(bd.footerY)};height:${f(plan.footerH)}">`
|
||||
+ `<div class="bless">${POSTER.blessing.intro.map((l) => `<p class="bi" dir="auto">${esc(l)}</p>`).join('')}`
|
||||
+ `${POSTER.blessing.lines.map((l) => `<p dir="auto">${esc(l)}</p>`).join('')}`
|
||||
+ `${POSTER.blessing.outro.map((l) => `<p class="bi" dir="auto">${esc(l)}</p>`).join('')}<p class="br" dir="auto">${esc(POSTER.blessing.ref)}</p></div>`
|
||||
+ `<div class="cta"><p class="ctal" dir="auto">${esc(lab('closing', CLOSING_EN))}</p></div>`
|
||||
+ `<div class="jny">${POSTER.journey.map((v) => `<div class="st${v.centre ? ' ctr' : ''}"><p class="sn" dir="auto">${esc(lab('journey_' + v.id, JOURNEY_EN[v.id]))}</p><p class="sv" dir="auto">${esc(v.text)}</p><p class="sr" dir="auto">${esc(v.ref)}</p></div>`).join('')}</div>`
|
||||
+ `<p class="close" dir="auto">${esc(lab('closing', CLOSING_EN))}</p>`
|
||||
+ `<div class="frule"></div>`
|
||||
+ `<p class="fv" dir="ltr" lang="en">${POSTER.benediction.lines.map(esc).join('<br>')}</p>`
|
||||
+ `<p class="fvref" dir="ltr">${esc(POSTER.benediction.ref)}</p>`
|
||||
+ `<p class="fcredit" dir="ltr" lang="en">${esc(POSTER.benediction.credit)}</p>`
|
||||
+ `</footer>` : ''
|
||||
// STRIP layout: one blessing verse per row, on a strip under the row (the same rotation as the cards').
|
||||
const strips = plan.strips.filter((t) => t.s === s).map((t) => { const v = blessingFor(t.row)
|
||||
|
|
@ -303,6 +321,9 @@ function posterParts(groups, presetId, texts, withCards = true) {
|
|||
// warm paper, rules instead of boxes, Georgia for titles and scripture, the sans for labels and names,
|
||||
// tracked capitals for the small labels, and the purple ink for references and emphasis.
|
||||
const PAD = padOf(W)
|
||||
// SECTION DIVIDERS run the page's width, as on the UPG 100 List posters: XR past the content column on each
|
||||
// side, stopping a quarter of the border short of the sheet's edge. The text stays in the content column.
|
||||
const XR = b * 0.75
|
||||
const SANS = "Lato,-apple-system,'Segoe UI',system-ui,Arial,sans-serif"
|
||||
const css = `@page{size:${P.sheetW}in ${P.sheetH}in;margin:0}
|
||||
*{box-sizing:border-box}html,body{margin:0;padding:0}
|
||||
|
|
@ -310,10 +331,13 @@ body,.poster{font-family:Georgia,'Times New Roman',serif;color:${INK};-webkit-pr
|
|||
.sheet{position:relative;width:${P.sheetW}in;height:${P.sheetH}in;overflow:hidden;page-break-after:always;break-after:page;background:${PAPER}}
|
||||
.ban{position:absolute;left:${f(b)};width:${f(W)};text-align:center}
|
||||
.hd{padding:0 ${f(PAD)}}
|
||||
.hd{display:flex;flex-direction:column;justify-content:center;align-items:center;border-bottom:.015in solid #C9BFA4}
|
||||
.hd h1{margin:0;font-size:${f(plan.headH * 0.44)};line-height:1.15;font-weight:700;letter-spacing:-.02em;color:#1c1c17}
|
||||
.hd .subt{margin:${f(plan.headH * 0.06)} 0 0;font:${f(plan.headH * 0.085)}/1.35 ${SANS};color:#7E7562;letter-spacing:.01em}
|
||||
.themes{display:grid;grid-template-columns:${f(W * 0.06)} repeat(3,1fr);grid-template-rows:34% 33% 33%;height:100%;border-bottom:.02in solid #C9BFA4;overflow:hidden}
|
||||
.hd{position:relative;display:flex;flex-direction:column;justify-content:center;align-items:center}
|
||||
.hd::after,.tblw::after,.cta::before,.cta::after{content:'';position:absolute;border-top:.015in solid #C9BFA4}
|
||||
.hd::after{left:${f(-XR)};right:${f(-XR)};bottom:0}
|
||||
.tblw{position:relative}.tblw::after{left:${f(-XR)};right:${f(-XR)};bottom:0;border-top-width:.02in}
|
||||
.hd h1{margin:0;font-size:${f(plan.titleH * 0.44)};line-height:1.15;font-weight:700;letter-spacing:-.02em;color:#1c1c17}
|
||||
.hd .subt{margin:${f(plan.titleH * 0.06)} 0 0;font:${f(plan.titleH * 0.085)}/1.35 ${SANS};color:#7E7562;letter-spacing:.01em}
|
||||
.themes{display:grid;grid-template-columns:${f(W * 0.06)} repeat(3,1fr);grid-template-rows:34% 33% 33%;height:100%;overflow:hidden}
|
||||
.themes > div{padding:${f(W * 0.006)} ${f(PAD)};overflow:hidden;border-inline-start:.01in solid #D6CCB4}
|
||||
.themes > .rl{display:flex;align-items:center;justify-content:center;color:#8a8470;padding:${f(W * 0.002)};border-inline-start:0}
|
||||
.themes > .td,.themes > .rl:not(.rl-h){border-top:.01in solid #D6CCB4}
|
||||
|
|
@ -324,19 +348,23 @@ body,.poster{font-family:Georgia,'Times New Roman',serif;color:${INK};-webkit-pr
|
|||
.td{display:flex;flex-direction:column;justify-content:center;text-align:center}
|
||||
.td .tv{margin:0;font-size:${f(tvFs)};line-height:1.3;font-style:italic;color:#3a362c}
|
||||
.td .tr{margin:${f(W * 0.002)} 0 0;font:700 ${f(W * 0.0055)} ${SANS};letter-spacing:.12em;text-transform:uppercase;color:${ACCENT}}
|
||||
.ftr{position:absolute;left:${f(b)};width:${f(W)};padding:0 ${f(PAD)};display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;border-top:.02in solid #C9BFA4}
|
||||
.bless p{margin:0;font-size:${f(FF.line)};line-height:1.25;font-weight:700;font-style:italic;color:#2A2721}
|
||||
.bless p.bi{margin:${f(W * 0.001)} 0;font-size:${f(FF.small)};line-height:1.35;font-weight:400;font-style:italic;color:#6a6450}
|
||||
.bless .br{margin-top:${f(W * 0.002)};font:700 ${f(FF.ref)} ${SANS};letter-spacing:.14em;text-transform:uppercase;color:${ACCENT};font-style:normal}
|
||||
.ftr{position:absolute;left:${f(b)};width:${f(W)};padding:0 ${f(PAD)};display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center}
|
||||
/* The call to action, in a band ruled across the page (the UPG posters' goal line), above the journey's table. */
|
||||
.cta{position:relative;width:100%;padding:${f(W * 0.009)} 0}
|
||||
.cta::before{left:${f(-PAD - XR)};right:${f(-PAD - XR)};top:0}.cta::after{left:${f(-PAD - XR)};right:${f(-PAD - XR)};bottom:0}
|
||||
.ctal{margin:0;font:italic ${f(FF.cta)}/1.3 Georgia,'Times New Roman',serif;color:#4a4636}
|
||||
/* Rules run the full content width, like the chart's; the padding belongs to the text. */
|
||||
.jny{display:grid;grid-template-columns:repeat(7,1fr);gap:${f(W * 0.006)};margin:${f(W * 0.01)} ${f(-PAD)} 0;width:${f(W)};align-items:stretch;border-top:.01in solid #D6CCB4;padding-top:${f(W * 0.004)}}
|
||||
.jny{display:grid;grid-template-columns:repeat(7,1fr);gap:${f(W * 0.006)};margin:${f(W * 0.01)} ${f(-PAD)} 0;width:${f(W)};align-items:stretch}
|
||||
.st{display:flex;flex-direction:column;justify-content:flex-start;padding:${f(W * 0.003)} ${f(W * 0.004)}}
|
||||
.st + .st{border-inline-start:.01in solid #D6CCB4}
|
||||
.st .sn{margin:0 0 ${f(W * 0.002)};font:700 ${f(FF.name)} ${SANS};letter-spacing:.08em;text-transform:uppercase;color:#8a8470}
|
||||
.st .sv{margin:0;font-size:${f(FF.station)};font-style:italic;line-height:1.3;color:#4a4636}
|
||||
.st .sr{margin:${f(W * 0.002)} 0 0;font:700 ${f(Math.max(6 / 72, W * 0.0048))} ${SANS};letter-spacing:.1em;text-transform:uppercase;color:${ACCENT}}
|
||||
.st.ctr{background:#F2EFE6;border:.02in solid #cdbf9e}.st.ctr + .st{border-inline-start:0}.st.ctr .sv{font-weight:600;color:#2A2721}.st.ctr .sn{color:${ACCENT}}
|
||||
.close{margin:${f(W * 0.008)} 0 0;font:italic 700 ${f(FF.close)}/1.35 Georgia,'Times New Roman',serif;color:#2A2721}
|
||||
.frule{width:${f(W + 2 * XR)};margin:${f(W * 0.012)} ${f(-PAD - XR)} ${f(W * 0.01)};border-top:.015in solid #C9BFA4}
|
||||
.fv{margin:0;font:italic 700 ${f(FF.verse)}/1.12 Georgia,'Times New Roman',serif;color:#2A2721;letter-spacing:-.01em;white-space:nowrap}
|
||||
.fvref{margin:${f(W * 0.004)} 0 0;font:700 ${f(FF.ref)} ${SANS};letter-spacing:.14em;text-transform:uppercase;color:${ACCENT}}
|
||||
.fcredit{margin:${f(W * 0.004)} 0 0;font:${f(FF.credit)}/1.3 ${SANS};color:#8a8470}
|
||||
.vstrip{position:absolute;display:flex;align-items:center;justify-content:center;gap:.6em;font-size:${STRIP_PT}pt;font-style:italic;
|
||||
color:#3a362c;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;border-top:.01in solid #D6CCB4}
|
||||
.vstrip .vref{font:700 .8em ${SANS};letter-spacing:.12em;text-transform:uppercase;color:${ACCENT}}
|
||||
|
|
|
|||
|
|
@ -2,17 +2,18 @@
|
|||
* blessings.js — the verses the Wall's BLESS mode prints under each people group, and the poster's
|
||||
* banner / sub-banner / footer text.
|
||||
*
|
||||
* 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
|
||||
* SOURCE: the World English Bible British Edition, PUBLIC DOMAIN (ebible.org/eng-webbe: "a Public Domain
|
||||
* translation"), which reads "the LORD" where the WEB reads "Yahweh". The texts were first extracted
|
||||
* mechanically from the WEB (ebible.org/eng-web, 2026-09-28) and moved to the British Edition on 2026-10-03:
|
||||
* every one was re-checked verbatim against bible.helloao.org eng_webpb (its British spellings, e.g.
|
||||
* "towards", "honour", "amongst"), never retyped or paraphrased. A card quotes a
|
||||
* 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}
|
||||
* (sentence start); change DIVINE_NAME below to switch every verse at once. (If switching, re-verify the
|
||||
* handful of verses against the British Edition text: it also uses British spelling.)
|
||||
* THE DIVINE NAME: texts carry {NAME} (mid-sentence) and {Name} (sentence start), filled from DIVINE_NAME. The
|
||||
* poster uses "the LORD" (the British Edition's reading, and the one the brief prefers). Going back to the WEB's
|
||||
* "Yahweh" means switching the texts' spellings back too, verse by verse against the WEB.
|
||||
*/
|
||||
export const DIVINE_NAME = { mid: 'Yahweh', start: 'Yahweh' } // British Edition: { mid: 'the LORD', start: 'The LORD' }
|
||||
export const DIVINE_NAME = { mid: 'the LORD', start: 'The LORD' }
|
||||
|
||||
const withName = (s) => s.replace(/\{Name\}/g, DIVINE_NAME.start).replace(/\{NAME\}/g, DIVINE_NAME.mid)
|
||||
|
||||
|
|
@ -21,7 +22,7 @@ const withName = (s) => s.replace(/\{Name\}/g, DIVINE_NAME.start).replace(/\{NAM
|
|||
const VERSES = [
|
||||
{ ref: 'Numbers 6:24', text: '{Name} bless you, and keep you.' },
|
||||
{ ref: 'Numbers 6:25', text: '{Name} make his face to shine on you, and be gracious to you.' },
|
||||
{ ref: 'Numbers 6:26', text: '{Name} lift up his face toward you, and give you peace.' },
|
||||
{ ref: 'Numbers 6:26', text: '{Name} lift up his face towards you, and give you peace.' },
|
||||
{ ref: 'Psalm 134:3', text: 'May {NAME} bless you from Zion, even he who made heaven and earth.' },
|
||||
{ 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.' },
|
||||
|
|
@ -46,11 +47,11 @@ export const BLESSING_COUNT = VERSES.length
|
|||
// 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 is from the
|
||||
// Verses are the WEB British Edition, verbatim; {NAME}/{Name} follow DIVINE_NAME. The sea's New Testament verse is from the
|
||||
// storm on the sea (Matthew 8:23-27): the verse where he rebukes the wind and the sea.
|
||||
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.' },
|
||||
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.' },
|
||||
|
|
@ -64,21 +65,15 @@ export const THEMES = THEMES_RAW.map((t) => ({ id: t.id, ot: verse(t.ot), nt: ve
|
|||
|
||||
export const POSTER = {
|
||||
banner: 'Bless the nations in Jesus’ name',
|
||||
// The large footer: the priestly blessing, the WHOLE passage (Numbers 6:22-27, Aaron and Israel
|
||||
// included): the Lord's instruction to Aaron in small type, the three blessing lines large, and the
|
||||
// promise that closes it. Verbatim WEB, its own speech marks kept.
|
||||
blessing: {
|
||||
ref: 'Numbers 6:22–27',
|
||||
intro: [
|
||||
withName('{Name} spoke to Moses, saying,'),
|
||||
'“Speak to Aaron and to his sons, saying, ‘This is how you shall bless the children of Israel.’ You shall tell them,',
|
||||
],
|
||||
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.’'),
|
||||
],
|
||||
outro: ['“So they shall put my name on the children of Israel; and I will bless them.”'],
|
||||
// The closing benediction, the poster's footer across its full width: the priestly blessing, Numbers 6:24-26,
|
||||
// in the World English Bible British Edition (public domain, ebible.org/eng-webbe; read via bible.helloao.org
|
||||
// eng_webpb), verbatim, set as two lines broken at verse 25's comma. Every verse on the poster is from the same
|
||||
// translation, so the credit line names it once.
|
||||
benediction: {
|
||||
lines: ['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.'],
|
||||
ref: 'Numbers 6:24–26',
|
||||
credit: 'Scripture: World English Bible British Edition (public domain).',
|
||||
},
|
||||
// The second footer: the calling to bless the nations as a journey, Abraham to the church, with Christ
|
||||
// the cornerstone at its centre (the `centre` station). Each station quotes its WHOLE passage, from the
|
||||
|
|
@ -86,12 +81,12 @@ export const POSTER = {
|
|||
// Luke 19:9-10): 1 Peter 2:4-10 is split at verse 9 into the cornerstone and the royal priesthood, and
|
||||
// Galatians 3:5-7 and 3:28-29 are one station, joined by " … ". Verbatim WEB, speech marks kept.
|
||||
journey: [
|
||||
{ id: 'called', ref: 'Genesis 12:1–4', text: 'Now {Name} said to Abram, “Leave your country, and your relatives, and your father’s house, and go to the land that I will show you. I will make of you a great nation. I will bless you and make your name great. You will be a blessing. I will bless those who bless you, and I will curse him who treats you with contempt. All the families of the earth will be blessed through you.” So Abram went, as {Name} had told him. Lot went with him. Abram was seventy-five years old when he departed from Haran.' },
|
||||
{ id: 'promised', ref: 'Genesis 15:5–6', text: '{Name} brought him outside, and said, “Look now toward the sky, and count the stars, if you are able to count them.” He said to Abram, “So your offspring will be.” He believed in {NAME}, who credited it to him for righteousness.' },
|
||||
{ id: 'called', ref: 'Genesis 12:1–4', text: 'Now {NAME} said to Abram, “Leave your country, and your relatives, and your father’s house, and go to the land that I will show you. I will make of you a great nation. I will bless you and make your name great. You will be a blessing. I will bless those who bless you, and I will curse him who treats you with contempt. All the families of the earth will be blessed through you.” So Abram went, as {NAME} had told him. Lot went with him. Abram was seventy-five years old when he departed from Haran.' },
|
||||
{ id: 'promised', ref: 'Genesis 15:5–6', text: '{Name} brought him outside, and said, “Look now towards the sky, and count the stars, if you are able to count them.” He said to Abram, “So your offspring will be.” He believed in {NAME}, who credited it to him for righteousness.' },
|
||||
{ id: 'redeemed', ref: 'Galatians 3:13–14', text: 'Christ redeemed us from the curse of the law, having become a curse for us. For it is written, “Cursed is everyone who hangs on a tree,” that the blessing of Abraham might come on the Gentiles through Christ Jesus, that we might receive the promise of the Spirit through faith.' },
|
||||
{ id: 'cornerstone', centre: true, ref: '1 Peter 2:4–8', text: 'Come to him, a living stone, rejected indeed by men, but chosen by God, precious. 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. Because it is contained in Scripture, “Behold, I lay in Zion a chief cornerstone, chosen and precious. He who believes in him will not be disappointed.” For you who believe therefore is the honor, but for those who are disobedient, “The stone which the builders rejected has become the chief cornerstone,” and, “a stumbling stone and a rock of offense.” For they stumble at the word, being disobedient, to which also they were appointed.' },
|
||||
{ id: 'children', ref: 'Galatians 3:5–7, 28–29', text: 'He therefore who supplies the Spirit to you and does miracles among you, does he do it by the works of the law, or by hearing of faith? Even so, Abraham “believed God, and it was counted to him for righteousness.” Know therefore that those who are of faith are children of Abraham. … There is neither Jew nor Greek, there is neither slave nor free man, there is neither male nor female; for you are all one in Christ Jesus. If you are Christ’s, then you are Abraham’s offspring and heirs according to promise.' },
|
||||
{ id: 'priesthood', ref: '1 Peter 2:9–10', 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. In the past, you were not a people, but now are God’s people, who had not obtained mercy, but now have obtained mercy.' },
|
||||
{ id: 'cornerstone', centre: true, ref: '1 Peter 2:4–8', text: 'Come to him, a living stone, rejected indeed by men, but chosen by God, precious. 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. Because it is contained in Scripture, “Behold, I lay in Zion a chief cornerstone, chosen and precious. He who believes in him will not be disappointed.” For you who believe therefore is the honour, but for those who are disobedient, “The stone which the builders rejected has become the chief cornerstone,” and, “a stumbling stone and a rock of offence.” For they stumble at the word, being disobedient, to which also they were appointed.' },
|
||||
{ id: 'children', ref: 'Galatians 3:5–7, 28–29', text: 'He therefore who supplies the Spirit to you and does miracles amongst you, does he do it by the works of the law, or by hearing of faith? Even so, Abraham “believed God, and it was counted to him for righteousness.” Know therefore that those who are of faith are children of Abraham. … There is neither Jew nor Greek, there is neither slave nor free man, there is neither male nor female; for you are all one in Christ Jesus. If you are Christ’s, then you are Abraham’s offspring and heirs according to promise.' },
|
||||
{ id: 'priesthood', ref: '1 Peter 2:9–10', 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 marvellous light. In the past, you were not a people, but now are God’s people, who had not obtained mercy, but now have obtained mercy.' },
|
||||
{ id: 'sent', ref: 'Luke 19:9–10', text: 'Jesus said to him, “Today, salvation has come to this house, because he also is a son of Abraham. For the Son of Man came to seek and to save that which was lost.”' },
|
||||
].map((v) => ({ ...v, text: withName(v.text) })),
|
||||
// Kept for reference: the earlier footer candidates (sheet row 99), superseded by the chosen footer.
|
||||
|
|
|
|||
|
|
@ -1,217 +0,0 @@
|
|||
<template>
|
||||
<div v-if="visible" class="dialog-overlay" @click="closeDialog">
|
||||
<div class="dialog-container" @click.stop ref="dialogBox" role="dialog" aria-modal="true" :aria-label="$t('wall.chatsTitle')" tabindex="-1">
|
||||
<!-- Dialog Header -->
|
||||
<div class="dialog-header">
|
||||
<h2>{{ $t('wall.chatsTitle') }}</h2>
|
||||
<button class="close-btn" :aria-label="$t('wall.close')" @click="closeDialog">✕</button>
|
||||
</div>
|
||||
|
||||
<!-- People Group Image (Instagram style) -->
|
||||
<div class="group-image-container">
|
||||
<img
|
||||
:src="getImageUrl()"
|
||||
:alt="group?.name || $t('wall.peopleGroup')"
|
||||
class="group-image"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="group-name-dialog">{{ group?.name || $t('wall.unknownGroup') }}</div>
|
||||
|
||||
<!-- People Group Info -->
|
||||
<div class="dialog-content">
|
||||
<div class="group-info">
|
||||
<div class="info-row">
|
||||
<strong>{{ $t('wall.status') }}</strong> {{ group?.status || $t('wall.unknown') }}
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<strong>{{ $t('wall.id') }}</strong> {{ group?.ID || $t('wall.na') }}
|
||||
</div>
|
||||
<div class="info-row" v-if="group?.lat && group?.lng">
|
||||
<strong>{{ $t('wall.location') }}</strong> {{ group.lat }}, {{ group.lng }}
|
||||
</div>
|
||||
<div class="info-row prayer-stat">
|
||||
<span class="heart-icon">❤️</span>
|
||||
{{ $t('wall.havePrayed', { n: prayerCount }, prayerCount) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Prayer Button -->
|
||||
<div class="prayer-action">
|
||||
<button
|
||||
class="prayer-btn"
|
||||
@click="addPrayer"
|
||||
:class="{ active: justPrayed }"
|
||||
>
|
||||
{{ justPrayed ? $t('wall.youPrayed') : $t('wall.prayForGroup') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Comments Section -->
|
||||
<div class="comments-section">
|
||||
<h3>{{ $t('wall.prayersComments') }}</h3>
|
||||
|
||||
<!-- Comment List -->
|
||||
<div class="comments-list">
|
||||
<div
|
||||
v-if="comments.length === 0"
|
||||
class="no-comments"
|
||||
>
|
||||
{{ $t('wall.noComments') }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="comment in comments"
|
||||
:key="comment.id"
|
||||
class="comment-item"
|
||||
>
|
||||
<div class="comment-header">
|
||||
<span class="comment-icon">💭</span>
|
||||
<span class="comment-date">{{ formatDate(comment.timestamp) }}</span>
|
||||
</div>
|
||||
<div class="comment-text">{{ comment.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Comment Form -->
|
||||
<div class="add-comment">
|
||||
<textarea
|
||||
v-model="newComment"
|
||||
:placeholder="$t('wall.commentPlaceholder')"
|
||||
rows="3"
|
||||
maxlength="500"
|
||||
></textarea>
|
||||
<div class="comment-actions">
|
||||
<span class="char-count">{{ newComment.length }}/500</span>
|
||||
<button
|
||||
@click="submitComment"
|
||||
:disabled="!newComment.trim()"
|
||||
class="submit-btn"
|
||||
>
|
||||
{{ $t('wall.postComment') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { trapDialog } from '../trapDialog.js'
|
||||
// No third-party placeholder service: a missing photo is drawn inline.
|
||||
const NO_IMAGE = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='400'%3E%3Crect width='100%25' height='100%25' fill='%23f3f3f1'/%3E%3Ctext x='50%25' y='50%25' fill='%236d766e' font-family='sans-serif' font-size='22' text-anchor='middle' dominant-baseline='middle'%3ENo image available%3C/text%3E%3C/svg%3E"
|
||||
// Comments and Prayers Dialog Component
|
||||
export default {
|
||||
name: 'CommentsDialog',
|
||||
props: {
|
||||
group: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
newComment: '',
|
||||
justPrayed: false,
|
||||
imageError: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
prayerCount() {
|
||||
if (!this.group || !this.$store) return 0;
|
||||
// Access the prayers state directly
|
||||
return this.$store.prayers?.[this.group.ID] || 0;
|
||||
},
|
||||
|
||||
comments() {
|
||||
if (!this.group || !this.$store) return [];
|
||||
// Access the comments state directly
|
||||
return this.$store.comments?.[this.group.ID] || [];
|
||||
}
|
||||
},
|
||||
beforeUnmount() { this._releaseTrap?.(); this._releaseTrap = null; },
|
||||
methods: {
|
||||
closeDialog() {
|
||||
this.$emit('close');
|
||||
this.newComment = '';
|
||||
this.justPrayed = false;
|
||||
},
|
||||
|
||||
addPrayer() {
|
||||
if (this.group && this.$store) {
|
||||
this.$store.addPrayer(this.group.ID);
|
||||
this.justPrayed = true;
|
||||
|
||||
// Reset after 2 seconds
|
||||
setTimeout(() => {
|
||||
this.justPrayed = false;
|
||||
}, 2000);
|
||||
}
|
||||
},
|
||||
|
||||
submitComment() {
|
||||
if (this.newComment.trim() && this.group && this.$store) {
|
||||
this.$store.addComment(
|
||||
this.group.ID,
|
||||
this.newComment.trim()
|
||||
);
|
||||
this.newComment = '';
|
||||
}
|
||||
},
|
||||
|
||||
formatDate(timestamp) {
|
||||
const date = new Date(timestamp);
|
||||
const now = new Date();
|
||||
const diffMs = now - date;
|
||||
const diffMins = Math.floor(diffMs / 60000);
|
||||
const diffHours = Math.floor(diffMs / 3600000);
|
||||
const diffDays = Math.floor(diffMs / 86400000);
|
||||
|
||||
// The reader's language, from the platform: "just now" / "5 minutes ago" / "hace 5 minutos" …
|
||||
// (Intl.RelativeTimeFormat), so no hand-built English "m ago" and no catalogue keys to drift.
|
||||
const locale = String(this.$i18n?.locale || 'en');
|
||||
let rtf = null;
|
||||
try { rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }); } catch (_) { /* old engine: fall back below */ }
|
||||
if (rtf) {
|
||||
if (diffMins < 1) return rtf.format(0, 'second');
|
||||
if (diffMins < 60) return rtf.format(-diffMins, 'minute');
|
||||
if (diffHours < 24) return rtf.format(-diffHours, 'hour');
|
||||
if (diffDays < 7) return rtf.format(-diffDays, 'day');
|
||||
}
|
||||
return date.toLocaleDateString(locale);
|
||||
},
|
||||
|
||||
getImageUrl() {
|
||||
if (this.imageError) {
|
||||
return NO_IMAGE;
|
||||
}
|
||||
|
||||
// The card's own photo (the live API's image_url); uupg.doxa.life no longer answers.
|
||||
return this.group?.imageUrl || NO_IMAGE;
|
||||
},
|
||||
|
||||
handleImageError(event) {
|
||||
this.imageError = true;
|
||||
event.target.src = NO_IMAGE;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(newVal) {
|
||||
if (newVal) this.$nextTick(() => { this._releaseTrap?.(); this._releaseTrap = trapDialog(this.$refs.dialogBox, () => this.closeDialog()); });
|
||||
else { this._releaseTrap?.(); this._releaseTrap = null; }
|
||||
if (newVal) {
|
||||
// Reset state when dialog opens
|
||||
this.justPrayed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
@ -295,8 +295,9 @@ export default {
|
|||
this.$emit('open-prayer', group);
|
||||
},
|
||||
|
||||
openComments(group) {
|
||||
this.$emit('open-comments', group);
|
||||
// Comments are not built yet: the comment and "view prayers" taps say so instead of opening an empty board.
|
||||
openComments() {
|
||||
this.flash(this.$t('wallSocial.commentsSoon'), 3000);
|
||||
},
|
||||
|
||||
truncateText(text, maxLength) {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,10 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div v-else class="prayer-confirmed">
|
||||
<!-- The group's own sign-up on pray.doxa.life (its #signup-section): commit to pray for it there. -->
|
||||
<a v-if="signUpUrl" :href="signUpUrl" target="_blank" rel="noopener noreferrer" class="signup-link">{{ $t('wallSocial.signUpToPray', { name: group?.name || '' }) }}</a>
|
||||
|
||||
<div v-if="hasPrayed" class="prayer-confirmed">
|
||||
<div class="confirmed-message">
|
||||
<svg class="heart-icon" width="1em" height="1em" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 20.5s-7.2-4.4-9.2-8.8C1.3 8.3 3.2 4.6 6.7 4.6c2 0 3.5 1.1 4.3 2.4.8-1.3 2.3-2.4 4.3-2.4 3.5 0 5.4 3.7 3.9 7.1-2 4.4-9.2 8.8-9.2 8.8z" fill="#ed4956" stroke="#ed4956" stroke-width="1.8" stroke-linejoin="round"/></svg>
|
||||
<span>{{ $t('wall.thankYou') }}</span>
|
||||
|
|
@ -64,6 +67,8 @@
|
|||
<script>
|
||||
import { trapDialog } from '../trapDialog.js'
|
||||
// No third-party placeholder service: a missing photo is drawn inline.
|
||||
// The locales pray.doxa.life serves under a path prefix (English is unprefixed).
|
||||
const PRAY_LOCALES = new Set(['ar', 'de', 'es', 'fr', 'hi', 'it', 'pt', 'ro', 'ru', 'zh'])
|
||||
const NO_IMAGE = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='400'%3E%3Crect width='100%25' height='100%25' fill='%23f3f3f1'/%3E%3Ctext x='50%25' y='50%25' fill='%236d766e' font-family='sans-serif' font-size='22' text-anchor='middle' dominant-baseline='middle'%3ENo image available%3C/text%3E%3C/svg%3E"
|
||||
export default {
|
||||
name: 'PrayerDialog',
|
||||
|
|
@ -84,6 +89,18 @@ export default {
|
|||
imageError: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// The group's prayer sign-up on pray.doxa.life (the #signup-section of its page), in the reader's language:
|
||||
// the site serves its group pages under /<lc>/ for these ten locales and English unprefixed (read from its
|
||||
// routes; /ar/<slug> carries the same section). None without a slug.
|
||||
signUpUrl() {
|
||||
const slug = this.group && this.group.slug
|
||||
if (!slug) return ''
|
||||
const lc = String(this.$i18n?.locale || 'en').split('-')[0].toLowerCase()
|
||||
const prefix = PRAY_LOCALES.has(lc) ? '/' + lc : ''
|
||||
return `https://pray.doxa.life${prefix}/${encodeURIComponent(slug)}#signup-section`
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
isOpen(newVal) {
|
||||
if (newVal) this.$nextTick(() => { this._releaseTrap?.(); this._releaseTrap = trapDialog(this.$refs.dialogBox, () => this.closeDialog()); });
|
||||
|
|
|
|||
|
|
@ -275,6 +275,12 @@ body {
|
|||
}
|
||||
|
||||
/* Group Image in Dialog */
|
||||
/* The prayer dialog SCROLLS when it is taller than the window: .dialog-container clips at 90vh, which hid
|
||||
everything under the photo on a laptop screen (the sign-up link, and the prayer box after "Yes"). The photo
|
||||
also gives way, so the prompt and its actions show without scrolling at 900 px. */
|
||||
.dialog-container.prayer-dialog { overflow-y: auto; overscroll-behavior: contain; }
|
||||
.prayer-dialog .group-image-container img { max-height: min(400px, 38vh); }
|
||||
|
||||
.group-image-container {
|
||||
width: 100%;
|
||||
background-color: #fafafa;
|
||||
|
|
@ -513,6 +519,20 @@ body {
|
|||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* The group's sign-up on pray.doxa.life, under the prayer prompt: a quiet full-width link. */
|
||||
.signup-link {
|
||||
display: block;
|
||||
margin: 12px 0 4px;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid #c9bfa4;
|
||||
border-radius: 8px;
|
||||
color: #6b4a78;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
.signup-link:hover, .signup-link:focus-visible { background: #f7f4ec; }
|
||||
|
||||
.prayer-confirm-btn {
|
||||
padding: 14px 28px;
|
||||
background: var(--heart-color);
|
||||
|
|
|
|||
|
|
@ -18,29 +18,23 @@
|
|||
* (src/wallD3.js). Font Awesome is no longer used: the canvas draws its own icons.
|
||||
*/
|
||||
import PeopleGroupsWall from './src/components/PeopleGroupsWall.vue'
|
||||
import CommentsDialog from './src/components/CommentsDialog.vue'
|
||||
import PrayerDialog from './src/components/PrayerDialog.vue'
|
||||
import { ensureD3 } from './src/wallD3.js'
|
||||
|
||||
export default {
|
||||
name: 'WallOfPeopleGroups',
|
||||
components: { PeopleGroupsWall, CommentsDialog, PrayerDialog },
|
||||
components: { PeopleGroupsWall, PrayerDialog },
|
||||
data() {
|
||||
return { ready: false, error: null, commentsDialogVisible: false, prayerDialogVisible: false, selectedGroup: null }
|
||||
return { ready: false, error: null, prayerDialogVisible: false, selectedGroup: null }
|
||||
},
|
||||
mounted() {
|
||||
ensureD3().then(() => { this.ready = true }, (e) => { this.error = e.message })
|
||||
},
|
||||
methods: {
|
||||
openCommentsDialog(group) { this.selectedGroup = group; this.commentsDialogVisible = true },
|
||||
closeCommentsDialog() {
|
||||
this.commentsDialogVisible = false
|
||||
setTimeout(() => { this.selectedGroup = null }, 300)
|
||||
},
|
||||
openPrayerDialog(group) { this.selectedGroup = group; this.prayerDialogVisible = true },
|
||||
closePrayerDialog() {
|
||||
this.prayerDialogVisible = false
|
||||
setTimeout(() => { if (!this.commentsDialogVisible) this.selectedGroup = null }, 300)
|
||||
setTimeout(() => { this.selectedGroup = null }, 300)
|
||||
},
|
||||
handlePrayerConfirmed() { /* PrayerDialog already updated the store */ },
|
||||
},
|
||||
|
|
@ -49,9 +43,8 @@ export default {
|
|||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<people-groups-wall v-if="ready" @open-comments="openCommentsDialog" @open-prayer="openPrayerDialog" />
|
||||
<people-groups-wall v-if="ready" @open-prayer="openPrayerDialog" />
|
||||
<div v-else-if="error" class="wall-error">{{ $t('wall.drawLibError') }} {{ error }}</div>
|
||||
<comments-dialog :visible="commentsDialogVisible" :group="selectedGroup" @close="closeCommentsDialog" />
|
||||
<prayer-dialog :is-open="prayerDialogVisible" :group="selectedGroup" @close="closePrayerDialog" @prayer-confirmed="handlePrayerConfirmed" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1787,7 +1787,7 @@
|
|||
"bundle": "wall-of-people-groups",
|
||||
"scriptUrl": "./wall-of-people-groups/wall-of-people-groups.js",
|
||||
"output": "dx-maps/wall-of-people-groups/wall-of-people-groups.js",
|
||||
"integrity": "sha384-hl7O/Acvof820zbglnPrfsO3F0CaB3TmA882fSkj3ctLrEQDGfuLoCVlVC7qZcnW",
|
||||
"integrity": "sha384-YR31Wdy/svC86D+/G+AVKNK/HdWQYnWh4Uv1rRhPJyPcjRVRT9s2opXRCCU9BeZK",
|
||||
"elements": [
|
||||
"wall-of-people-groups"
|
||||
],
|
||||
|
|
@ -1807,7 +1807,7 @@
|
|||
"scriptUrl": "./wall-of-people-groups/wall-of-people-groups.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./wall-of-people-groups/wall-of-people-groups/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./wall-of-people-groups/wall-of-people-groups.js\" integrity=\"sha384-hl7O/Acvof820zbglnPrfsO3F0CaB3TmA882fSkj3ctLrEQDGfuLoCVlVC7qZcnW\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<wall-of-people-groups profile-config='{\"profile\":\"wall-of-people-groups\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></wall-of-people-groups>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./wall-of-people-groups/wall-of-people-groups.js\" integrity=\"sha384-YR31Wdy/svC86D+/G+AVKNK/HdWQYnWh4Uv1rRhPJyPcjRVRT9s2opXRCCU9BeZK\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<wall-of-people-groups profile-config='{\"profile\":\"wall-of-people-groups\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></wall-of-people-groups>"
|
||||
},
|
||||
"bundle": "wall-of-people-groups"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1787,7 +1787,7 @@
|
|||
"bundle": "wall-of-people-groups",
|
||||
"scriptUrl": "./wall-of-people-groups/wall-of-people-groups.js",
|
||||
"output": "dx-maps/wall-of-people-groups/wall-of-people-groups.js",
|
||||
"integrity": "sha384-hl7O/Acvof820zbglnPrfsO3F0CaB3TmA882fSkj3ctLrEQDGfuLoCVlVC7qZcnW",
|
||||
"integrity": "sha384-YR31Wdy/svC86D+/G+AVKNK/HdWQYnWh4Uv1rRhPJyPcjRVRT9s2opXRCCU9BeZK",
|
||||
"elements": [
|
||||
"wall-of-people-groups"
|
||||
],
|
||||
|
|
@ -1807,7 +1807,7 @@
|
|||
"scriptUrl": "./wall-of-people-groups/wall-of-people-groups.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./wall-of-people-groups/wall-of-people-groups/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./wall-of-people-groups/wall-of-people-groups.js\" integrity=\"sha384-hl7O/Acvof820zbglnPrfsO3F0CaB3TmA882fSkj3ctLrEQDGfuLoCVlVC7qZcnW\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<wall-of-people-groups profile-config='{\"profile\":\"wall-of-people-groups\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></wall-of-people-groups>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./wall-of-people-groups/wall-of-people-groups.js\" integrity=\"sha384-YR31Wdy/svC86D+/G+AVKNK/HdWQYnWh4Uv1rRhPJyPcjRVRT9s2opXRCCU9BeZK\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<wall-of-people-groups profile-config='{\"profile\":\"wall-of-people-groups\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></wall-of-people-groups>"
|
||||
},
|
||||
"bundle": "wall-of-people-groups"
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -67,7 +67,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <wall-of-people-groups> custom element. -->
|
||||
<script src="../wall-of-people-groups.js" integrity="sha384-hl7O/Acvof820zbglnPrfsO3F0CaB3TmA882fSkj3ctLrEQDGfuLoCVlVC7qZcnW" crossorigin="anonymous"></script>
|
||||
<script src="../wall-of-people-groups.js" integrity="sha384-YR31Wdy/svC86D+/G+AVKNK/HdWQYnWh4Uv1rRhPJyPcjRVRT9s2opXRCCU9BeZK" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
Loading…
Reference in a new issue