惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
N
Netflix TechBlog - Medium
大猫的无限游戏
大猫的无限游戏
爱范儿
爱范儿
Martin Fowler
Martin Fowler
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Register - Security
The Register - Security
IT之家
IT之家
博客园_首页
Microsoft Security Blog
Microsoft Security Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 三生石上(FineUI控件)
I
InfoQ
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Jina AI
Jina AI
Apple Machine Learning Research
Apple Machine Learning Research
M
MIT News - Artificial intelligence
博客园 - Franky
C
Check Point Blog
T
The Blog of Author Tim Ferriss
V
Visual Studio Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Tailwind CSS Blog
Recent Announcements
Recent Announcements
云风的 BLOG
云风的 BLOG
美团技术团队
The Cloudflare Blog
Y
Y Combinator Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
MyScale Blog
MyScale Blog
The GitHub Blog
The GitHub Blog
D
DataBreaches.Net
Google DeepMind News
Google DeepMind News
V
V2EX
aimingoo的专栏
aimingoo的专栏
GbyAI
GbyAI
G
Google Developers Blog
S
SegmentFault 最新的问题
Hugging Face - Blog
Hugging Face - Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
U
Unit 42
罗磊的独立博客
量子位
MongoDB | Blog
MongoDB | Blog
Last Week in AI
Last Week in AI
Stack Overflow Blog
Stack Overflow Blog
小众软件
小众软件
D
Docker
人人都是产品经理
人人都是产品经理

Codrops

