diff --git a/library/packet/packet-render.js b/library/packet/packet-render.js index ac4f70533..428db099c 100644 --- a/library/packet/packet-render.js +++ b/library/packet/packet-render.js @@ -376,7 +376,11 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de .pray-head{position:absolute;inset-inline-end:${M}px;display:grid;grid-template-columns:repeat(${PRAY_COLS.length},${PRAY_CELL}px); height:${PRAY_HEAD_H - 6}px;align-items:end;font:700 15px/1.2 Georgia,serif;color:#6f6a5c;text-align:center} .pray-head span{text-wrap:balance;padding:0 0 5px;border-bottom:2px solid #3b463d} - .fd-top{position:absolute;left:${M}px;top:${M + HEAD}px;width:${CW.toFixed(0)}px;height:${FD_TOP_H}px;overflow:hidden} + /* Page 1 of the Full detail list: the explanation, then the head row and the rows right under it (listPages). */ + .fd-first{position:absolute;left:${M}px;top:${M + HEAD}px;width:${CW.toFixed(0)}px} + .fd-top{max-height:${FD_TOP_H}px;overflow:hidden} + .fd-rows{position:relative} + .fd-rows .pray-head{inset-inline-end:0} .hd-r{position:absolute;right:${M}px;top:54px;font:400 19px Georgia,serif;color:#8c8170} /* ⚠ box-sizing:border-box IS LOAD-BEARING HERE, NOT HOUSEKEEPING. Without it the row's height is the CONTENT box and the padding + border are added OUTSIDE it, so the row renders taller @@ -1191,7 +1195,14 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de PRAY_COLS.map((key) => `${esc(t(key))}`).join('')}`; for (let start = 0, page = 0; start < groups.length; page += 1) { const per = page === 0 ? FD_FIRST : PER_PAGE; - const rowsTop = page === 0 ? FD_ROWS_TOP : M + HEAD; + // ★ PAGE 1'S ROWS FOLLOW THE EXPLANATION (2026-10-03). The explanation reserved a fixed FD_TOP_H and the + // rows sat below the reserve, so every language shorter than the tallest left a gap above the head row + // (0.36-1.30 in, measured in all 11). Now the explanation (at most FD_TOP_H) and the rows share one + // column (.fd-first), and the rows are placed inside .fd-rows, right under it. The page still holds + // FD_FIRST rows, sized for the full reserve, so a shorter explanation leaves the room at the page's foot. + // The rows stay absolutely placed (see below), only their origin moves: no row can reflow to a page. + const rowsTop = page === 0 ? PRAY_HEAD_H : M + HEAD; + const left = page === 0 ? 0 : M; const slice = groups.slice(start, start + per); // ★ ONE FULL-WIDTH ROW PER GROUP. The column index is gone: `top` advances by the row, and the // three cells sit inside it. Absolute positioning is kept rather than switching to flow layout @@ -1208,7 +1219,7 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de const img = src ? `` : `
${esc(t('no_photo'))}
`; - return `
${img}
${i + 1}. ${esc(g.name)}
@@ -1217,15 +1228,17 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de
${PRAY_COLS.map(() => '').join('')}
`; }).join(''); - const first = page === 0 - ? `
${explainTop()}${explainBand(groups.length, + const rowsH = PRAY_HEAD_H + slice.length * (CH + GUT) - GUT; + const body = page === 0 + ? `
${explainTop()}${explainBand(groups.length, { w: PRAY_COLS.length * PRAY_CELL, cols: `repeat(${PRAY_COLS.length},${PRAY_CELL}px)`, gap: 0, cell: 'xb-cell xb-cell-fd' })}
` - : ''; + + `
${prayHead(0)}${cards}
` + : `${prayHead(rowsTop - PRAY_HEAD_H)}${cards}`; pages.push(sheet(`
${esc(zone.label || zone.id)}
${esc(fill(t('list_header'), { from: num(start + 1), to: num(Math.min(start + per, groups.length)), count: num(groups.length) }))}
- ${first}${prayHead(rowsTop - PRAY_HEAD_H)}${cards}`)); + ${body}`)); start += per; } return pages;