// 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. export function buildBody(deckHtml, { id, count, W, H, t }) { let n = 0 let body = deckHtml.replace(/]*class="print-slide-container"[^>]*)>/g, (_, attrs) => { const i = n++ return `
` }) // 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 } } // 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 }) => `
1 / ${n}
${body}
` const css = (W, H) => ` .deck-slides { display: flex; flex-direction: column; gap: 1.5rem; } .deck-slide { container-type: inline-size; position: relative; width: 100%; /* NOT aspect-ratio: 980/552. Slidev's frame is 552px tall and 12 of this deck's 16 slides hold MORE than that — up to 672px — so a rigid 16:9 box silently cuts the bottom off three quarters of the deck. Slidev clips them too; nobody noticed because until now the slides were PNGs of themselves. A reading surface may not lose content: the card is as tall as the slide's real content, measured (--deck-h) and scaled (--deck-k). */ height: calc(var(--deck-h, ${H}) * var(--deck-k, 1) * 1px); overflow: hidden; border-radius: .75rem; box-shadow: 0 1px 3px rgba(0,0,0,.25), 0 8px 24px rgba(0,0,0,.18); } /* The card is dark because THE DECK is dark, and not by accident: its source declares colorSchema:dark, so Slidev never compiles a light palette at all. A light/dark toggle here was tried and reverted — removing the .dark class turned the card white while the deck's text stayed rgb(232,232,232): light on white, unreadable. Slidev's 37 .dark rules exist in the bundle, but they are not what makes THIS deck dark; the theme's base colours are, unconditionally. And the slides hard-code absolute utilities (text-gray-400, bg-gray-900) that would not flip with a theme anyway. A light deck means re-authoring the decks, not toggling a class. The chrome around it still follows the SITE's theme. */ .deck-slide { background: #0b0b0b; border: 1px solid rgba(255,255,255,.10); } /* The slide is a
, and the site's .prose gives every figure margin: 2em 0. The card is exempt from the boundary guard on purpose (deck- classes are ours), so that margin lands on it — and on the zoom modal it pushed the fixed element 32px down, past the bottom of the viewport on a large screen. Specificity (0,2,0) to beat .prose figure (0,1,1). */ .deck-view .deck-slide { margin: 0; } .deck-slide::after { color: rgba(255,255,255,.35); background: rgba(0,0,0,.35); } /* Slidev hard-codes 980x552 inline. The slide has to be SCALED to its box, or the content is simply clipped: on a 390px phone the box is 328px wide while the content stays 980px — two thirds of every slide cropped away, with no error anywhere. NOT scale(calc(100cqw / 980)): scale() demands a , and calc() cannot divide a length by a length, so that declaration is silently DROPPED. It merely looks right while the column happens to measure ~980px. The ratio must be computed (ResizeObserver) and published as a custom property. Bound to [data-js] for (0,4,0): Slidev's own