Meet the Speakers of the First Three.js Conference | Codrops The Architecture Behind Trionn: Coordinating GSAP, Three.js, Lenis, and Web Audio | Codrops Where Stories Become Interactive Worlds: How makemepulse Brings Ideas to Life Across Web, XR, and Beyond | Codrops The Sleepers: Creating an Atmospheric WebGL Experience with Lightweight Techniques Building an Interactive Wave Propagation Cube Grid with Three.js | Codrops Ten Years Away: Designing an Interactive Comic for Studio375’s Tenth Anniversary | Codrops Building a Scroll-Driven 3D Gallery Using a Blender Camera Path with Three.js and GSAP | Codrops From Years of Client Work to a Next.js + Sanity Starter Kit | Codrops Sculpting a Digital Athlete: Capturing Stefanos Tsitsipas Beyond the Court | Codrops Building Persistent Page Transitions with WebGPU and Vanilla JavaScript | Codrops Inside Bisous: Designing an Editorial Experience for Cinematic CGI | Codrops Engineering the Web Experience Behind Shopify’s Spring ’26 Edition: Everywhere | Codrops Shaping Stories into Experience: The Work of Kevin Lam | Codrops Testing What Users Actually See with Vitest and Chromatic | Codrops Podium: Building a Website Where Running Becomes Storytelling | Codrops Designing Beyond the Surface: How DashDigital Turns Complexity into Clarity | Codrops Creative Entrepreneurship: Designing the Machine | Codrops Exploring 3D Image Rotations on Scroll | Codrops Always Building, Always Learning: Cyd Stumpel’s Journey Through the Modern Web | Codrops Designing A Better Lou: Reducing Cognitive Load Through Design, Content, and Systems | Codrops Sketching the Impossible: A 3D Portfolio Built Without a Single 3D Model | Codrops Building Horeca: Advanced Motion Design in Webflow Without the Performance Trade-Offs | Codrops Building an Interactive Digital Stamp Collection with Shaders, Postcards, and Playful Inspection | Codrops No Two Paths Alike: Inside San Rita’s Approach to Digital Experiences | Codrops The Making of the New Lesse Studio Website: Clarity, Performance, and Intentionality | Codrops Creating a Thumbnail Flow Animation with GSAP MotionPath | Codrops Forging Her Own Path: Houmahani Kane’s Journey in Creative Development | Codrops Beaucoup and the Pursuit of Memorable Experiences | Codrops The Future of Emotional Technology: Comfort Websites for Growth and Self-Awareness | Codrops The Never Ending Story: Building a Seamless Infinite Scroll Experience with GSAP & Lenis | Codrops Whooshes, Snaps and Shaders: Adrien Vanderpotte and the Feeling of the Interface | Codrops Building a Scroll-Driven 3D Cube Gallery in Webflow with GSAP | Codrops Creating Scroll-Driven SVG Map Animations with GSAP | Codrops Made With Gsap: Building a Fun Gravity-Based Mouse Trail | Codrops 80s Business Tech and Seamless Scene Transitions: Inside Shader.se’s Scroll-Driven WebGPU Pipeline | Codrops Merlin: The Code Boutique Turning Motion Into Digital Magic | Codrops Designing Ourselves: The New Obys Identity and Website | Codrops Exploring the HTML-in-Canvas Proposal | Codrops Le:mma Studio: Building the Feeling Behind the Screen | Codrops From Shader Uniforms to Clip-Path Wipes: How GSAP Drives My Portfolio | Codrops Reverse-Engineering Claude AI’s Mascot Animations with SVG and GSAP | Codrops Akaru: Crafting Precision-Driven Digital Experiences | Codrops Designing Against the Gallery: A Two-Year Journey to a Layered Portfolio Experience | Codrops Between Human and Machine: Sofia Papadopoulou’s Approach to Augmented Creativity | Codrops More Than a Portfolio: Building a Scroll-Driven 3D World with Something to Say | Codrops Rhumb Studio: Small but Mighty, Shaped by Curiosity | Codrops Susurrus: Crafting a Cozy Watercolor World with Three.js and Shaders | Codrops Building 4WIDE: Turning Distortion, Blur, and Motion into a Coherent Experience | Codrops A Playful Clip Menu with GSAP’s easeReverse | Codrops False Earth: From WebGL Limits to a WebGPU-Driven World | Codrops Interactive Storytelling for the Web: Building Immersive Stories with Timelines, 3D, and Layered Scenes | Codrops The Art of Complex Motion: Corentin Bernadou’s World of Shaders and Experimentation | Codrops They Call Me Giulio: The Making of a Cinematic Cyberpunk Portfolio | Codrops Lusion: Where Digital Craft Meets Ambitious Experimentation | Codrops The Exat Microsite: Pushing a Typography Showcase to New Creative Extremes | Codrops A Single DM, a First SOTD, and the Start of Chanh M. Ho’s Creative Journey | Codrops Creating Custom Page Transitions in Astro with Barba.js and GSAP | Codrops R—K ’26: The Thinking and Code Behind a Portfolio Led by Presence | Codrops Where Engineering Meets Craft: Edoardo Lunardi’s Obsession with the Details | Codrops Animating 160,000 Cubes in Three.js to Visualize Dithering | Codrops Arnaud Rocca’s Portfolio: From a GSAP-Powered Motion System to Fluid WebGL | Codrops Where Confidence Needs No Decoration: The Malvah.Studio Story | Codrops Motion Highlights #17 | Codrops
Building the Maxima Therapy Website: React, GSAP, and Dabbling with AI | Codrops
By Nicolas Garnier · 2026-04-06 · via Codrops

The Project

Maxima Therapy is an organization dedicated to supporting neurodivergent individuals throughout their lives. They believe in kindness, creativity and a warm and empowering environment. With this mindset, our mission was to deliver a highly illustrated, interactive and playful website for the California-based company.

Beyond the creative aspects, we wanted to push the boundaries of what a “tech industry friendly” stack could offer. And with the rise of coding agents, we looked at how using AI could possibly help us in the process.

The Stack

For the foundation, we chose Sanity as our CMS, React Router for the frontend with static generation, and Cloudflare Pages for hosting. Compared to Next.js, React Router offered an easier configuration to get things started. Data is fetched from Sanity at the root level and passed down to individual contexts, which lets us share it across different parts of the site without being constrained by page-level scope limitations.

