Here I take the site apart piece by piece. Each component is shown live, with what it does, why it exists, how it works and what I honestly think of it. Further down, free experiments with no brief.
Everything the site is made of, one by one. Live demos, code in the repository.
C—01 · components/webgl/DitherHero.tsx
The dithered organic sphere behind the hero. It breathes, follows the cursor like a loupe and dissolves on scroll.
Why
I wanted a visual identity with no image at all: two colours, one algorithm. A photo or video would weigh several MB; the shader weighs 6 KB.
How
Raw WebGL (no three.js), a single full-screen triangle. A raymarched sphere warped by 3D simplex noise, lit from its normal, then 8×8 Bayer ordered dithering. Compiled while the browser is idle (KHR_parallel_shader_compile) so it never blocks the first paint.
WebGL
GLSL
Raymarching
Bayer 8×8
Verdict —The centrepiece: it gives the site its identity. What I'd redo: an automatic low-power mode based on the GPU rather than on screen size.
C—02 · components/ui/Kinetic.tsx
Headings reveal letter by letter, and their weight thickens as the cursor gets closer.
Give the typography depth without video or images: text becomes the main interactive element.
Split into words, then masked letters, animated with GSAP. For the weight: a variable font, where the distance to the cursor drives each letter's 'wght' axis, smoothed. The loop stops as soon as everything is still.
GSAP
Variable fonts
requestAnimationFrame
Verdict —Great effect-to-cost ratio, and accessible: the full text stays available to screen readers (sr-only). Disabled on touch screens, where it would make no sense.
C—03 · components/ui/Kinetic.tsx
A paragraph that lights up word by word, in step with scrolling.
Slow down the reading of key texts: you read at the pace they appear.
ScrollTrigger in scrub mode: each word's opacity is tied to the scroll position. Minimum opacity of 0.38 to keep WCAG contrast.
ScrollTrigger
Verdict —Effective but very common on award sites. Use sparingly, once or twice per page.
C—04 · components/ui/PixelImage.tsx
Images "resolve" from big pixels to full definition. On hover, the colour original is revealed in a circle from the cursor.
Every image on the site is dithered black and white to stay on brand; this component still lets you see the real visual.
Canvas 2D: the image is shrunk then enlarged without smoothing, in 7 steps (64 → 1 px). The reveal is a CSS circular clip-path positioned through variables.
Canvas 2D
clip-path
IntersectionObserver
Verdict —Solves a real problem (brand vs readable projects). On touch screens, the original reveals itself after the animation.
C—05 · components/ManifestoBand.tsx
The band of giant scrolling words. It speeds up, skews and reverses with scrolling.
A breather between the hero and the About section, and a way to say what I do in three words.
Scroll velocity (Lenis) is read every frame by the GSAP ticker: it drives translation and skewX. The content is duplicated to loop seamlessly.
GSAP ticker
Lenis
CSS transforms
Verdict —Nice, but a genre classic. Scroll the page to see it react.
C—06 · components/CustomCursor.tsx
A dot and a reticle replacing the cursor. Contextual labels and magnetic buttons.
Make navigation feel more tactile and give hints ("View", "Open") without adding text to the UI.
Two elements tracked with gsap.quickTo at different speeds (the reticle lags). Any element opts in with data-cursor="Text" or data-magnetic, without touching the component.
GSAP quickTo
Data attributes
Verdict —Nice attribute-based API. Disabled on touch and when the user prefers reduced motion: the system cursor comes back.
C—07 · components/NegativeMode.tsx
An easter egg: the whole site flips to negative, with a circular reveal.
Reward the curious. The secret: the Konami code (↑ ↑ ↓ ↓ ← → ← → B A), or 5 taps on the "CD" logo on mobile.
A class on <html> inverts colours; the View Transitions API animates the switch. Instant fallback in browsers without it.
View Transitions API
CSS
Verdict —Original and almost free in code. The kind of detail visitors talk about.
C—08 · components/ui/Expandable.tsx
The site’s mobile "Read more". On opening, pixels leave the folded edge and assemble the hidden words, line after line going down, then the real text takes over. On closing, the text breaks back into pixels that fly up.
On mobile, experience entries and case-study lists were truncated with no way to read everything. They needed to expand, and I wanted it to stay on theme: pixels, two colours.
Each hidden word is measured where the browser laid it out (Range.getClientRects), redrawn off-screen on a canvas, then sampled into 2 px pixels. Each pixel flies from the folded edge to its spot, with a delay proportional to its height. Meanwhile a CSS mask hides the real text, which fades in at the end. The button label decodes itself and its 5-pixel "+" rearranges into "−".
Range API
CSS mask
Web Animations API
Verdict —A useful detail AND a signature: it fixes a real mobile reading problem. Honours reduced motion (instant opening). On desktop the full text is simply shown.
C—09 · components/ui/Counter.tsx
Numbers that roll up to their value (number of projects, repositories…).
Give weight to key numbers and rhythm to page headers.
A requestAnimationFrame loop with easing, triggered once on entering the viewport. Leading zeros so the width never shifts.
Verdict —Useful but ordinary. It earns its place, nothing more.
C—10 · components/JourneySection.tsx
The Réunion → Saguenay journey on a dithered globe, with a kilometre counter.
Tell my story visually rather than with a sentence.
A dot globe built from a land mask, a great-circle arc, progress tied to scrolling with pinning on large screens.
Géodésie
Verdict —More personal than technical, but it makes the About page memorable. No isolated demo here: it needs the page scroll.
C—11 · contexts/StarContext.tsx
Stars hidden all over the site. Finding them all unlocks a mini-game.
Get people to explore the whole site instead of skimming the home page.
A React context keeps progress (saved locally), a notification hints at the next one. The game only loads once unlocked.
React Context
localStorage
Lazy loading
Verdict —Good for engagement. I won't say where they are.
Star
C—12 · components/Chatbot/
The assistant in the bottom right that answers questions about my background.
Answer recruiters at any hour, with no paid API and no data sent to a third party.
Local engine: text normalisation, intent detection with weighted keywords, a bilingual knowledge base. Loaded after the main content.
TypeScript
NLP maison
Verdict —Interesting logic-wise, less so visually. It won't replace a real language model, but it costs nothing and leaks nothing.
ThreeSceneA three.js background grid, the first version of the site. Replaced by DitherHero: ~600 KB of library for a grid was too much.
ProjectCardClassic project cards. Replaced by a list index (ProjectIndex), more editorial and quicker to scan.
No client, no constraints. Ideas tried for fun — some will end up on the site, others won't.
E—01 · 2026.10
Status : Ongoing
Two simulated chemicals feeding on and destroying each other. Living patterns (coral, cells, mazes) emerge from two equations, on the GPU, dithered in two colours.
WebGL2
Ping-pong FBO
E—02 · 2026.10
Status : Stable
A word sampled into dots, each dot a particle on a spring pulling it home. The cursor blows them away. Type your own word.
Physique de ressorts
E—03 · 2026.10
Thousands of particles follow a direction field that ripples over time. They leave a slowly fading ink trail. The cursor creates a vortex.
Champs vectoriels
E—04 · 2026.10
Status : Archived
A rotating sphere rendered in characters. Meant for the hero, dropped for the dithered WebGL version, but too pretty to delete.
ASCII