Wall of People Groups: the Bless poster takes its sheets, glue bands and marks from PanelSheets, byte for byte
blessPoster.js now plans its 5 x 8 ft sheets, finished size, screen layout, glue band, trim line and sheet tags through library panelSheets.js instead of its own copy. The print HTML, the screen layout and the plan are unchanged: hashed for 3 presets and the default in all 11 locales (44 cases), before and after, all identical; a 0.001 in change to the glue label moves all 44. check:panel-sheets (gates:pre, headless) pins both geometries (60x96 stacked, 66x66 side by side, one sheet; glue and bleed): - the sheets tile the finished poster; - glue slices tile it, and bleed slices repeat exactly the join; - a bleed join prints a guide, not a solid band, and a screen preview always shows the red band; - the Bless 5x8ft takes its bands and marks from the module. It is two-sided: 4 planted defects are caught, and the Bless arm fails on the old private copy.
This commit is contained in:
parent
3ee5ed9fde
commit
332aa002f8
7 changed files with 179 additions and 56 deletions
118
internal/check-panel-sheets.mjs
Normal file
118
internal/check-panel-sheets.mjs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* check-panel-sheets — the shared panel geometry (library/components/poster/panelSheets.js). Headless.
|
||||
*
|
||||
* A poster too big for one sheet prints as several sheets, each trimmed edge laid over the next sheet's
|
||||
* join band. This pins:
|
||||
* A. the two multi-sheet sizes: 60x96 = 3 × 60×36 stacked, 6 in joins (the Bless 5 × 8 ft poster);
|
||||
* 66x66 = 2 × 36×66 side by side, 6 in join (India); and a single sheet has no join;
|
||||
* B. the sheets TILE the finished poster: what each sheet shows (itself minus the band the sheet before
|
||||
* covers) covers every inch once, no gap, no double;
|
||||
* C. sliceFor: with 'glue' the slices tile the poster; with 'bleed' neighbours repeat exactly joinIn;
|
||||
* D. the print marks: a 'glue' join prints the red band; a 'bleed' join prints only a guide (a solid band
|
||||
* would show through a misaligned trim); a screen preview always draws the red band; the trim line is on
|
||||
* every sheet but the last; every sheet has its "s / N" tag;
|
||||
* E. the Bless poster takes its sheets, bands and marks from this module (no private copy to drift): its
|
||||
* 5x8ft content bands equal the panels' content boxes, and blessPoster.js carries no glue band of its own.
|
||||
* Two-sided: each arm runs against a planted defect and must fail.
|
||||
* EXIT 0 pass · 1 fail
|
||||
*/
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve, dirname, join } from 'node:path'
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||
|
||||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const real = await import(pathToFileURL(join(ROOT, 'library/components/poster/panelSheets.js')).href)
|
||||
const bless = await import(pathToFileURL(join(ROOT, 'plugin-designs/wall-of-people-groups/src/blessPoster.js')).href)
|
||||
const blessSrc = readFileSync(join(ROOT, 'plugin-designs/wall-of-people-groups/src/blessPoster.js'), 'utf8')
|
||||
const eq = (a, b) => Math.abs(a - b) < 1e-9
|
||||
|
||||
const SIZES = {
|
||||
'60x96': { sheetW: 60, sheetH: 36, count: 3, direction: 'vertical', joinIn: 6, want: { w: 60, h: 96, at: [0, 30, 60] } },
|
||||
'66x66': { sheetW: 36, sheetH: 66, count: 2, direction: 'horizontal', joinIn: 6, want: { w: 66, h: 66, at: [0, 30] } },
|
||||
'36x36': { sheetW: 36, sheetH: 36, count: 1, direction: 'vertical', joinIn: 0, want: { w: 36, h: 36, at: [0] } },
|
||||
}
|
||||
|
||||
function run(M) {
|
||||
const errs = []
|
||||
let checked = 0
|
||||
for (const [key, z] of Object.entries(SIZES)) {
|
||||
for (const joinFill of ['glue', 'bleed']) {
|
||||
const P = M.planPanels({ ...z, borderIn: 2, joinFill })
|
||||
const V = P.direction === 'vertical', along = V ? P.sheetH : P.sheetW, total = V ? P.finished.h : P.finished.w
|
||||
// A. geometry
|
||||
if (!eq(P.finished.w, z.want.w) || !eq(P.finished.h, z.want.h)) errs.push(`A ${key}: finished ${P.finished.w} × ${P.finished.h}, want ${z.want.w} × ${z.want.h}`)
|
||||
if (P.sheets.length !== z.count) errs.push(`A ${key}: ${P.sheets.length} sheets, want ${z.count}`)
|
||||
P.sheets.forEach((sh, s) => {
|
||||
const off = V ? sh.at.y : sh.at.x
|
||||
if (!eq(off, z.want.at[s])) errs.push(`A ${key}: sheet ${s + 1} starts at ${off} in, want ${z.want.at[s]}`)
|
||||
if ((s > 0) !== !!sh.band) errs.push(`A ${key}: sheet ${s + 1} ${sh.band ? 'has' : 'lacks'} a join band`)
|
||||
if (sh.band && !eq(V ? sh.band.h : sh.band.w, z.joinIn)) errs.push(`A ${key}: sheet ${s + 1}'s band is not ${z.joinIn} in deep`)
|
||||
if (sh.trimmed !== (s < z.count - 1)) errs.push(`A ${key}: sheet ${s + 1} trimmed=${sh.trimmed}`)
|
||||
const c = sh.content, lead = V ? c.y0 : c.x0, trail = along - (V ? c.y1 : c.x1)
|
||||
if (!eq(lead, s === 0 ? 2 : z.joinIn) || !eq(trail, s === z.count - 1 ? 2 : 0)) errs.push(`A ${key}: sheet ${s + 1}'s content box leaves ${lead} / ${trail} in`)
|
||||
})
|
||||
// B. the visible parts tile the finished poster (1/8 in samples)
|
||||
const hits = new Array(Math.round(total * 8)).fill(0)
|
||||
P.sheets.forEach((sh, s) => {
|
||||
const off = V ? sh.at.y : sh.at.x, from = off + (sh.band ? P.joinIn : 0), to = off + along
|
||||
for (let k = 0; k < hits.length; k++) { const t = (k + 0.5) / 8; if (t >= from && t < to) hits[k]++ }
|
||||
})
|
||||
const bad = hits.findIndex((h) => h !== 1)
|
||||
if (bad >= 0) errs.push(`B ${key}: at ${(bad + 0.5) / 8} in the poster is shown by ${hits[bad]} sheets, not 1`)
|
||||
// C. slices
|
||||
const sl = P.sheets.map((_, s) => M.sliceFor(P, s))
|
||||
for (let s = 1; s < sl.length; s++) {
|
||||
const endPrev = V ? sl[s - 1].y + sl[s - 1].h : sl[s - 1].x + sl[s - 1].w, start = V ? sl[s].y : sl[s].x
|
||||
const want = joinFill === 'bleed' ? endPrev - z.joinIn : endPrev
|
||||
if (!eq(start, want)) errs.push(`C ${key} ${joinFill}: slice ${s + 1} starts at ${start} in, want ${want}`)
|
||||
}
|
||||
const last = sl[sl.length - 1]
|
||||
if (!eq(V ? last.y + last.h : last.x + last.w, total) || !eq(V ? sl[0].y : sl[0].x, 0)) errs.push(`C ${key} ${joinFill}: the slices do not span the poster`)
|
||||
// D. marks
|
||||
P.sheets.forEach((sh, s) => {
|
||||
const pr = M.panelChromeHtml(P, s, {}), sc = M.panelChromeHtml(P, s, { screen: true })
|
||||
const solid = /class="glue[ "]/.test(pr.before), guide = /class="glue-guide/.test(pr.before)
|
||||
if (sh.band && joinFill === 'glue' && !solid) errs.push(`D ${key}: a glue join does not print its red band`)
|
||||
if (sh.band && joinFill === 'bleed' && (solid || !guide)) errs.push(`D ${key}: a bleed join prints ${solid ? 'a solid band' : 'no guide'}`)
|
||||
if (sh.band && !/class="glue[ "]/.test(sc.before)) errs.push(`D ${key}: the screen preview does not draw the red band`)
|
||||
if (!sh.band && (pr.before || sc.before)) errs.push(`D ${key}: sheet 1 draws a join band`)
|
||||
if (/class="trim/.test(pr.after) !== sh.trimmed) errs.push(`D ${key}: sheet ${s + 1}'s trim line is wrong`)
|
||||
if (!pr.after.includes(`>${s + 1} / ${z.count}<`)) errs.push(`D ${key}: sheet ${s + 1} has no "${s + 1} / ${z.count}" tag`)
|
||||
})
|
||||
checked++
|
||||
}
|
||||
}
|
||||
return { errs, checked }
|
||||
}
|
||||
|
||||
const fails = []
|
||||
const r = run(real)
|
||||
fails.push(...r.errs)
|
||||
if (r.checked < 6) fails.push(`only ${r.checked} of 6 size × fill cases ran`)
|
||||
// E. the Bless consumer
|
||||
const plan = bless.planPoster('5x8ft', 2107)
|
||||
const P = real.planPanels({ sheetW: 60, sheetH: 36, count: 3, direction: 'vertical', joinIn: 6, borderIn: 6, joinFill: 'glue' })
|
||||
plan.bands.forEach((bd, s) => {
|
||||
const c = P.sheets[s].content
|
||||
if (!eq(bd.y0 - (s === 0 ? plan.bannerH : 0), c.y0) || !eq(bd.y1 + (s === 2 ? plan.footerH + (plan.gapH || 0) : 0), c.y1)) fails.push(`E: Bless 5x8ft sheet ${s + 1}'s band is not the panels' content box`)
|
||||
})
|
||||
if (!eq(plan.posterW, P.finished.w) || !eq(plan.posterH, P.finished.h)) fails.push('E: the Bless poster\'s finished size is not the panels\'')
|
||||
if (!/from '[./]+library\/components\/poster\/panelSheets\.js'/.test(blessSrc)) fails.push('E: blessPoster.js does not import panelSheets.js')
|
||||
if (/class="glue"/.test(blessSrc) || /\.glue\{/.test(blessSrc)) fails.push('E: blessPoster.js carries its own glue band (a private copy to drift)')
|
||||
if (!/class="glue"/.test(bless.buildBlessPosterHtml(Array.from({ length: 50 }, () => ({ name: 'x' })), '5x8ft', {}))) fails.push('E: the Bless 5x8ft print has no glue band')
|
||||
|
||||
// Two-sided: each arm against a planted defect.
|
||||
const planted = {
|
||||
'A/B (the joins not subtracted)': { ...real, planPanels: (o) => { const p = real.planPanels(o); const V = p.direction === 'vertical'; return { ...p, finished: V ? { w: p.sheetW, h: p.count * p.sheetH } : { w: p.count * p.sheetW, h: p.sheetH } } } },
|
||||
'C (a glue slice keeps the band)': { ...real, sliceFor: (p, s) => real.sliceFor({ ...p, joinFill: 'bleed' }, s) },
|
||||
'D (a bleed join prints a solid band)': { ...real, panelChromeHtml: (p, s, o = {}) => real.panelChromeHtml(p, s, { ...o, screen: true }) },
|
||||
'D (no trim line)': { ...real, panelChromeHtml: (p, s, o) => { const h = real.panelChromeHtml(p, s, o); return { ...h, after: h.after.replace(/<div class="trim[^>]*><\/div>/, '') } } },
|
||||
}
|
||||
for (const [name, M] of Object.entries(planted)) {
|
||||
const want = name[0]
|
||||
if (!run(M).errs.some((e) => e.startsWith(want) || (want === 'A' && e.startsWith('B')))) fails.push(`SELF-TEST: the planted defect "${name}" was not caught`)
|
||||
}
|
||||
|
||||
if (fails.length) { console.log('PANEL-SHEETS: FAIL'); for (const f of fails) console.log(' - ' + f); process.exitCode = 1 }
|
||||
else console.log(`PANEL-SHEETS: PASS — ${r.checked} size × fill cases (60x96 stacked, 66x66 side by side, one sheet; glue and bleed): the sheets tile the finished poster, the slices tile or repeat exactly the join, the marks follow the fill, and the Bless 5x8ft takes its bands and marks from panelSheets.js; ${Object.keys(planted).length} planted defects caught.`)
|
||||
|
|
@ -240,6 +240,7 @@
|
|||
"check:popup-pan-controls": "node internal/check-popup-pan-clears-controls.mjs",
|
||||
"check:country-map-refetch": "node internal/check-country-map-refetch.mjs",
|
||||
"check:pane-config-reforward": "node internal/check-pane-config-reforward.mjs",
|
||||
"check:panel-sheets": "node internal/check-panel-sheets.mjs",
|
||||
"check:control-inset": "node internal/check-control-inset-measured.mjs",
|
||||
"check:viewport-meta": "node internal/check-viewport-meta.mjs",
|
||||
"check:viewport-meta:self-test": "VIEWPORT_META_SELFTEST=1 node internal/check-viewport-meta.mjs",
|
||||
|
|
@ -344,7 +345,7 @@
|
|||
"fix:sri": "node internal/fix-sri.mjs",
|
||||
"gallery": "node internal/generate-gallery.mjs",
|
||||
"gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:backlog-refs check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:auto-engine check:engine-contract check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:no-silent-engine:self-test check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge check:instance-config-keys check:frozen-labels check:plural-forms check:region-fill-repaint check:region-paint-single-source check:poster-print-fallback check:posters-exportable check:country-names check:reduced-motion check:no-dead-library check:universal-props check:readme-size check:tree-relative check:leaf-hints check:snippet-engine check:snippet-engine:self-test check:embed-base check:link-coverage check:no-public-binds check:shared-reached check:tree-weight",
|
||||
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-new-driver-references check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:dxlife check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch",
|
||||
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-new-driver-references check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:dxlife check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:panel-sheets check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch",
|
||||
"gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs",
|
||||
"internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs",
|
||||
"library-index": "node internal/generate-library-index.mjs",
|
||||
|
|
@ -422,6 +423,7 @@
|
|||
"check:wall-print-watch": "the Wall's HD poster print never prints silently incomplete: with fake timers and a fake frame, the watch counts photos as they load, counts the ones that could not, and reports both. Two-sided on planted printWatch copies. Headless.",
|
||||
"check:wall-no-bg-photos": "the Wall's store downloads no photos on its own (it used to test all 932, 132 MB, on every visit): the REAL store runs through Vite against the committed fixtures/pray-list-sample.json with fetch stubbed and every Image request counted. Two-sided with the old background test planted. Headless, no network.",
|
||||
"check:wall-pan": "the Wall pans and zooms with the pointer 1:1: the REAL WallCanvas methods (through Vite) with d3-zoom's transform arithmetic, over 12 fits and zooms: a pan moves every card exactly as far as the pointer, a zoom keeps the point under the pointer, toWorld inverts toScreen, and a fling never loses the wall. Two-sided: the old transform-inside-the-fit formulas are planted and caught. Headless.",
|
||||
"check:panel-sheets": "the shared panel geometry (library/components/poster/panelSheets.js) for posters printed on several sheets: 60x96 = 3 × 60×36 stacked and 66x66 = 2 × 36×66 side by side, 6 in joins, and a single sheet; the sheets tile the finished poster, glue slices tile and bleed slices repeat exactly the join, a bleed join prints a guide not a solid band, a screen preview always shows the red band, and the Bless 5x8ft takes its bands and marks from the module. Two-sided (4 planted defects). Headless.",
|
||||
"check:template-i18n": "the starter template every new plugin copies renders its text from the catalogue: profile B, server-rendered with the REAL template.json in en, ar and zh, takes its heading, note and sentence from the page's language. Two-sided with the English catalogue served as Arabic. Headless (SSR).",
|
||||
"check:coverage-consistent:chained": "every clause of check:coverage-consistent EXCEPT the link-verification age (VERIFY_MAX_AGE_DAYS set out of reach): the chains run what depends on the code and data, and leave what depends on the calendar to the on-demand run, so the gates cannot turn red on a date with no change.",
|
||||
"check:viewport-meta": "every generated page declares <meta name=\"viewport\"> with width=device-width. Without it a browser lays the page out at the 980px desktop fallback even when the viewport is set to 390, so a mobile probe silently measures a viewport no phone has and still passes (measured: innerWidth 980 without the tag, 390 with it). width=device-width is required specifically, because a meta carrying only initial-scale still lays out at 980. Checked on the BUILT tree, not the generator template. Fixed-size print sheets are exempt by their stated shape (a .stage with a three-digit px width), and a page that must render at a fixed desktop width carries a viewport-meta-ok: reason. 8 self-tests. Source-only, headless.",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@
|
|||
* SAME layout for the screen (screenPoster: what the wall shows is what prints). No Vue.
|
||||
*/
|
||||
import { blessingFor, POSTER, THEMES } from './blessings.js'
|
||||
// The sheets, their glue bands and the print marks: the shared panel geometry (also India's large posters).
|
||||
import { planPanels, screenLayout, panelChromeHtml, panelCss } from '../../../library/components/poster/panelSheets.js'
|
||||
|
||||
// Sheet sizes in inches (w × h of ONE printed page), how many stack vertically, and the border. The brief's
|
||||
// three: 3 × 4 ft (the default, a living-room wall), 2 × 3 ft (small), 5 × 8 ft (three glued panels).
|
||||
|
|
@ -33,6 +35,9 @@ export const DEFAULT_PRESET = '3x4ft'
|
|||
export const MIN_READABLE_PT = 6 // verse size below which a verse under every card is not readable
|
||||
export const STRIP_PT = 9 // the one-verse strip under each row, on the sizes that use it
|
||||
|
||||
// A preset as panels: its sheets stacked top to bottom, each join a blank GLUE band as deep as the border.
|
||||
const panelsOf = (P) => planPanels({ sheetW: P.sheetW, sheetH: P.sheetH, count: P.sheets, direction: 'vertical', joinIn: P.border, borderIn: P.border, joinFill: 'glue' })
|
||||
|
||||
const CARD_ASPECT = 0.8 // card width / height: a portrait card (name, photo, verse)
|
||||
const PT = 1 / 72 // one point in inches
|
||||
|
||||
|
|
@ -48,15 +53,14 @@ export function planPoster(presetId, n) {
|
|||
// priestly blessing, the journey from Abraham to the church and the closing line. All scale with the width.
|
||||
const headH = W * 0.075, tableH = W * 0.17
|
||||
const bannerH = headH + tableH, footerH = footerHeight(W)
|
||||
// Each sheet's content band, in the sheet's own coordinates: y0..y1.
|
||||
const bands = []
|
||||
for (let s = 0; s < P.sheets; s++) {
|
||||
const last = s === P.sheets - 1
|
||||
let y0 = b // sheet 1: outer border; the others: the glue band
|
||||
let y1 = P.sheetH - (last ? b : 0) // flat bottom edge, except the last sheet's border
|
||||
// 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).
|
||||
const panels = panelsOf(P)
|
||||
const bands = panels.sheets.map(({ s, last, content: { y0, y1 } }) => {
|
||||
const top = s === 0 ? bannerH : 0, bottom = last ? footerH : 0
|
||||
bands.push({ s, y0: y0 + top, y1: y1 - bottom, bannerY: s === 0 ? y0 : null, footerY: last ? y1 - footerH : null })
|
||||
}
|
||||
return { s, y0: y0 + top, y1: y1 - bottom, bannerY: s === 0 ? y0 : null, footerY: last ? y1 - footerH : null }
|
||||
})
|
||||
// The largest card (fewest columns) whose grid holds n when rows are fitted to each band; `strip` is the
|
||||
// height of the verse strip under each row (0 = the card layout, a verse on every card).
|
||||
const fit = (strip) => {
|
||||
|
|
@ -87,7 +91,7 @@ export function planPoster(presetId, n) {
|
|||
preset: P, presetId: PRESETS[presetId] ? presetId : DEFAULT_PRESET, n, cols, rowsPer, cw, ch, bands, cards,
|
||||
layout: stripLayout ? 'strip' : 'cards', strips,
|
||||
bannerH, footerH, headH, tableH, contentW: W,
|
||||
posterW: P.sheetW, posterH: P.sheetH * P.sheets - (P.sheets - 1) * b, // after gluing: 60 × 96 for 5x8ft
|
||||
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 },
|
||||
}
|
||||
}
|
||||
|
|
@ -173,12 +177,11 @@ const PX_IN = 96 // CSS px per inch: the screen world is the print, in CSS px
|
|||
*/
|
||||
export function screenPoster(groups, presetId = DEFAULT_PRESET, texts = {}) {
|
||||
const { plan, css, pages } = posterParts(groups, presetId, texts, false)
|
||||
const P = plan.preset, step = P.sheetH + SCREEN_GAP_IN
|
||||
const sheets = pages.map((pg, s) => ({ x: 0, y: s * step * PX_IN, w: P.sheetW * PX_IN, h: P.sheetH * PX_IN }))
|
||||
const L = screenLayout(panelsOf(plan.preset), { gapIn: SCREEN_GAP_IN, pxPerIn: PX_IN }), step = L.step
|
||||
return {
|
||||
plan, css, pxPerIn: PX_IN,
|
||||
worldW: P.sheetW * PX_IN, worldH: (P.sheets * P.sheetH + (P.sheets - 1) * SCREEN_GAP_IN) * PX_IN,
|
||||
sheets,
|
||||
worldW: L.worldW, worldH: L.worldH,
|
||||
sheets: L.sheets.map(({ x, y, w, h }) => ({ x, y, w, h })),
|
||||
cards: plan.cards.map((c) => ({ i: c.i, x: c.x * PX_IN, y: (c.s * step + c.y) * PX_IN, w: c.w * PX_IN, h: c.h * PX_IN })),
|
||||
chromeHtml: pages.map((pg, s) => `<section class="sheet" style="position:absolute;left:0;top:${s * step}in">${pg.chrome}</section>`).join(''),
|
||||
}
|
||||
|
|
@ -217,9 +220,10 @@ function posterParts(groups, presetId, texts, withCards = true) {
|
|||
return `<div class="card" style="left:${f(c.x)};top:${f(c.y)};width:${f(c.w)};height:${f(c.h)}">`
|
||||
+ `<div class="nm${nf.wrap ? ' w2' : ''}" dir="auto" style="font-size:${nf.pt}pt${nf.wrap ? ';-webkit-line-clamp:' + nf.lines : ''}">${esc(g.name || '')}</div>${img}${verse}</div>`
|
||||
}
|
||||
const panels = panelsOf(P)
|
||||
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>` : ''
|
||||
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
|
||||
|
|
@ -247,11 +251,10 @@ function posterParts(groups, presetId, texts, withCards = true) {
|
|||
+ `<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>`
|
||||
+ `</footer>` : ''
|
||||
const trim = !last && P.sheets > 1 ? '<div class="trim"></div>' : ''
|
||||
// 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)
|
||||
return `<div class="vstrip" dir="auto" style="left:${f(t.x)};top:${f(t.y)};width:${f(t.w)};height:${f(t.h)}">${esc(v.text)}<span class="vref">${esc(v.ref)}</span></div>` }).join('')
|
||||
return { cards: withCards ? plan.cards.filter((c) => c.s === s).map(card).join('') : '', chrome: `${glue}${head}${strips}${foot}${trim}<div class="tag">${s + 1} / ${P.sheets}</div>` }
|
||||
return { cards: withCards ? plan.cards.filter((c) => c.s === s).map(card).join('') : '', chrome: `${marks.before}${head}${strips}${foot}${marks.after}` }
|
||||
})
|
||||
const css = `@page{size:${P.sheetW}in ${P.sheetH}in;margin:0}
|
||||
*{box-sizing:border-box}html,body{margin:0;padding:0}
|
||||
|
|
@ -293,8 +296,6 @@ body,.poster{font-family:Georgia,'Times New Roman',serif;color:#1c1c1c;-webkit-p
|
|||
.card .vs{font-size:${vPt}pt;line-height:1.15;font-style:italic}
|
||||
.card .vs,.card .rf{flex:none}
|
||||
.card .rf{font-size:${rPt.toFixed(1)}pt;color:#777;margin-top:1pt}
|
||||
.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}`
|
||||
${panelCss(panels)}`
|
||||
return { plan, css, pages, banner }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1473,7 +1473,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-S2g83dVn7Vb2ynBlin0ySD8rO/cFEdA60yPYJ+04ObqzXmiojWOZ5ufGPj4URG9t",
|
||||
"integrity": "sha384-+0FF2Yam1UsHfo2turc9M9IORYEfg/eKrOjl3VLUymNUK7P8/AnL8/p9zlrf/NEh",
|
||||
"elements": [
|
||||
"wall-of-people-groups"
|
||||
],
|
||||
|
|
@ -1492,7 +1492,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-S2g83dVn7Vb2ynBlin0ySD8rO/cFEdA60yPYJ+04ObqzXmiojWOZ5ufGPj4URG9t\" 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-+0FF2Yam1UsHfo2turc9M9IORYEfg/eKrOjl3VLUymNUK7P8/AnL8/p9zlrf/NEh\" 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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1473,7 +1473,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-S2g83dVn7Vb2ynBlin0ySD8rO/cFEdA60yPYJ+04ObqzXmiojWOZ5ufGPj4URG9t",
|
||||
"integrity": "sha384-+0FF2Yam1UsHfo2turc9M9IORYEfg/eKrOjl3VLUymNUK7P8/AnL8/p9zlrf/NEh",
|
||||
"elements": [
|
||||
"wall-of-people-groups"
|
||||
],
|
||||
|
|
@ -1492,7 +1492,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-S2g83dVn7Vb2ynBlin0ySD8rO/cFEdA60yPYJ+04ObqzXmiojWOZ5ufGPj4URG9t\" 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-+0FF2Yam1UsHfo2turc9M9IORYEfg/eKrOjl3VLUymNUK7P8/AnL8/p9zlrf/NEh\" 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-S2g83dVn7Vb2ynBlin0ySD8rO/cFEdA60yPYJ+04ObqzXmiojWOZ5ufGPj4URG9t" crossorigin="anonymous"></script>
|
||||
<script src="../wall-of-people-groups.js" integrity="sha384-+0FF2Yam1UsHfo2turc9M9IORYEfg/eKrOjl3VLUymNUK7P8/AnL8/p9zlrf/NEh" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
Loading…
Reference in a new issue