The site’s scrolling is smoothed with Lenis, while GSAP powers all of the interactive elements, especially the scroll-based animations driven by ScrollTrigger. Both are synchronized using GSAP’s internal ticker and Lenis’s onScroll event. Many of the SVG animations are handled with Lottie, alongside some custom canvas implementations for patterns and Matter.js for the physics-based effects. Using GSAP with React also gives us access to the useGSAP hook, which ensures automatic cleanup while mimicking React’s own useEffect.

On the styling side, we used Tailwind CSS, with the occasional CSS module where needed, and we also brought in TypeScript for type checking, even if that discipline was sometimes loosely enforced. Altogether, the stack gave us a solid foundation and enough flexibility to achieve the final result. Claude Code also came in very handy for speeding up and automating tasks that would otherwise have been tedious, which I’ll get into more below.

The Features

Home Carousel

The main gate to the experience is the interactive home hero carousel.

Users can grab and release the wheel to navigate through the main programs Maxima offers, and each section of the wheel contains a set of SVG animations playing with Lottie as well as a custom interactive element.

The home hero carousel

The main challenge here was to render and animate quite a large amount of big SVG elements while keeping the page interactive. It is done by splitting the four different sections into four separated, rotated <div> elements within a main wrapper. Each section is unresponsive until it becomes visible.

When a user clicks an arrow or drags the wheel, the SVG animations pause, the whole wrapper rotates and the newly visible section becomes interactive.

The section swap also triggers a route change. Because the hero carousel and the rest of the page (represented by the Outlet component) live on a different level within the page’s layout, the data on the page can change while the carousel doesn’t re-render:

routes/programs/layout.tsx

export default function ProgramsLayout() {
  return (
    <ProgramHoverProvider>
      // Carousel logic, separated from the rest of the page and free of re-renders when the data updates
      <ProgramHeroCarousel />
      <ProgramHeroCards />

      // Rest of the page, re-rendering when the program route changes
      <Outlet />
    </ProgramHoverProvider>
  );
}

Each section of the carousel showcases a Lottie animation combined with a main user interaction:

The mountain looks at the cursor on the first program illustration
The second program lets users play with a balloon
An interactive masked Lottie animation is playing for the third program

The fourth program features a sun setting on a calm sea, illuminated by a lighthouse in the background. Users can interact with the water itself: every mouse movement triggers gentle ripples that spread across the surface.

This is done using a simple ripple simulation on an SVG shape with multiple control points:

Mouse-based ripples for the fourth program

This is where AI came in handy. Creating and distributing control points across an SVG shape can be difficult and time-consuming. Claude Code helped with that, taking the original shape and generating the content so GSAP could animate each control point following the ripple algorithm:

Original sea shape:

<path ref={sea} className="sea2" d="M277.223 703.836
  C 1200 703.836 1800 703.836 2705.13 703.836
  C 2705.13 703.836 2705.13 1491.4 2705.13 1491.4
  C 2705.13 1491.4 277.223 1491.4 277.223 1491.4
  C 277.223 1491.4 277.223 703.836 277.223 703.836 Z" 
  fill="#56D3D2" 
/>

// Claude-generated control point system combined with GSAP:

