3.1 C
New York
Wednesday, February 18, 2026

Joffrey Spitzer Portfolio: A Minimalist Astro + GSAP Construct with Reveals, Flip Transitions and Refined Movement



I’ve been working within the internet business for seven years, but I by no means took the time to construct a really strong portfolio.

Nicely… that’s not solely correct. I truly spent a variety of time beginning portfolio variations, however I by no means completed any of them. I saved iterating, chasing a outcome that felt “adequate,” and each try ultimately was one other restart.

I spotted one thing alongside the best way: you could be your individual hardest consumer.

To make it work this time, I made a decision to deal with my portfolio like an actual undertaking, with an actual constraint: a deadline. My objective is to launch in January 2026, which supplies me three months to design and develop a completed model and, most significantly, to ship it.

Design and inspirations

On this article, I’ll principally deal with the technical facet of issues and the way these interactions are constructed, fairly than the design course of itself however I’ll nonetheless share just a few notes on the design decisions alongside the best way.

I designed my portfolio to really feel restrained and exact, mixing minimalist readability with brutalist edge clear, quiet layouts with moments that really feel uncooked and direct. I’m obsessive about finesse: easy interactions, delicate transitions, and that further consideration to micro-details that makes all the things really feel intentional with out being loud.

The work of Brijan Powell, Thomas Monavon, Greg Lallé, and Gil Huybrecht has been an enormous affect right here.

Tech stack

I made a decision to make use of Astro: a framework that’s turn into fairly common lately, and one I genuinely get pleasure from as a result of it makes SSG easy whereas nonetheless letting me maintain issues easy with vanilla JavaScript (no React or different JS frameworks).

For animations and interactivity, I naturally went with GSAP, an business customary for internet animation. I used Lenis for easy scrolling (it pairs very well with GSAP), Three.js for WebGL, and Swup to deal with web page transitions. For styling and format, I used Tailwind.

I received’t go too deep on the backend, however I used Prismic as a CMS and hosted all the things on Netlify: a strong different to Vercel, which I’m selecting to step away from (for causes you possibly can in all probability guess).

Animations & Interactions

Excessive-quality, delicate animations that permit me showcase what I can do whereas maintaining all the things minimal and concise. The objective is to maintain all the things as easy as doable.

I’ve been utilizing GSAP for years, and it’s nonetheless my go-to device for artistic front-end work. With its plugin ecosystem, we are able to construct every kind of animations and interactions whereas maintaining issues performant and easy. That’s why this text focuses on GSAP, and I received’t cowl the 3D “rock” animation made with Three.js. That half might simply be a tutorial by itself.

Listed here are the animation varieties we’ll be :

  • Reveal animations
  • Web page transitions
  • The vertical slider
  • The slider to grid swap
  • The preloader

Reveal animations

This half is essential as a result of it defines the positioning’s rhythm and the way easy all the things feels.

For textual content, I break up issues into two classes: paragraphs (normally a number of strains) and titles (only one or just a few phrases). Paragraphs will reveal line by line, whereas titles will animate character by character.

To try this, we’ll use GSAP’s SplitText plugin, then animate every line with a easy masks, a delicate stagger, and a constant ease. That is in all probability one of the vital components of movement design if you need all the things to really feel easy.

When animating strains, I’m utilizing autoSplit: true so the textual content can re-split responsively if the format adjustments. In that case, the animation should be created contained in the onSplit() callback and returned so GSAP can correctly revert and rebuild it on resize.

// Titles
this.break up = new SplitText(this.factor, {
  sort: "phrases, chars",
  autoSplit: true,
  masks: "chars",
  charsClass: "char",
  onSplit: (self) => {
    return gsap.from(self.chars, {
      length: 1,
      yPercent: -120,
      scale: 1.2,
      stagger: 0.01,
      ease: "expo.out"
    });
  }
});

// Paragraphs
this.break up = new SplitText(this.factor, {
  sort: "strains, phrases",
  autoSplit: true,
  masks: "strains",
  linesClass: "line",
  onSplit: (self) => {
    gsap.from(self.strains, {
      length: 0.9,
      yPercent: 105,
      stagger: 0.04,
      ease: "expo.out"
    })
    return this.break up
  }
})

Concerning pictures and movies, I went for a delicate fade-up reveal, with a small stagger to create a mild delay between visuals within the galleries.

// Galleries
gsap.fromTo(
  this.pictures,
  { yPercent: 100, autoAlpha: 0 },
  {
    yPercent: 0,
    autoAlpha: 1,
    length: 0.8,
    ease: "power3.out",
    stagger: 0.1
  }
)

Web page transitions

Web page transitions are additionally important in artistic growth. They allow us to keep in charge of the positioning’s rhythm and maintain the expertise feeling steady. As a substitute of a tough minimize between two screens, we are able to information the content material swap with delicate movement, making navigation smoother and avoiding that “new web page” jolt. There are a number of front-end libraries that assist with this, and for this undertaking I selected Swup as a result of it’s simple to combine and offers you loads of freedom to orchestrate the animations.

On web page depart, I’m maintaining it easy by reversing the reveal animations. This clears the stage so the brand new web page can animate on enter.

I’m an enormous fan of transitions that carry a component from the outgoing web page into the following one. It provides an actual sense of continuity to navigation, and that type of element usually makes the distinction in how polished all the things feels. That’s precisely what I needed for the transition to the About web page, the place the “About” menu merchandise turns into the web page title and naturally we reverse it when leaving the web page.

