Free course recommendation: Master JavaScript animation with GSAP through 34 free video lessons, step-by-step projects, and hands-on demos. Enroll now →
Ciel Rose is a France-based artistic duo made up of a director and a director of photography. They create promotional films for brands like Decathlon and Electro Dépôt, as well as music videos for artists.
Their work is all about blending storytelling with striking cinematography, giving each project a strong visual identity.
For their portfolio, the goal was to design a site that feels smooth and immersive while staying true to their universe. The real challenge was to craft a unique experience that enhances their work without taking the spotlight away from it. We wanted to strike the right balance between letting their projects speak for themselves and making the navigation feel effortless and engaging.
Design & Art Direction
Inspiration & Art Direction
For Ciel Rose, the goal was to keep the focus entirely on the client’s assets—minimalistic but impactful. The name Ciel Rose (which refers to sunrise and sunset) naturally led me to explore gradient-based visuals that subtly represent these moments without feeling too overdone or cliché. For the interaction design, I was heavily inspired by Angus Emmerson’s portfolio, Thomas Monavon, Greg Lallé, and Design Embraced, which led me to focus on deformations as a key visual and interactive element.
Exploring on Figma
I started by testing different deformation ideas in Figma, thinking about how to integrate them seamlessly into the hero section. The goal was to keep it simple and fluid, so we decided on a scroll-based slider deformation for the homepage. The gradients were also carefully integrated in a subtle way.

Prototyping in After Effects
Once the Figma design was finalized, I moved to After Effects to refine the animations and interactions. There was a lot of iteration, often switching back to Figma as new ideas emerged during the animation process. I focused on adjusting the easings to ensure that every interaction felt cohesive and fluid, making the overall navigation as smooth as possible.

Tech Stack
For this project, I used my own boilerplate, which was inspired by Bizarro’s setup. This gave me full control over every aspect of the development process. It was also a great learning opportunity, helping me better understand complex concepts like page transitions.
All animations using WebGL were applied to planes that blended seamlessly into the DOM, creating smooth and immersive effects.
The main technologies used:
- Vite.js for fast development and bundling
- SCSS for styling flexibility and maintainability
- Node.js for server-side logic
- WebGL with three.js
- Smooth scroll with Lenis
- Prismic as a headless CMS
The entire website content is editable within Prismic. It was essential to give Ciel Rose the flexibility to manage their portfolio, add new projects, and adjust content as needed. Since the format of images (1:1, 3:2, etc.) plays a key role in showcasing their work properly, everything was carefully planned to ensure they had full control over how their visuals are presented.
Page Transitions
One of the key aspects of this project was ensuring seamless page transitions while keeping video as the focal point. The goal was to create an immersive experience where navigation felt fluid and natural, without disrupting the visual storytelling.
When researching tools for handling page transitions, I explored Barba.js, Highway.js, and Taxi.js. These libraries offer smooth transitions, but since I already had a clear vision of what I wanted to achieve and wanted a deeper understanding of how transitions work, I decided to create my own custom router.
Here’s what it looked like:
export default class TransitionWatcher {
constructor() {
this.transition = null;
}
handleTransition(previousPage, newPage, canvas) {
if (isMobile) {
this.transition = new TransitionGlobal({ lastTemplate: previousPage, nextTemplate: newPage });
return;
}
const key = `${previousPage}-${newPage}`;
Presets
switch (key) {
case 'about-projets':
this.transition = new TransitionAboutToProjet();
break;
case 'projet-projets':
this.transition = new TransitionProjetToProjet();
break;
case 'home-projets':
this.transition = new TransitionHomeToProjet();
break;
case 'projet-home':
if (canvas.home) this.transition = new TransitionProjetToHome();
break;
default:
this.transition = new TransitionGlobal({ lastTemplate: previousPage, nextTemplate: newPage });
}
}
async canvasOut(previousCanvasState, currentCanvas, previousPageState) {
await this.transition?.canvasOut(previousCanvasState, currentCanvas, previousPageState);
}
async pageOut(previousPageState) {
await this.transition?.pageOut(previousPageState);
}
async canvasIn(currentCanvas, template) {
await this.transition?.canvasIn(currentCanvas, template);
}
async pageIn(nextPageState) {
await this.transition?.pageIn(nextPageState);
}
}
At its core, I structured the system around dedicated transition classes for each type of page change (ex: Home to project..). By default, the global transition with the descale effect is used, but depending on the route and the device, the transition dynamically switches to the most appropriate one.
This approach may not be the most conventional, but it allowed me to gain a better grasp of how to animate each element smoothly across transitions.
Each transition class is built around four key methods:
- canvasOut() → Handles the exit animation of WebGL elements
- pageOut() → Manages the outro animation of DOM elements from the current page
- canvasIn() → Controls the intro animation of the WebGL elements for the next page
- pageIn() → Brings in the DOM elements of the new page
This separation helped me keep things modular and flexible, making it easier to fine-tune each transition independently.
Handling Video Persistence Across Page Transitions
While I was working on the transitions, I encountered a major issue. The videos were standard HTML DOM elements, but I was using Three.js to render them as textures inside my WebGL scene. Since the video textures relied on the src of the DOM video elements, when changing pages, the corresponding video elements would be removed from the DOM, causing the WebGL texture to be lost.
While browsing other websites using WebGL and video, I came across Grégory Lallé and Thomas Monavon’s incredible work on the Angus Emmerson portfolio (check it out if you didn’t yet!).
The Trick
To solve this, I needed a way to persist the video texture across page changes. The approach was to create a video element without a source at first.
The trick was simple:
- On hover or click of a project’s video, it would retrieve its
src
URL and apply it to a global persistent video element. - This global video element wasn’t attached to any particular page, ensuring that its source remained available throughout navigation.
- On click, when the page transition starts, I would just mix() the previous and current video textures in my shader to blend smoothly from the previous video to the new one.
vec4 video = mix(previousVideoTexture, currentVideoTexture, transitionProgress);
This ensured a seamless fade between videos, maintaining visual continuity throughout navigation.
There may be other ways to tackle this problem, and I’d be curious to hear different approaches!
Visual Effects & Shader Experiments
Why Shaders?
Shaders were mainly used to enhance page transitions, ensuring a smooth and seamless experience while maintaining the project videos as the focal point. By leveraging WebGL, I was able to control how elements blended and animated between pages in a way that wouldn’t be possible with traditional CSS or JavaScript alone.
How It Was Used
WebGL elements were placed on top of the DOM elements, allowing for smooth blending effects.
Shaders played a key role in creating smooth and immersive page transitions. The main effect used when switching between projects involved a perspective-based deformation, leveraging the length()
function to control how different parts of the plane react to movement.
- Fullscreen Expansion – The video plane expands fullscreen while animating its Z position, creating a smooth in-and-out effect.
- Non-Uniform Deformation – Instead of moving as a rigid plane, the corners of the video arrive slightly after the center by calculating the distance from the plane’s UV center. This subtle warping makes the motion feel more organic.
- Dynamic Stickiness – A mix of positional adjustments and controlled easing (
mix()
) allows the transition to feel fluid while maintaining visual coherence.
This pretty subtle and simple approach ensured the videos remained at the heart of the experience while adding a refined and immersive transition between projects.
Some of the animations using shaders:
Final Words
In the end, this project was about more than just creating a portfolio—it was about translating Ciel Rose’s cinematic language into an interactive format that feels alive and intuitive. Every visual and technical decision was made with care, aiming to support their storytelling without overshadowing it. The result is a digital space that reflects their unique universe while giving them the flexibility to evolve and grow. We hope it does justice to their vision.