function animateWave(mouseX?: number) {
    const startX = 277.223;
    const endX = 2705.13;
    const baseY = 703.836;
    const numPoints = 50;
    const amplitude = -(pointerSpeed.speedY) * 0.3;
    const duration = 3;

    // Determine epicenter of ripple
    const epicenterX = mouseX !== undefined ? Math.max(startX, Math.min(endX, mouseX)) : (startX + endX) / 2;

    gsap.killTweensOf(rippleProgress.current);
    rippleProgress.current.value = 0;

    gsap.to(rippleProgress.current, {
      value: 1,
      duration,
      ease: 'power2.out',
      onUpdate: () => {
        if (!sea.current) return;

        const progress = rippleProgress.current.value;
        const fadeOut = 1 - progress;

        // Calculate ripple spread distance from epicenter
        const maxDistance = Math.max(epicenterX - startX, endX - epicenterX);
        const rippleDistance = progress * maxDistance * 1.5;

        // Generate points along the curve
        const points: Array<{
          x: number;
          y: number
        }> = [];
        for (let i = 0; i <= numPoints; i++) {
          const x = startX + (endX - startX) * (i / numPoints);

          // Calculate distance from epicenter
          const distanceFromEpicenter = Math.abs(x - epicenterX);

          // Ripple effect: only apply amplitude where the ripple has reached
          let ripple = 0;
          if (distanceFromEpicenter <= rippleDistance) {
            // Calculate how far into the ripple wave this point is
            const ripplePhase = (rippleDistance - distanceFromEpicenter) / (maxDistance * 0.3);
            ripple = Math.sin(ripplePhase * Math.PI) * amplitude * fadeOut;
          }

          const y = baseY + ripple;
          points.push({
            x,
            y
          });
        }

        // Build the path with smooth curves
        let pathData = `M${startX} ${baseY}\n`;

        // Create smooth curve through all points
        for (let i = 0; i < points.length - 1; i++) {
          const current = points[i];
          const next = points[i + 1];
          const cpX = (current.x + next.x) / 2;
          pathData += `Q ${current.x} ${current.y} ${cpX} ${(current.y + next.y) / 2}\n`;
        }

        // Connect to last point and close the path
        const lastPoint = points[points.length - 1];
        pathData += `L ${lastPoint.x} ${lastPoint.y}\n`;
        pathData += `L ${endX} ${baseY}\n`;
        pathData += `L ${endX} 1491.4\n`;
        pathData += `L ${startX} 1491.4\n`;
        pathData += `L ${startX} ${baseY} Z`;

        sea.current.setAttribute('d', pathData);
      },
      onComplete: () => {
        if (!sea.current) return;
        // Reset to original path
        sea.current.setAttribute('d', `
          M277.223 703.836
          C 1200 703.836 1800 703.836 2705.13 703.836
          C 2705.13 703.836 2705.13 1491.4 2705.13 1491.4
          C 2705.13 1491.4 277.223 1491.4 277.223 1491.4
          C 277.223 1491.4 277.223 703.836 277.223 703.836 Z
        `);
      }
    });
  }

Scroll-based Interactive Backgrounds for Lottie Animations

Another fun challenge was to mix Lottie-based animations with a fixed pattern background, that reacts to the scroll:

Interactive fixed background pattern on Lottie animation

To achieve the effect, we came up with an approach that combines multiple canvas layers, blended together using globalCompositeOperation:

Step1: draw the fixed pattern once in an offscreen canvas
Step2: set Lottie's render mode to canvas
Step3: grab the Lottie's canvas and use it as a mask
Step4: draw the mask and fixed pattern together with a globalCompositeOperation

Physics-based Interactive Block

The careers page features an interactive element where the word “supports” hangs from two ropes, echoing the team-building message. To make it even more impactful, Louis came up with the idea to let users interact with it.

The natural solution was to incorporate a physics engine for a realistic and engaging experience.

We used the Matter.js library, mapping the word’s dimensions to a rectangle body. The ropes are a stack of composites, rendered in canvas. The word “supports” itself is an SVG, that then gets displaced with the results from the physics simulation:

Result and skeleton of the physics behind the rope animation

Morphing Shapes

A small interaction, that at first looks simple but actually presented some challenges is the morphing shape of the square-to-circle CTA piece.

CTA getting morphed on pointer hover

GSAP’s MorphSVG plugin makes this straightforward:

