working animations

This commit is contained in:
2026-03-13 22:18:44 +01:00
parent d71bb5d26e
commit 4293eef824
8 changed files with 44 additions and 45 deletions

View File

@@ -1,5 +1,6 @@
import { useGSAP } from "@gsap/react";
import { useLayoutEffect,
import { useEffect,
useLayoutEffect,
useRef, type ReactNode } from "react";
import { useGsapContext } from "~/app/_providers/GsapProvicer";
import { SplitText } from "gsap/SplitText";
@@ -7,7 +8,7 @@ import gsap from 'gsap'
const AnimateTextIn = ({children,animation="type",position}:{children:ReactNode,animation?:"type"|"slide",position:gsap.Position}) => {
const el = useRef<HTMLDivElement>(null)
const gsapContext = useGsapContext();
useGSAP(() => {
useEffect(() => {
console.log("aniamte text with:",position)
const tl = gsap.timeline();
const chars = new SplitText(el.current,{type:'chars'})

View File

@@ -102,7 +102,10 @@ export default function AnimatedBackgroundContainer({
const [mounted, setMounted] = useState(false);
const { resolvedTheme } = useTheme();
const isDark = resolvedTheme === "dark";
let isDark = resolvedTheme === "dark";
if (resolvedTheme == undefined) {
isDark = true;
}
const palette = isDark ? PALETTES.dark : PALETTES.light;
/* Spawn particles */
@@ -264,7 +267,6 @@ export default function AnimatedBackgroundContainer({
minHeight: "100vh",
width: "100%",
overflow: "hidden",
backgroundColor: palette.base,
transition: "background-color 0.6s ease",
}}
>

View File

@@ -7,19 +7,16 @@ const AnimatedPageTitle = (
) => {
const el = useRef<HTMLHeadingElement>(null)
const gsapContext = useGsapContext();
useGSAP(() => {
useEffect(() => {
console.log("add animated title with:",position)
const tl = gsap.timeline();
tl.addLabel("title")
const split = new SplitText(el.current, { type: "chars" })
tl.from(el.current, { opacity: 0 })
tl.from(split.chars, {
gsapContext?.addAnimation(gsap.to(el.current, { opacity: 100 }),position)
gsapContext?.addAnimation(gsap.from(split.chars, {
stagger: 0.05, rotate: -90, opacity: 0, x: -10
}, '>')
gsapContext?.addAnimation(tl,position)
}),'>')
})
return (
<h1 className="text-4xl opacity-100 font-bold text-balance w-full" ref={el}> {text} </h1>
<h1 className="text-4xl opacity-0 font-bold text-balance w-full" ref={el}> {text} </h1>
)
}

View File

@@ -4,10 +4,18 @@ import { Moon, Sun } from "lucide-react"
import { useEffect } from "react"
type Props = {activeTheme:string|undefined}
const ThemeIcon = (props:Props) => {
if (props.activeTheme == "dark") {
return (<Sun/>)
} else {
return (<Moon/>)
}
return (
<>
{props.activeTheme && props.activeTheme == 'dark' &&
<Sun/>
}
{props.activeTheme && props.activeTheme == 'light' &&
<Moon/>
}
{!props.activeTheme &&
<Sun/>
}
</>
)
}
export default ThemeIcon;

View File

@@ -8,6 +8,9 @@ import ThemeIcon from "./ThemeIcon"
export function ThemeSwitch() {
const { setTheme, theme } = useTheme()
if (!theme) {
setTheme('dark')
}
const toggleTheme = () => {
setTheme(theme == "dark" ? "light" : "dark")
}

View File

@@ -27,12 +27,11 @@ export default function GsapProvider({ children }: { children: ReactNode }) {
if (!tl.current) {
tl.current = gsap.timeline({ paused: true })
}
console.log("resuming timeline:",tl.current)
return () => { console.log("gsap cleanup") }
})
const addAnimation = useCallback((animation: gsap.core.TimelineChild, position: gsap.Position) => {
console.log("add animation to:", position)
console.log("add animation to:", position, tl.current !== undefined)
tl.current?.add(animation, position);
},[])
const resetTimeline = useCallback(() => {
@@ -41,6 +40,7 @@ export default function GsapProvider({ children }: { children: ReactNode }) {
tl.current = gsap.timeline({paused:true})
},[])
const resumeTimeline = useCallback(() => {
console.log("resuming timeline:",tl.current)
tl.current?.resume()
},[])
return (

View File

@@ -1,23 +1,10 @@
'use client'
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
export default function ThemeProvider({children}:{children: React.ReactNode}) {
const [mounted,setMounted] = React.useState(false)
React.useEffect(() => {
setMounted(true)
})
if (mounted) {
return (
<NextThemesProvider disableTransitionOnChange nonce="test" attribute="class" defaultTheme="dark">
{children}
</NextThemesProvider>
)
} else {
return (
<>
{children}
</>
)
}
return (
<NextThemesProvider disableTransitionOnChange attribute="class" defaultTheme="dark">
{children}
</NextThemesProvider>
)
}

View File

@@ -1,6 +1,8 @@
'use client'
import { useGSAP } from "@gsap/react";
import { useLayoutEffect,
import { useEffect,
useEffectEvent,
useLayoutEffect,
useRef } from "react";
import { trpc } from "~/app/_trpc/Client";
import * as Card from "~/components/ui/card";
@@ -8,21 +10,20 @@ import { useGsapContext } from "../_providers/GsapProvicer";
import AnimatedPageTitle from "../_components/Animated/AnimatedPageTitle";
import { Spinner } from "~/components/ui/spinner";
import AnimateTextIn from "../_components/Animated/AnimateIn";
import gsap from 'gsap'
export default function MusicPage() {
const { data: tracks, isLoading } = trpc.music.list.useQuery();
const container = useRef<HTMLDivElement>(null)
const gsapContext = useGsapContext();
useGSAP(() => {
gsapContext?.resumeTimeline()
useEffect(() => {
if (tracks) {
gsapContext?.resumeTimeline()
}
return () => {
console.log("page cleanup")
gsapContext?.resetTimeline()
}
});
},[tracks]);
return (<>
<div ref={container} className="w-full h-full max-w-4xl mx-auto px-4 py-8 flex flex-col gap-4">
<div className="w-full h-full max-w-4xl mx-auto px-4 py-8 flex flex-col gap-4">
<AnimatedPageTitle position={0} text="Just Some Music I Made"/>
<AnimateTextIn position={0.5}>
<div className="flex flex-row h-8 content-center items-center">