O site ta excelente, desde a ideia até a apresentação/estilização, curti demais.

Se não se importar, poderia me dizer como foi feita essa animação ao trocar de página dentro do site? 👀 Curti tanto que vou aplicar em algum projeto futuro. E quais componentes do shadcn estão sendo usados?

Parabéns pelo projeto e boa sorte na sua jornada!

Opa Ruan, obrigado!

Para a animação de troca de páginas, escolhi o gsap por conta do conrforto e o utilizei para criar as seguintes funções:

import gsap from "gsap";
import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime";

export const animatePageIn = () => {
  const bannerOne = document.getElementById("banner-1");
  const bannerTwo = document.getElementById("banner-2");
  const bannerThree = document.getElementById("banner-3");
  const bannerFour = document.getElementById("banner-4");
  if (bannerOne && bannerTwo && bannerThree && bannerFour) {
    const tl = gsap.timeline();
    tl.set([bannerOne, bannerTwo, bannerThree, bannerFour], {
      yPercent: 0,
    }).to([bannerOne, bannerTwo, bannerThree, bannerFour], {
      yPercent: 100,
      stagger: 0.2,
    });
  }
};

export const animatePageOut = (href: string, router: AppRouterInstance) => {
  const bannerOne = document.getElementById("banner-1");
  const bannerTwo = document.getElementById("banner-2");
  const bannerThree = document.getElementById("banner-3");
  const bannerFour = document.getElementById("banner-4");
  if (bannerOne && bannerTwo && bannerThree && bannerFour) {
    const tl = gsap.timeline();
    tl.set([bannerOne, bannerTwo, bannerThree, bannerFour], {
      yPercent: -100,
    }).to([bannerOne, bannerTwo, bannerThree, bannerFour], {
      yPercent: 0,
      stagger: 0.2,
      onComplete: () => {
        if (href == "back") {
          router.back();
        } else {
          router.push(href);
        }
      },
    });
  }
};

com essas funções feitas, só falta criar o esqueleto da animação, para isso criei um arquivo chamado template.tsx no /app do meu projeto next e inseri o seguinte conteúdo:

"use client";

import { animatePageIn } from "@/utils/animation";
import { useEffect } from "react";

export default function Template({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    animatePageIn();
  }, []);
  return (
    <div className="">
      <div
        id="banner-1"
        className="min-h-screen bg-purple-500 z-[50] fixed top-0 left-0 w-[calc(25%+3px)] "
      />
      <div
        id="banner-2"
        className="min-h-screen bg-purple-500 z-[50] fixed top-0 left-1/4 w-[calc(25%+3px)] "
      />
      <div
        id="banner-3"
        className="min-h-screen bg-purple-500 z-[50] fixed top-0 left-2/4 w-[calc(25%+3px)] "
      />
      <div
        id="banner-4"
        className="min-h-screen bg-purple-500 z-[50] fixed top-0 left-3/4 w-[calc(25%+3px)] "
      />
      {children}
    </div>
  );
}

e quando eu quero trocar de páginas eu utilizo a função animatePageOut, você tem duas opção na verdade que é utilizar ela diretamente ou criar um componente, EU gosto de fazer dessa forma: animatePageOut('/course/learn-english-89a9sd9j', router) o router é a instância do useRouter do "next/navigation", mas a outra forma é esse componente aqui ó:

"use client";
import { animatePageOut } from "@/utils/animation";
import { usePathname, useRouter } from "next/navigation";

interface Props {
  href: string;
  children: React.ReactNode;
  className?: string;
  onClick?: any;
}

export default function TransitionLink({
  href,
  children,
  className,
  onClick,
}: Props) {
  const router = useRouter();
  const pathname = usePathname();
  const handleClick = () => {
    if (pathname !== href) {
      animatePageOut(href, router);
    }
  };
  return (
    <button
      className={` outline-none w-fit ${className}`}
      onClick={onClick ? onClick : handleClick}
    >
      {children}
    </button>
  );
}

ah, quase que esqueço da pergunta sobre o shadcn, não lembro exatamente, mas estou utilizando principalmente o Drawer, Dialog, Popup, Checkbox, Dropdown e Select.

Valeu Ruan!