<svg
  width="140"
  height="140"
  viewBox="0 0 140 140"
  fill="none"
  xmlns="http://www.w3.org/2000/svg"
  ref={svg}
>
  <path d={circleShapePath} className="cta-circle-shape opacity-0" />
  <path d={rectPath} className="cta-rect"/>
</svg>


function onEnter() {
  gsap.to('.cta-rect', {
    duration: 0.9,
    morphSVG: '.cta-circle-shape', ease: 'elastic.out(0.8, 0.8)'
  });
}

function onLeave() {
  gsap.to('.cta-rect', {
    duration: 0.9,
    morphSVG: '.cta-rec', ease: 'elastic.out(1.2, 1)'
  });
}

Stickers Block

Adding stickers to some of the sections felt like a natural interaction, giving a playful dimension to the text.

Clicking the title cycles through the stickers

Cycling through an array of stickers on click and displaying them with bouncy animations was made easy using GSAP’s built-in elastic easing:

// Animate the scale for the current sticker
if (stickerRefs.current[targetIndex]) {
  gsap.fromTo(stickerRefs.current[targetIndex], {
    scale: 0,
  }, {
    scale: 1,
    ease: 'elastic.out(1.2, 0.8)',
    duration: 1,
  });
}

Scroll-based Interactions

Lastly, the website is packed with scroll-based interactions. From text showing to images revealing and SVG animations playing, it is crucial to be able to manage each interactive element’s state with ease.

Scroll-based interactions

ScrollTrigger is an ideal fit for this. It helped us avoid setting up Intersection Observers manually and integrates perfectly with the useGSAP hook:

// Example of a scroll-based parallax animation created with ScrollTrigger and useGSAP. The tweens are automatically killed and discarded once the component unmounts

useGSAP(() => {
    ScrollTrigger.create({
      trigger: root.current,
      start: 'top top',
      end: 'bottom top',
      scrub: true,
      onUpdate: (self) => {
        setDispY(20 * self.progress * weight);
      }
    });
  }, []);

The Use of AI in the Project

While traditional coding techniques were still at the core of the project, AI offered a very strong extra pair of hands for tasks that would be otherwise tedious and time consuming.

It was very good at optimizing SVGs when given clear guidance (for example, converting a mask to a clip-path for performance gains). With a clear data model as a starting point, it also helped create additional similar Sanity blocks and map the data fetching on the application’s side.

We also found that some of the more advanced creative coding techniques (like the wave ripple effect) were also surprisingly accurate, and it made resolving TypeScript issues and creating types a breeze.

It also came with some caveats:

First, it occasionally produced inconsistent and inaccurate results. It would sometimes alter certain data fetching patterns unexpectedly, making the code messy and potentially causing critical issues if left unchecked.

Though good at optimizing and cleaning certain SVGs, it also hallucinated in some instances (even creating made-up shapes and adding them to the code without consideration).

All in all, we enjoyed using AI for smaller, tightly scoped tasks rather than bigger ones. One point could also be made about “AI fatigue”, where letting an agent code most of a functionality led to a certain feeling of not controlling the output anymore. Going back to generated code felt like doing extra code review, and became tricky when the features had to be rewritten in some instances.

Final Words

Crafting Maxima Therapy was an exercise in combining tech and creativity. Setting up a strong framework with Sanity, React, and GSAP streamlined the design-to-code delivery. With a strong creative vision, having the tools ready meant we were able to say “yes” to the ideas as they came along.

Using a coding agent made iterating on concepts faster and reduced some of the most tedious tasks to an afterthought, allowing us to focus on the true creative intent.

It truly was a fun challenge and we are very grateful to have been trusted with helping Maxima in their mission to deliver extra good care for people with neurodivergence.

Credits

Agency: KOKI-KIKO
Strategy & PO: Kim Levan
Art Direction & Design: Louis Paquet
Branding & Illustrations: Anthony Morell
Tech & Development: Nicolas Garnier