To attain this, we’ll use the Flip plugin. It’s extremely highly effective and, for my part, nonetheless a bit underused. We first seize the hyperlink’s state (place, dimension, typography, and so on.) with Flip.getState(). Then we transfer that very same hyperlink into the title’s spot and match its dimensions and typographic properties. Lastly, Flip.from(state) animates it from the captured state to the brand new format, scaling it up till it turns into the web page title.

// 1) Seize the hyperlink’s present state (small, within the header)
const state = Flip.getState(hyperlink)

// 2) Disguise the title and match the hyperlink into the title’s place/dimension
gsap.set(title, { opacity: 0 })
Flip.match(hyperlink, title, {
  absolute: true,
  scale: false,
  props: "fontSize,lineHeight,letterSpacing"
})

// 3) Animate from the captured state to the present place (hyperlink “grows” into the title)
Flip.from(state, {
  absolute: false,
  easy: true,
  length: 0.9,
  ease: "expo.inOut",
  onComplete: () => {
    gsap.set(title, { opacity: 1 });
    gsap.set(hyperlink, { opacity: 0 })
  }
})

I’m utilizing the identical strategy to animate the transition from the work checklist to every work’s element web page.

The vertical slider

For the circumstances itemizing, the thought was to go for a really brutalist-style interplay: a vertical slider that allows you to scroll by way of the works, whereas the energetic one scales as much as take the highlight. I received’t go into each element right here as a result of, even when it appears fairly easy, the animation is definitely fairly complicated and ended up being a good chunk of code. An enormous a part of that complexity comes from efficiency decisions: as an alternative of animating width and top, I’m scaling the photographs (which is way smoother), however that additionally means it’s important to fastidiously recalculate positioning as you scroll.

To construct this sort of scroll-controlled interplay, I’m utilizing ScrollTrigger to drive a GSAP timeline.

const timeline = gsap.timeline({
  scrollTrigger: {
    set off: this.dom,
    begin: "top-=6.5% middle",
    finish: "backside center-=0.5%",
    scrub: true
  }
})

timeline.to(picture, {
  scaleX: scaleExpanded,
  scaleY: scaleExpanded,
  force3D: true,
  length: 0.8,
  ease: "none",
}, index)

timeline.to(picture, {
  scaleX: 1,
  scaleY: 1,
  force3D: true,
  length: 1.5,
  ease: "none",
  delay: 0.3,
}, ">")

For the work element pages, I’m utilizing the identical strategy to animate the thumbnail navigation, whereas additionally displaying the energetic picture at a bigger scale on the left: the largest picture is at all times the at present energetic one.

From slider to grid

I actually needed so as to add just a little further that doesn’t essentially deliver a lot by way of content material (perhaps nothing in any respect), however clearly ranges up the movement and is one thing you usually see in portfolios: a format swap, transferring the works from a vertical slider to a grid.

You in all probability guessed it from the transition we constructed earlier: Flip is the proper device for this. I merely toggle a CSS class on the wrapper, which utterly reshapes the format.

.--grid-mode {
  .projects__wrapper {
    @apply col-span-full grid grid-cols-6 gap-12;
  }

  .projects__item {    
    &:first-child {
      @apply col-span-2;
    }

    &:nth-child(4) {
      @apply col-start-4;
    }

    &:nth-child(6) {
      @apply col-start-1;
    }

    &:nth-child(8) {
      @apply col-start-5;
    }
  }
}

The concept is to seize the state of my pictures earlier than toggling the CSS class. As soon as the category is utilized, I can merely use Flip.from(state) to animate every factor from its captured state to its new dimension and place.

// 1) Seize present state (checklist format)
const state = Flip.getState(targets)

// 2) Toggle format: CSS does the remaining (grid vs checklist)
projectsDOM.classList.add("--grid-mode")

// 3) Animate from captured state to new format
Flip.from(state, {
  absolute: true,
  length: 1,
  ease: "expo.inOut",
  nested: true,
  scale: true,
  easy: true
})

The preloader

Past the purely artistic facet, the preloader is generally right here to preload media and key belongings, so the positioning feels sooner and extra dependable. That’s particularly vital in my case as a result of the homepage opens straight on a video that weighs a number of megabytes, so its loading must be anticipated.

A easy counter on high of a background that ultimately transforms into the video.

The counter animation itself is simple. It’s only a quantity going from 0 to 100 in fourteen steps and with GSAP you may get that “chunky” development by utilizing a steps(14) ease.

gsap.to(progressVal, {
  length: 3,
  ease: 'steps(14)',
  worth: 100,
})

After that, I take advantage of Flip (once more) to maneuver the background picture (which is the primary body of the video) to the precise dimension and place of the showreel video on the web page.

const showreelState = Flip.getState(showreel)

showreel.classList.take away("--preloading-showreel")

Flip.from(showreelState, {
  absolute: true,
  length: 1,
  ease: "expo.inOut",
  scale: true,
  easy: true
})

In parallel, I additionally animate the body that accommodates the counter utilizing clip-path.

gsap.fromTo(
  background,
  { clipPath: "inset(2.5rem 2.5rem 2.5rem 2.5rem)" },
  {
    clipPath: "inset(100% 0rem 0rem 0rem)",
    length: 1,
    ease: "expo.inOut"
  }
)

Conclusion

I hope this text was clear and that you simply picked up just a few helpful takeaways alongside the best way.

Ultimately, I’m actually pleased with the way it turned out, and I really feel assured concerning the technical decisions I made. Astro has been a real revelation for me, and GSAP continues to be a library you possibly can at all times depend on: it’s extremely versatile, but stays performant and simple to work with. And it exhibits in apply, even with a strong quantity of GSAP animation and a few Three.js on high, the positioning nonetheless scores nicely on PageSpeed Insights.

Thanks for studying, and see you round 👋





Supply hyperlink

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles