JMR Advisory: a portfolio that moves
- JS shipped
- 88 KB gz
- JS budget
- <200 KB
- a11y gates
- axe + LH in CI
Why a portfolio at all
After eight years in pharmacy benefit management — most recently as Lead, Rebates Data & Reporting at Abarca Health — I founded JMR Advisory to do data automation and web design from Puerto Rico. A portfolio site is the cheapest, highest-leverage artifact in that pivot: it filters inbound work, demonstrates the craft itself, and forces a discipline of editorial restraint that client work rarely gives you.
I wanted the site to be the case study — to render at SOTD-credible level, to ship in a sub-200KB JS budget, and to stay accessible end-to-end. None of that is novel; all of it is hard.
The brief
A self-imposed brief, with non-negotiables:
- A hero that earns the scroll — but readable underneath. No mystery-meat navigation, no infinite scroll for its own sake.
- Sub-200KB total JS gzipped. Lighthouse perf gates run on every PR.
- WCAG-compliant, keyboard-first. axe runs in CI. Reduced-motion is honored at the design-token layer, not as an afterthought.
- Static-first. Astro 6 with Preact 10 islands only where the page actually needs interaction. The hero is the one exception.
The Loom
The hero is a four-strand weave that breathes — career visualized as data,
engineering, design, and business strands intersecting at year markers.
It’s an OGL canvas (not three.js — the bundle math doesn’t work with three)
running one geometry and two shaders: a thread shader with a sustained
breath glow, and an intersection shader that blooms additively at event
positions. Scroll progress drives a uUnfurl uniform that animates the
strands in from collapsed to woven.
Reduced-motion or no-WebGL falls back to a static SVG version of the same composition with CSS pulse animations — same visual grammar, zero canvas cost.
Measured numbers from the build:
- JS shipped: 88 KB gz (44% of the 200 KB budget)
- Static pages: 4 —
/,/work/, plus two case-study routes - Total bundle deltas this phase: zero new JS islands
What’s still hard
Honest list:
- The performance budget is ratcheted, not relaxed. CI Lighthouse gates were temporarily set to perf ≥90, LCP ≤3s, TBT ≤250ms when the WebGL hero shipped. Tightening them back toward the original 99-across-the-board is a tracked next milestone, not an open question.
- The reduced-motion fallback shipped before the audio layer. Placeholder ambient audio is intentionally muted by default; the fallback exists, but the polished version is later in the roadmap.
- Lenis is scaffolded but
initLenis()is never called. ScrollTrigger runs against native scroll for now — fine, just less silky.
These are tradeoffs, not bugs. The site shipped because the calls were made deliberately and the regressions are scoped.
What’s next
Phase 3 (the route you’re reading right now) seeded the editorial layer: career timeline, skills mosaic, project grid, this case study route. Phase 4 adds an in-browser SQL playground over a synthetic PBM dataset — the smallest possible “show, don’t tell” for the analytics-engineering side of the practice. Phases 5–6 are content, polish, and launch.
The site, like the practice, ships in waves.