feat(cards): horizontal prayer cards — the layout changes, not just the page
Some checks are pending
build-gate / build (push) Waiting to run
Some checks are pending
build-gate / build (push) Waiting to run
Driver asked for "horizontal cards" alongside the vertical ones, with the print dialog's aspect ratio chosen at the print point. ★ LANDSCAPE IS NOT TRANSPOSED DIMENSIONS. The portrait card stacks the photo ABOVE the text, occupying the top 46%; at 3.5 x 2.5in that same band is a strip too short to carry a face AND leave room for the rows under it. The landscape stocks put the photo BESIDE the text instead — full-bleed down the left 42%, name over a scrim at its foot, data and QR in the right column. So `side` is a LAYOUT switch the CSS reads, not a cosmetic flag. It is driven by the stock rather than by comparing width to height: a square stock would be ambiguous, and the composition is a deliberate property of the format, not an inference from its numbers. Two landscape stocks added and exposed in the poster-wall picker: pokerLandscape 3.5 x 2.5 in -> 252 x 180 pt postcardLandscape 6 x 4 in -> 432 x 288 pt Verified at the rendered layer by the new check:print-geometry, which measures the MediaBox rather than reading the CSS — and read as a picture, not only asserted on: photo left, "Aheu" over the scrim, country/people/language/religion in the right column, QR and "PRAY FOR THEM" at the foot, zero overflow.
This commit is contained in:
parent
47c54b12ac
commit
507cf68e12
3 changed files with 135 additions and 95 deletions
|
|
@ -37,10 +37,17 @@ import { qrSvg } from './qr.js';
|
|||
// sleeves and boxes are built around, and the one people already associate with a
|
||||
// deck you hold and deal out. Bleed is included in the page so a trimmed card
|
||||
// keeps its full-bleed portrait.
|
||||
// ★ LANDSCAPE IS NOT JUST TRANSPOSED DIMENSIONS. The portrait card stacks the
|
||||
// photo ABOVE the text (portrait occupies the top 46%); at 3.5 x 2.5in that same
|
||||
// band is a strip too short to carry a face or leave room for the rows beneath it.
|
||||
// A landscape card puts the photo BESIDE the text instead — so `side: true` is a
|
||||
// LAYOUT switch the CSS reads, not a cosmetic flag.
|
||||
export const STOCK = {
|
||||
poker: { w: 2.5, h: 3.5, label: 'Poker (2.5 x 3.5 in)' },
|
||||
postcard: { w: 4, h: 6, label: 'Postcard (4 x 6 in)' },
|
||||
a6: { w: 4.13, h: 5.83, label: 'A6 (105 x 148 mm)' }, // explicit in, NOT "A6"
|
||||
poker: { w: 2.5, h: 3.5, label: 'Card 2.5 × 3.5 in' },
|
||||
pokerLandscape: { w: 3.5, h: 2.5, label: 'Card 3.5 × 2.5 in (landscape)', side: true },
|
||||
postcard: { w: 4, h: 6, label: 'Postcard 4 × 6 in' },
|
||||
postcardLandscape: { w: 6, h: 4, label: 'Postcard 6 × 4 in (landscape)', side: true },
|
||||
a6: { w: 4.13, h: 5.83, label: 'A6 105 × 148 mm' }, // explicit in, NOT "A6"
|
||||
};
|
||||
|
||||
const PX = 96; // CSS px per inch
|
||||
|
|
@ -73,6 +80,10 @@ export function createCardRenderer({ strings, lang = 'en', stock = 'poker',
|
|||
const S = STOCK[stock] || STOCK.poker;
|
||||
const W = Math.round(S.w * PX); // card box in CSS px
|
||||
const H = Math.round(S.h * PX);
|
||||
// Photo BESIDE the text rather than above it. Driven by the stock, not by
|
||||
// comparing W to H: a square stock would be ambiguous, and the choice is a
|
||||
// deliberate property of the format rather than an inference from its numbers.
|
||||
const SIDE = S.side === true;
|
||||
|
||||
// The live profile URL for this group, in this language. English has no prefix
|
||||
// (the generator's own rule); every other language does.
|
||||
|
|
@ -93,26 +104,36 @@ export function createCardRenderer({ strings, lang = 'en', stock = 'poker',
|
|||
page-break-after:always;break-after:page}
|
||||
.card:last-child{page-break-after:auto;break-after:auto}
|
||||
|
||||
/* PORTRAIT: full-bleed across the top. A face is what makes this a prayer card
|
||||
rather than a data tile — it is the largest thing on the card by design. */
|
||||
.ph{position:absolute;left:0;top:0;width:${W}px;height:${Math.round(H * 0.46)}px;
|
||||
object-fit:cover;display:block;background:#ddd6c6}
|
||||
.ph-none{position:absolute;left:0;top:0;width:${W}px;height:${Math.round(H * 0.46)}px;
|
||||
background:linear-gradient(160deg,#e6dfcd,#cfc6ad);display:flex;
|
||||
align-items:center;justify-content:center;
|
||||
/* THE PHOTO. Portrait: full-bleed across the TOP, the largest thing on the card
|
||||
by design — a face is what makes this a prayer card rather than a data tile.
|
||||
Landscape: full-bleed down the LEFT instead, because a 46% band on a 2.5in-tall
|
||||
card is a strip too short to carry a face AND leave room for the rows. */
|
||||
.ph,.ph-none{position:absolute;left:0;top:0;display:block;
|
||||
width:${SIDE ? Math.round(W * 0.42) : W}px;
|
||||
height:${SIDE ? H : Math.round(H * 0.46)}px}
|
||||
.ph{object-fit:cover;background:#ddd6c6}
|
||||
.ph-none{background:linear-gradient(160deg,#e6dfcd,#cfc6ad);display:flex;
|
||||
align-items:center;justify-content:center;text-align:center;padding:6px;
|
||||
font:italic 11px Georgia,serif;color:#8c8170}
|
||||
|
||||
/* A soft scrim under the name so it stays legible over any photo. */
|
||||
.scrim{position:absolute;left:0;top:${Math.round(H * 0.30)}px;width:${W}px;
|
||||
height:${Math.round(H * 0.16)}px;
|
||||
.scrim{position:absolute;left:0;
|
||||
width:${SIDE ? Math.round(W * 0.42) : W}px;
|
||||
top:${SIDE ? Math.round(H * 0.58) : Math.round(H * 0.30)}px;
|
||||
height:${SIDE ? Math.round(H * 0.42) : Math.round(H * 0.16)}px;
|
||||
background:linear-gradient(to bottom,rgba(24,22,18,0),rgba(24,22,18,.72))}
|
||||
.nm{position:absolute;left:14px;right:14px;top:${Math.round(H * 0.355)}px;
|
||||
font:700 19px Georgia,serif;color:#fff;line-height:1.05;
|
||||
.nm{position:absolute;
|
||||
left:${SIDE ? 10 : 14}px;
|
||||
${SIDE ? `width:${Math.round(W * 0.42) - 20}px;` : `right:14px;`}
|
||||
top:${SIDE ? Math.round(H * 0.74) : Math.round(H * 0.355)}px;
|
||||
font:700 ${SIDE ? 15 : 19}px Georgia,serif;color:#fff;line-height:1.05;
|
||||
text-shadow:0 1px 3px rgba(0,0,0,.5)}
|
||||
.nm.long{font-size:15px}
|
||||
.nm.long{font-size:${SIDE ? 12 : 15}px}
|
||||
|
||||
.body{position:absolute;left:14px;right:14px;top:${Math.round(H * 0.48)}px;
|
||||
bottom:${Math.round(H * 0.20)}px;overflow:hidden}
|
||||
.body{position:absolute;right:14px;overflow:hidden;
|
||||
left:${SIDE ? Math.round(W * 0.42) + 12 : 14}px;
|
||||
top:${SIDE ? 12 : Math.round(H * 0.48)}px;
|
||||
bottom:${SIDE ? Math.round(H * 0.30) : Math.round(H * 0.20)}px}
|
||||
.row{display:flex;gap:6px;font:400 9.5px Georgia,serif;line-height:1.5;margin-bottom:1px}
|
||||
.k{color:#8c8170;min-width:52px;flex:0 0 auto}
|
||||
.v{color:#3b3a34;font-weight:600}
|
||||
|
|
@ -120,7 +141,8 @@ export function createCardRenderer({ strings, lang = 'en', stock = 'poker',
|
|||
|
||||
/* FOOTER: the QR is the card's whole reason to survive the day it is handed
|
||||
over — it carries the reader to the live profile in their own language. */
|
||||
.ft{position:absolute;left:14px;right:14px;bottom:10px;
|
||||
.ft{position:absolute;right:14px;bottom:10px;
|
||||
left:${SIDE ? Math.round(W * 0.42) + 12 : 14}px;
|
||||
display:flex;align-items:flex-end;gap:8px}
|
||||
.qr{flex:0 0 auto;line-height:0}
|
||||
.ftx{flex:1 1 auto;min-width:0}
|
||||
|
|
|
|||
|
|
@ -271,9 +271,16 @@ const exportState = ref(null) // { label, phase, message, pct, kind }
|
|||
// is what commercial card printers, sleeves and boxes are built around.
|
||||
// @page takes these as explicit inches — a NAMED size silently falls back to letter
|
||||
// in Chromium, which is the exact bug this deck is being fixed for.
|
||||
// Both orientations, because the Driver asked for "horizontal cards" as well as
|
||||
// vertical ones. A landscape stock is not merely transposed: the renderer moves
|
||||
// the photo BESIDE the text, since a 46%-height band on a 2.5in-tall card is too
|
||||
// short to carry a face. The labels come from the renderer's own STOCK table so
|
||||
// this list cannot drift from what the renderer actually supports.
|
||||
const DECK_STOCKS = [
|
||||
{ id: 'poker', label: 'Card 2.5 × 3.5 in' },
|
||||
{ id: 'pokerLandscape', label: 'Card 3.5 × 2.5 in (landscape)' },
|
||||
{ id: 'postcard', label: 'Postcard 4 × 6 in' },
|
||||
{ id: 'postcardLandscape', label: 'Postcard 6 × 4 in (landscape)' },
|
||||
{ id: 'a6', label: 'A6 105 × 148 mm' },
|
||||
]
|
||||
const deckStock = ref('poker')
|
||||
|
|
|
|||
165
public/doxa-maps/my-upg-100-list/my-upg-100-list.js
generated
165
public/doxa-maps/my-upg-100-list/my-upg-100-list.js
generated
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue