Personal project · Three.js, Windows XP and this résumé
If you came in through the 3D office, you're looking at it right now: this page is open in Internet Explorer, inside Windows XP, on the office monitor. On a phone, it opens in a pocket version of Internet Explorer instead.
The problem
A PDF can say “web developer” without proving it. I wanted the portfolio itself to be the proof, and the résumé to stay readable, printable and indexable for people who don't have time for a 3D scene.
What I built
Two apps that talk to each other.
The room. A 3D office in Three.js, TypeScript and Vite. The model went from 31.4 MB to 1.54 MB with Draco-compressed geometry and re-encoded textures. The camera moves between named keyframes (room, desk, monitor) with tween.js, and there's a free-camera mode.
The screen. A working Windows XP runs in an iframe that CSS3DRenderer places on the monitor. The screen mesh uses an invisible material that still writes depth, so it cuts the hole the iframe shows through and the bezel keeps hiding the edges as the camera moves. Size and angle come from the mesh's own vertices, and the model is scaled 1000x so CSS3D works in real pixels and text stays sharp.
The OS. A trimmed fork of win32.run (SvelteKit). I removed unused programs and their orphaned libraries, cut static assets from 239 MB to about 26 MB, and moved every CDN dependency into the project, since dragging and resizing windows depended on a CDN copy of jQuery UI.
The bridges. The XP forwards mouse and keyboard events to the room with postMessage, which is how the scene knows someone is using the computer. The language button travels the other way: room, XP, then this page.
The phone version. Phones skip the 3D, and the three.js bundle with it, and open this résumé inside a pocket Internet Explorer in the style of Windows Mobile 2003, with its own back button, menus and language switch. If WebGL fails on a computer, the loader says so and links the résumé instead of showing a blank page.
One source. The pages, the PDFs and the text search engines read all come from one content file. A script writes the site in English and Portuguese, and Playwright prints the PDFs from it.
The deploy. Two Hostinger apps, a static Vite build and a Node server for the XP, on subdomains of the same domain so the XP's virtual disk in IndexedDB isn't blocked inside the iframe. A Playwright smoke test walks through boot, desk, monitor, Esc and free camera, and reports console errors.
Result
A résumé you can open inside a working OS inside a 3D room, which still degrades to plain HTML when it has to. This page follows the same idea: plain HTML and CSS styled like an early-2000s personal site, because that's what Internet Explorer 6 would have shown.
ENFAM · Renejum, the national network of judicial schools
The problem
Courses offered across the network lived in separate systems run by different courts, so building one calendar meant consolidating them by hand.
What I built
A custom WordPress plugin with two shortcodes: an interactive monthly calendar and a list view. A PHP proxy fetches three external APIs on the server (SINEMAF from the Federal Justice Council, Educa from ENFAM and SIGA from the Minas Gerais Court of Justice) and caches each source by month in WordPress transients, so visitors don't hit the courts' APIs on every page view. Visitors filter by month, category, modality and school, switch between course start dates and open registrations, and see monthly totals. ARIA roles and labels throughout.
Result
One calendar for the whole network, with no manual consolidation. The September 2026 view lists 87 events and 3,456 seats.
ENAM is Brazil's unified national exam that qualifies candidates for judicial careers in federal, labor, military and state courts. Its portal had to stay up and accessible when candidates from across the country arrive at the same time, on registration and results days.
What I built
An accessible WordPress and Elementor front end organized around what candidates look for (the exam, data, regulations, certificate, results, news and FAQ), with caching and server configuration handled on our own LAMP stack.
Result
30,000+ visits in the first month. The same portal now runs the exam's 6th edition.
The website of Brazil's national school for judges was hard to navigate and slow, and new pages took too long to build.
What I built
A UX-driven redesign of information architecture, navigation and page templates, backed by a Figma component library that I turned into reusable WordPress templates. I also refactored the legacy theme, optimized assets and added server-side caching.
Result
Daily visits grew 300%, and Core Web Vitals moved into the “Good” range.
Freelance · landing page for a self-custodial crypto app
The problem
Cypay puts swaps, yield, trading, bridging and a card across nine blockchains into one self-custodial wallet. The landing page had to explain all of that without turning into a wall of text.
What I built
A scroll-driven single page. Sections, feature cards and app mockups enter and leave the screen as you scroll, tied to scroll position with requestAnimationFrame and IntersectionObserver rather than timers. Hand-written HTML, CSS and vanilla JavaScript, no framework. The first version also had a scroll progress bar and a 3D mascot, the Cypay logo extruded from SVG with Three.js, that travelled across the page as you scrolled.
Result
Live at cypay.io. The content has changed since launch, but the scroll choreography is still how the page works.
Freelance · translation plugin for IESP-UERJ, the Institute of Social and Political Studies
The problem
The institute's WordPress site needed to be readable in English, Spanish and French by international researchers, at zero translation cost and without mangling institutional names and terms.
What I built
A WordPress plugin in PHP 8 with three layers. By default, the free Google Translate widget, with no character limit. On top of it, a glossary and “segments” (HTML blocks tagged with data-iesp-key) for fixed human translations. And, per page, an optional server-side mode with Google Cloud Translation for SEO: translations stored in the database, invalidated by a content hash on save and reprocessed by a WP-Cron queue, all behind a quota guard with a safe limit and a hard stop below the free tier, plus alerts at 70, 85, 95 and 100%. Editors control it from Gutenberg panels, an eight-tab admin, CSV and Excel import and WP-CLI; the quota guard, import and segments have automated tests.
Result
Four languages managed from inside WordPress, with zero API cost by design. The plugin is at version 3.1.