Wall of People Groups: Bless mode by default, a blessing verse under every people group, and Social mode one click away
This commit is contained in:
parent
988092d363
commit
f4c3767784
3 changed files with 114 additions and 2 deletions
73
plugin-designs/wall-of-people-groups/src/blessings.js
Normal file
73
plugin-designs/wall-of-people-groups/src/blessings.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* 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
|
||||
* whole verse, or one whole sentence of it (marked "b" in the reference), 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.)
|
||||
*/
|
||||
export const DIVINE_NAME = { mid: 'Yahweh', start: 'Yahweh' } // British Edition: { mid: 'the LORD', start: 'The LORD' }
|
||||
|
||||
const withName = (s) => s.replace(/\{Name\}/g, DIVINE_NAME.start).replace(/\{NAME\}/g, DIVINE_NAME.mid)
|
||||
|
||||
// Blessings spoken over a people, then the nations' praise. Assigned by group index, so a group keeps
|
||||
// its verse and neighbours alternate.
|
||||
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: '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.' },
|
||||
{ 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 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.' },
|
||||
]
|
||||
|
||||
/** The verse for the group at index i: { text, ref }. */
|
||||
export function blessingFor(i) {
|
||||
const v = VERSES[((i % VERSES.length) + VERSES.length) % VERSES.length]
|
||||
return { text: withName(v.text), ref: v.ref }
|
||||
}
|
||||
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).
|
||||
export const POSTER = {
|
||||
banner: 'Bless the nations in Jesus’ name',
|
||||
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). */
|
||||
export function wrapText(text, perLine, maxLines = 4) {
|
||||
const words = String(text).split(/\s+/)
|
||||
const lines = []
|
||||
let cur = ''
|
||||
for (const w of words) {
|
||||
if (!cur) cur = w
|
||||
else if ((cur + ' ' + w).length <= perLine) cur += ' ' + w
|
||||
else { lines.push(cur); cur = w }
|
||||
}
|
||||
if (cur) lines.push(cur)
|
||||
if (lines.length > maxLines) {
|
||||
const kept = lines.slice(0, maxLines)
|
||||
kept[maxLines - 1] = kept[maxLines - 1].replace(/\s*\S*$/, '') + '…'
|
||||
return kept
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="wall-container">
|
||||
<div class="wall-header">
|
||||
<h1>{{ $t('wall.title') }}</h1>
|
||||
<h1>{{ mode === 'bless' ? $t('wall.blessBanner') : $t('wall.title') }}</h1>
|
||||
<div class="wall-controls">
|
||||
<button @click="toggleOrientation" class="control-btn orientation-btn">
|
||||
<span class="btn-label">{{ isLandscape ? $t('wall.toPortrait') : $t('wall.toLandscape') }}</span>
|
||||
|
|
@ -11,6 +11,12 @@
|
|||
start a second load while the first is still running. -->
|
||||
<button @click="printWall" class="control-btn" :class="{ busy: printing }" :disabled="printing" :aria-busy="printing ? 'true' : 'false'">{{ $t('wall.printPoster') }}</button>
|
||||
<button @click="resetZoom" class="control-btn">{{ $t('wall.resetZoom') }}</button>
|
||||
<!-- BLESS (default): name, photo and a blessing verse under every group, made to print.
|
||||
SOCIAL: the interactive card (pray, comments, share). -->
|
||||
<div class="mode-toggle" role="group" :aria-label="$t('wall.modeLabel')">
|
||||
<button type="button" class="control-btn" :class="{ on: mode === 'bless' }" :aria-pressed="mode === 'bless' ? 'true' : 'false'" @click="setMode('bless')">{{ $t('wall.modeBless') }}</button>
|
||||
<button type="button" class="control-btn" :class="{ on: mode === 'social' }" :aria-pressed="mode === 'social' ? 'true' : 'false'" @click="setMode('social')">{{ $t('wall.modeSocial') }}</button>
|
||||
</div>
|
||||
<span class="group-count">{{ $t('wall.groupsLoaded', { n: totalGroups }) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -37,6 +43,7 @@
|
|||
<script>
|
||||
import { lodPlan } from '../wallLod.js'
|
||||
import { wallD3 } from '../wallD3.js'
|
||||
import { blessingFor, wrapText } from '../blessings.js'
|
||||
// Wall of People Groups - D3 Zoomable Whiteboard
|
||||
export default {
|
||||
name: 'PeopleGroupsWall',
|
||||
|
|
@ -44,6 +51,7 @@ export default {
|
|||
inject: { profileConfig: { default: null } },
|
||||
data() {
|
||||
return {
|
||||
mode: 'bless', // 'bless' (default) | 'social'
|
||||
printing: false, // the HD poster print is loading photos (drives the button's busy state)
|
||||
svg: null,
|
||||
zoom: null,
|
||||
|
|
@ -296,7 +304,7 @@ export default {
|
|||
});
|
||||
|
||||
// Add status (moved down to accommodate 2-line names)
|
||||
cardGroups
|
||||
if (this.mode !== 'bless') cardGroups
|
||||
.append('text')
|
||||
.attr('x', cardWidth / 2)
|
||||
.attr('y', this.cardHeight * 0.14)
|
||||
|
|
@ -363,6 +371,26 @@ export default {
|
|||
this.$nextTick(() => { this.applyStartView(); this.updateLod(); });
|
||||
|
||||
// Add action buttons row with FontAwesome icons - Instagram style
|
||||
// BLESS: a verse under the photo instead of the buttons and counters (blessings.js, WEB, verbatim).
|
||||
if (this.mode === 'bless') {
|
||||
const vFont = Math.max(3, cardWidth * 0.052);
|
||||
const refFont = vFont * 0.82;
|
||||
const perLine = Math.max(10, Math.floor(cardWidth / (vFont * 0.5)));
|
||||
const vy = imageY + imageHeight + vFont * 1.5;
|
||||
cardGroups.each(function(d, i) {
|
||||
const b = blessingFor(i);
|
||||
const lines = wrapText(b.text, perLine, 4);
|
||||
const g = wallD3().select(this);
|
||||
const t = g.append('text').attr('class', 'bless-verse').attr('text-anchor', 'middle')
|
||||
.attr('font-size', vFont).attr('fill', '#262626').attr('font-style', 'italic').style('user-select', 'text');
|
||||
lines.forEach((ln, k) => t.append('tspan').attr('x', cardWidth / 2).attr('y', vy + k * vFont * 1.25).text(ln));
|
||||
g.append('text').attr('class', 'bless-ref').attr('x', cardWidth / 2)
|
||||
.attr('y', vy + lines.length * vFont * 1.25 + refFont * 0.5).attr('text-anchor', 'middle')
|
||||
.attr('font-size', refFont).attr('fill', '#8e8e8e').text(b.ref);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const buttonY = this.cardHeight * 0.72;
|
||||
const buttonSize = iconSize * 0.7; // Make buttons smaller (was iconSize)
|
||||
const buttonSpacing = buttonSize * 1.6; // Tighter spacing
|
||||
|
|
@ -444,6 +472,12 @@ export default {
|
|||
|
||||
},
|
||||
|
||||
setMode(m) {
|
||||
if (this.mode === m) return;
|
||||
this.mode = m;
|
||||
// Redraw the cards in the new mode; zoom, the level-of-detail state and the photos come back as before.
|
||||
if (this.svg) { this.svg.select('.cards-container').selectAll('*').remove(); this.renderCards(); }
|
||||
},
|
||||
handlePrayer(group) {
|
||||
// This method is deprecated - now using openPrayerDialog
|
||||
this.openPrayerDialog(group);
|
||||
|
|
|
|||
|
|
@ -85,6 +85,11 @@ body {
|
|||
animation: wall-spin .8s linear infinite; }
|
||||
@keyframes wall-spin { to { transform: rotate(360deg); } }
|
||||
@media (prefers-reduced-motion: reduce) { .control-btn.busy::after { animation: none; } }
|
||||
.mode-toggle { display: inline-flex; gap: 0; }
|
||||
.mode-toggle .control-btn { border-radius: 0; }
|
||||
.mode-toggle .control-btn:first-child { border-start-start-radius: 6px; border-end-start-radius: 6px; }
|
||||
.mode-toggle .control-btn:last-child { border-start-end-radius: 6px; border-end-end-radius: 6px; }
|
||||
.mode-toggle .control-btn.on { background: #262626; color: #fff; }
|
||||
.control-btn:hover {
|
||||
background: #0081d6;
|
||||
transform: translateY(-1px);
|
||||
|
|
|
|||
Loading…
Reference in a new issue