// Build the deck's reading surface: two views over ONE DOM.
//
// slider — one slide at a time, arrows / Space / index. The default (decided 2026-07-14).
// read — every slide stacked as a card. Ctrl+F works, text is selectable, the page
// prints, a screen reader walks it.
//
// Both views share the same nodes: the capture already gives one .print-slide-container
// per slide, which is the structural equivalent of the old carousel's . Every slide
// is ALWAYS in the DOM (so it is always indexable); the mode only changes what the reader
// reaches with Ctrl+F — 2.1k words stacked, ~30 in slider. That is the price of the
// default, and it is stated rather than buried.
// EL ANCLA SE EMITE EN EL HTML; NO SE ASIGNA EN EL SCRIPT.
//
// `s.id = 'slide-' + (k+1)` vivía en deck.js, y por eso ninguna slide era direccionable: el
// navegador resuelve el hash de la URL ANTES de que el script corra, así que `#slide-7` no
// encontraba nada — y en modo slider la slide destino está en `display:none`, o sea que ni
// scroll había que hacer. Un id que aparece después de que el navegador lo busque no es un id:
// es un adorno. Los cuatro términos del glosario que enlazan a este deck aterrizaban todos en
// la portada por esto, no porque a nadie se le ocurriera escribir el fragmento.
//
// En el marcado, el ancla existe para el hash, para el rastreador y para quien no tiene JS.
export function buildBody(deckHtml, { id, count, W, H, t, anchors = [] }) {
// Las anclas se leen de la FUENTE y las slides se cuentan en la CAPTURA. Si los dos números
// no coinciden, la lista está desalineada y cada ancla nombra a la slide de al lado — en
// silencio, que es el único fallo de este fichero que un lector no podría ver.
if (anchors.length && anchors.length !== count) {
throw new Error(`la fuente declara ${anchors.length} slides y la captura contó ${count}: las anclas quedarían pegadas a la slide equivocada`)
}
let n = 0
let body = deckHtml.replace(/
]*class="print-slide-container"[^>]*)>/g, (_, attrs) => {
const i = n++
// Dos direcciones para una tarjeta, y hacen falta las dos:
// · `slide-N` — TODA slide es direccionable sin anotar nada. Es posicional a propósito:
// sirve para «cópiame la URL de lo que estoy viendo», donde el número ES lo que se mira.
// · el ancla nombrada — la que se escribe en el léxico, porque sobrevive a un reordenado.
// Un id por elemento, así que la nombrada va en un vacío DENTRO de la figure: el hash
// resuelve al mismo sitio y el JS sube al .deck-slide que la contiene.
const a = anchors[i]
const named = a ? `` : ''
return `${named}
`
})
// Each .print-slide-container is a top-level sibling, so the figure closes right before
// the next one opens, and once at the end.
const parts = body.split(' ('').join('')
if (n !== count) throw new Error(`envolví ${n} slides pero la captura contó ${count}`)
return { html: chrome(body, { n, t }), css: css(W, H), js: js(W, H), slides: n, anchors }
}
// TWO layers, deliberately.
//
// .deck-view — the site's UI around the deck: bar, index, switcher, backdrop. It
// belongs to the PAGE, so it inherits the page's colours and follows the
// site's theme.
// .deck-read — the boundary. It wraps ONLY the captured DOM, and carries `dark`
// because the deck's own world is dark.
//
// Nesting the chrome inside .deck-read (as this first did) makes it inherit the DECK's
// palette — near-white text — while it is painted on the SITE's background: an index
// nobody can read on a light page. The boundary wraps what was captured, never the
// interface we build around it.
const chrome = (body, { n, t }) => `