- Role
- Solo designer and developer: the design system, the code and the copy in both languages.
- Outcome
- Lighthouse 100 for accessibility, best practices and SEO on mobile, with no layout shift. Every page in both languages is checked in CI: status, SEO tags, structured data, axe in both themes, phone width.
- Year
- 2026
- Status
- in progress
- Stack
- Next.js 16
- React 19
- TypeScript
- Tailwind 4
- three.js
- React Three Fiber
- Radix
- MDX
- Playwright
- Vercel
The start
A first version that did the job
The first version of this site went up in June 2025, with a page for my Roblox work and one for my side projects. It did the job, but I wanted more from it: a site that told how each project went instead of just listing it, and one that looked like mine.
The goal
One site for three kinds of visitor
Clients looking for a bot or an automation, Roblox studios looking for sound, interfaces or scripts, and employers reviewing my work: all of them should find what they need and a clear way to write to me. In Italian and in English, with the same substance. And it had to feel like mine, with my dragon and my colours, not like a template.
The adventure
Rebuilt from the ground up
I rebuilt it piece by piece. First a visual language with its own rules: purple for anything you can press, lime for what's live, nearly square corners, and every button, menu and player designed once and reused everywhere. Then every page in two languages, the case studies you can read by tabs, and the Roblox pages where every sound plays and every interface opens up.
The Roblox page, made for studios: my name as a wall of bricks you can brush and blast. The part I'm proudest of is the dragon on the home page. It's made of particles that leave the background and gather into my avatar, then turn to follow your pointer. Click the background and it bursts apart in slow motion and puts itself back together.
The end
A site that checks itself
It loads fast, works with a keyboard and a screen reader, and stays calm for anyone who prefers less motion. Every time I change something, every page in both languages is checked automatically before it goes live. It's still in progress: the redesign ships page by page, and this case study grows with it.
- Lighthouse accessibility, best practices and SEO
- 100
- Lighthouse accessibility, best practices and SEO
- Languages, same substance
- 2
- Languages, same substance
- Layout shift while loading
- 0
- Layout shift while loading
What's next
Even faster
The effects take some loading before the first screen appears on a phone. Loading more of them later is the next step.
More of my own media
Real screenshots and clips for every project, and my own 3D models.
Its own design system
Colour roles (purple to press, lime for what is on), fluid type tokens, three corner radii and a UI kit of buttons, forms, menus, media and dialogs, all shown in a lab page.
A dragon made of particles
The home page draws my avatar as a low-poly mesh that turns toward the pointer, with a click that blows it apart and puts it back together.
Two languages, one site
Every page in Italian and English. Switching language keeps the page running and sweeps the new text in behind a lime scan line.
Accessible by default
Real headings and text behind every effect, a visible focus ring, a skip link, contrast checked in both themes, and a still version for reduced motion.
Works without the effects
The 3D desk on the About page has a list of buttons beside it, so it works by keyboard and without WebGL, where the list is the whole thing.
Consent done properly
Nothing from Google loads before a choice. Decline and Allow are the same size, and Cookie settings in the footer changes it at any time.
Readable by search and AI
Canonical and hreflang tags, share images built at build time, one Person in the structured data on every page, and llms.txt files generated from the project data.
Checked on every change
CI runs lint, the type check, a production build and smoke tests across every page in the sitemap.
Two themes
Dark is the default and light is one click away, revealed as a circle that grows from the toggle. The light theme isn't the dark one inverted: panels lift on a soft shadow, field outlines are darker so they stay visible, and lime text switches to a deeper shade, because the bright one is too pale to read on white.
Roblox, discipline by discipline
The Roblox page links to one page per discipline. Each game opens as a panel, grouped the way the work was made, with players for every sound and a lightbox for every image. A link can point straight at a game or a group, and a Commission me button follows you once the title scrolls away.
Work you can filter
The topics shared by at least two projects become filters. The choice is saved in the address, so a link like the home page's "See this kind of work" opens the list already filtered.
On a phone
On phones the dragon gets its own band under the header and the copy starts below it, so nothing covers it. The buttons go full width, the navigation becomes a full-screen menu, and the language switch closes it so the scan line plays over the page.
When a page doesn't exist
The stack
Framework
Next.js 16, React 19
App Router with Turbopack, server components by default, and Next 16's proxy for the locale redirect. Deployed on Vercel, with API routes for the parts that need a server secret.
Styling
Tailwind 4
CSS-first configuration: the colour, type, radius and easing tokens are CSS variables in one design-system stylesheet, switched by the theme class.
3D
three.js, React Three Fiber
The Roblox voxel hero and the About page's desk, lazy-loaded, paused when off screen, and replaced by a poster without WebGL2.
Content
MDX
Case studies and game pages are MDX per language, with blocks like the journey, stats and challenges usable without imports.
The particle field
Two canvases sit behind the page: the main layer with the plexus and the hero shape, and a far layer with three blurred depth layers.
Sample
A Web Worker reads the avatar's pixels into outline, glasses, teeth, mouth, tongue and fill points.Mesh
The points are triangulated with Delaunator; only triangles on ink with short sides are kept.Recruit
While the hero is on screen, background particles leave home and land on the shape's points.Shade
Facets are batched by colour and lit with a sheen that turns with the dragon toward the pointer.
Every background particle has a home from a low-discrepancy sequence, so any prefix of the list is evenly spread: recruiting from the end leaves the rest of the field still even. The depth layers move with 1/z parallax, and their blur is drawn with gaussian sprites rather than CSS filters.
Languages
Every page lives under /en or /it. A path without a locale gets a 307 to the language saved in a cookie, else the best match in the browser's languages, else English. Interface strings live in two dictionaries, and the Italian one is typed against the English one, so a missing key fails the type check. Long copy is MDX per language.
There is one root layout above the locales, so a language switch is a soft navigation: the particle field, the cursor and the sound keep running, and a named view transition pairs the old and new page under the scan line.
Challenges
Problem
A blocked main thread
Sampling the dragon took about 850 ms on a mid-range phone, frozen in the middle of the first visit.
Solution
The sampling moved to a Web Worker. Its launcher lives outside the shapes module, because a worker URL inside the module the worker imports sent Turbopack's build into a loop.
Problem
Blur that cost the frame rate
Three CSS-blurred canvases ran at 3-4 fps in software rendering.
Solution
The blur is drawn instead: dots are gaussian sprites and links a wide faint stroke under a thin one, on a canvas at 0.6 resolution.
Problem
A late consent banner
Rendered after hydration, the banner became the largest paint on every first visit.
Solution
It ships in the server HTML, hidden by CSS until an inline script, run before first paint, marks the page as asking.
Problem
A hydration mismatch
With the proxy in front, the pathname could differ between the server HTML and hydration, and the header's current-page marker didn't match.
Solution
The header reads its route from the rendered layout segments instead, which are part of the tree, so server and client always agree.
Quality checks
Smoke tests
Playwright
Every page in the sitemap: status, one h1, canonical, hreflang, share image, structured data with the Person, no console errors, no sideways scrolling at phone width.
Accessibility
axe
The key pages in both themes, with no serious or critical violations, plus the consent flow, the localized 404 and reduced motion.
Security
Headers
A content security policy and the other security headers on every route, and API routes that validate their input and only serve an allowlist.
CI
GitHub Actions
Lint, type check, a production build and the smoke tests on every push.
Is the source code public?
No, the repository is private. I'm happy to walk through any part of it on a call.
Does it track me?
Only if you allow analytics. Until then nothing from Google loads, and the privacy policy lists every service your browser talks to.
Why is the plexus only on the home page?
It's the site's signature, but behind reading content it was too much. Every other page sits on a solid background.
What happens without WebGL or with reduced motion?
The 3D scenes fall back to a poster or a list of buttons, and with reduced motion the particles stand still, the shapes snap into place and the intro is skipped.
Can you build something like this for me?
Yes. Tell me what it should do and when you need it, on the contact page.
Need something like this?
Tell me what it should do, where it runs and when you need it.
More work

- #web
- #discord
- #bots
Arise Crossover Tools
Live dungeon and event timers for Arise Crossover, fed by a Discord bot that reads the official announcements.

- #computer-vision
- #automation
- #roblox
Leaf's Grand Blue Autofish
A free Windows app that fishes in Roblox Grand Blue for you and logs every catch it reads.

- #discord
- #bots
- #api-integration
BotFire
A Discord bot for my streaming community: game suggestions and my Twitch schedule, synced to Google Calendar.