About me

Selected work

Selected writing

Experience

Skills

Education

Archive

One résumé, two presentations — print CSS as progressive enhancement

Technical note · 2026

TL;DR: one content model, expressed twice — an interactive screen and a printable page, picked by CSS media targeting instead of two hand-maintained résumé documents. A dedicated /resume route covers “don’t make me learn your UI”; a fixed PDF covers “just give me a file.”

CSS was built, from day one, to let one document render as more than one thing depending on how it’s asked for. CSS Zen Garden (Dave Shea, 2003) is still the cleanest proof of that: dozens of visually unrelated designs, one shared HTML file, nothing but the stylesheet swapped out. Twenty-plus years on, most sites still treat that as a party trick rather than infrastructure — one visual target for screen, and print left to whatever the browser does by default, if it’s considered at all. Media queries are the part of CSS that makes the Zen Garden pitch practical rather than a demo: the same markup, different rules, picked automatically by context.

I ran into the unglamorous version of that pitch building this site’s home page. It’s a WebGL background, hash-target panels, and screen-only chrome — good for a portfolio, useless to anyone who wants to print my résumé, feed it to an ATS, or open a stable link without loading a canvas. The content underneath (About Me, Experience, Skills, Education) didn’t need to change. How it’s expressed per medium did. So rather than maintain a second résumé document by hand, I pushed the split down into CSS and let the same markup carry both jobs.

Concretely, that single content model is four Svelte components — AboutMe,

Experience, Skills, Education — each written once and imported wherever it needs to render. The home route composes them alongside the interactive panels and WebGL chrome; /resume composes the same four, in résumé order, with none of that. Svelte isn't the load-bearing part here — React, Vue, or any component framework that lets you write markup once and instantiate it in more than one place would do the same job. The actual idea is “single source of truth per section, multiple call sites”; a component is just the mechanism. The PDF, in turn, isn't a fifth document authored separately from the page: it's a static snapshot exported from /resume via Print / Save as PDF, which goes through the exact printstyle.css described below. One canonical markup, one canonical print stylesheet, and one captured PDF artifact that has to be regenerated when either of those changes.

Screen CSS stays on screen

Layout for the interactive site lives behind an @media screen query, so print doesn’t have to fight most of it rule-by-rule — those rules just don’t apply. The résumé route uses the same trick for its own stacked layout. When you print either page, the screen-scoped layout drops out first; the print stylesheet only has to reset the remaining global and runtime assumptions.

Print gets to undo everything screen did

printstyle.css is linked with media="print" on both the home layout and /resume, and its job is the mirror image of the section above: walk back every screen-only assumption before the résumé is even legible on paper. Concretely — canvases (#particles, #udgy, #speed, and any raw <canvas>) get display: none, because there's no such thing as a printed WebGL frame; the absolutely-positioned #cv panel gets forced back to position: static so it stops being pinned to a screen-sized viewport; body copy swaps to a plain serif at a fixed point size instead of whatever scale the screen layout uses; section blocks get page-break-inside: avoid; and paragraphs get their own page-break-inside, widow, and orphan rules so a job entry or bullet list is less likely to get guillotined across a page boundary. None of it is exotic CSS — it's a systematic “assume nothing about this element's screen state survives” pass, applied once and left alone.

The developer general’s warning, 2026 edition

I’d rather say this up front than let the tidy explanation above imply more than it delivers. Page-break handling is still genuinely inconsistent across browser print engines — a paragraph that stays intact in one browser isn’t guaranteed to behave identically in another. The downloadable PDF is a snapshot, not a live view: if I update /resume and forget to regenerate the file, the two quietly drift apart (a reminder to myself as much as a disclosure to you). And no CSS, print or otherwise, guarantees correct ATS parsing — but that’s the wrong layer to look for the guarantee in anyway. For the live

/resume route, what helps is a well-structured, progressively-enhanced semantic document underneath — real headings, real <address>/paragraph elements, a sane linear reading order — with none of that depending on any stylesheet being present. For the uploaded PDF path, the parser is looking at an exported artifact, so the best I can do is preserve that source order and avoid pretending a stylesheet can make every ATS happy. The print stylesheet is a nicety on top of the semantic document, not a prerequisite for it.

Why /resume gets its own address

A shareable link shouldn’t require the person on the other end to know they have to print an SPA panel to get anything useful out of it. /resume stacks the printable sections without WebGL or navigation chrome, exposes explicit Download PDF and Print actions, and carries its own Open Graph card so the link previews correctly wherever it’s dropped. The fixed file

/sebastian-ferreyra-pons-resume.pdf covers the fastest path — recruiters who want a file, not a browser print dialog.

So this was never really “build two résumés.” It’s semantic HTML written once, then expressed for an interactive screen, a print medium, and a distraction-free page — the same separation of structure from presentation I’d tell a client to do, pointed at my own home page for once.

One content model. Two presentations, chosen by media, not duplication. A print stylesheet is a delivery format, not a fallback nobody tests.

If you’re doing something similar to your own SPA-shell portfolio and would rather start from a working reset list than reverse-engineer one from a stylesheet, ask — happy to share exactly what

printstyle.css resets and why, beyond what's summarized above.

— Seb · get in touchferreyrapons.com

← Selected writing Home Résumé