From 9c5aec01e0b8fdd54d95976602d6c5b23044c6e8 Mon Sep 17 00:00:00 2001 From: Gregor Lohaus Date: Tue, 10 Mar 2026 21:14:36 +0100 Subject: [PATCH 1/2] fix create techstack form --- .../admin/project/techStack/_components/CreateUpdateForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/admin/project/techStack/_components/CreateUpdateForm.tsx b/src/app/admin/project/techStack/_components/CreateUpdateForm.tsx index 779e86b..0216849 100644 --- a/src/app/admin/project/techStack/_components/CreateUpdateForm.tsx +++ b/src/app/admin/project/techStack/_components/CreateUpdateForm.tsx @@ -31,7 +31,7 @@ export default function CreateUpdateStackForm(params: { className?: string, enti const deleteMutation = trpc.techStack.delete.useMutation({ onSuccess: makeOnSuccess('delete', form, undefined, path, router) }) function onSubmit(values: z.infer) { setSubmitted(true) - params.entity ? + id ? updateMutation.mutate(values) : createMutation.mutate(values); } From dfaba3a24e3796a786b44a6b83d0761d1b9742df Mon Sep 17 00:00:00 2001 From: Gregor Lohaus Date: Mon, 30 Mar 2026 14:13:04 +0200 Subject: [PATCH 2/2] animation stuff --- .gitignore | 1 + src/app/_components/Animated/AnimateIn.tsx | 29 ++-- src/app/_components/Animated/AnimatePopUp.tsx | 22 +++ src/app/_components/Animated/AnimatedDiv.tsx | 41 ++++++ .../Animated/AnimatedPageTitle.tsx | 15 +- src/app/_components/TopNav.tsx | 2 +- src/app/_providers/GsapProvicer.tsx | 8 +- src/app/music/page.tsx | 59 ++++---- src/app/projects/page.tsx | 139 ++++++++++-------- 9 files changed, 212 insertions(+), 104 deletions(-) create mode 100644 src/app/_components/Animated/AnimatePopUp.tsx create mode 100644 src/app/_components/Animated/AnimatedDiv.tsx diff --git a/.gitignore b/.gitignore index 2232c13..fe96094 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ yarn-error.log* # clerk configuration (can include secrets) /.clerk/ .worktrees +.claudesession diff --git a/src/app/_components/Animated/AnimateIn.tsx b/src/app/_components/Animated/AnimateIn.tsx index 6741860..aaa9c07 100644 --- a/src/app/_components/Animated/AnimateIn.tsx +++ b/src/app/_components/Animated/AnimateIn.tsx @@ -1,30 +1,41 @@ import { useGSAP } from "@gsap/react"; -import { useRef, type ReactNode } from "react"; +import { useRef, type HTMLAttributes, type ReactNode } from "react"; import { useGsapContext } from "~/app/_providers/GsapProvicer"; import { SplitText } from "gsap/SplitText"; import gsap from 'gsap' -const AnimateTextIn = ({children,animation="type",position}:{children:ReactNode,animation?:"type"|"slide",position:gsap.Position}) => { +import { cn } from "~/lib/utils"; +const AnimateTextIn = ({ + children, + animation = "type", + position, + className +}: { + children: ReactNode, + animation?: "type" | "slide", + position: gsap.Position, + className?:HTMLAttributes['className'] +}) => { const el = useRef(null) const gsapContext = useGsapContext(); useGSAP(() => { const rect = el.current?.getBoundingClientRect() const isInView = rect && rect.top < window.innerHeight - const chars = new SplitText(el.current,{type:'chars'}) - gsapContext?.addAnimation(gsap.to(el.current,{opacity:100, duration:0}),0) + const chars = new SplitText(el.current, { type: 'chars' }) + gsapContext?.addAnimation(gsap.to(el.current, { opacity: 100, duration: 0 }), 0) const fromVars = animation === "slide" - ? {opacity:0, x:-10, duration: 0.2, stagger: {each: 0.08}, ease:'bounce.inOut'} - : {opacity:0, duration: 0.01, stagger: {each: 0.04}, ease: 'bounce.inOut'} + ? { opacity: 0, x: -10, duration: 0.2, stagger: { each: 0.08 }, ease: 'bounce.inOut', onComplete: () => chars.revert() } + : { opacity: 0, duration: 0.01, stagger: { each: 0.04 }, ease: 'bounce.inOut', onComplete: () => chars.revert() } if (isInView) { - gsapContext?.addAnimation(gsap.from(chars.chars, fromVars),position) + gsapContext?.addAnimation(gsap.from(chars.chars, fromVars), position) } else { gsap.from(chars.chars, { ...fromVars, scrollTrigger: { trigger: el.current, start: 'top 85%', scroller: gsapContext?.getScroller() } }) } }, { dependencies: [] }) return ( -
+
{children}
- ) + ) } export default AnimateTextIn; diff --git a/src/app/_components/Animated/AnimatePopUp.tsx b/src/app/_components/Animated/AnimatePopUp.tsx new file mode 100644 index 0000000..e7ae443 --- /dev/null +++ b/src/app/_components/Animated/AnimatePopUp.tsx @@ -0,0 +1,22 @@ +import { type HTMLAttributes, type ReactNode } from "react"; +import AnimatedDiv from "./AnimatedDiv"; +import { cn } from "~/lib/utils"; +const AnimatePopUp = ({ + children, + position, + className, + duration=1, + ease='elastic' +}:{ + children:ReactNode + position:gsap.Position, + className?:HTMLAttributes['className'] + duration?:number, + ease?:gsap.EaseString|gsap.EaseFunction + }) => { + return ( + + ) +} + +export default AnimatePopUp; diff --git a/src/app/_components/Animated/AnimatedDiv.tsx b/src/app/_components/Animated/AnimatedDiv.tsx new file mode 100644 index 0000000..6a9e2aa --- /dev/null +++ b/src/app/_components/Animated/AnimatedDiv.tsx @@ -0,0 +1,41 @@ +import gsap from "gsap"; +import { type HTMLAttributes, +type ReactNode, useLayoutEffect, useRef } from "react"; +import { useGsapContext } from "~/app/_providers/GsapProvicer"; +const AnimatedDiv = ( + { + children, + position, + className, + animationMode='to', + ...tweenVars + }: + gsap.TweenVars & { + children:ReactNode, + position:gsap.Position, + animationMode?: 'from'|'to', + className?:HTMLAttributes['className'] + } +) => { + const div = useRef(null); + const gsapContext = useGsapContext() + useLayoutEffect(() => { + let tween:gsap.core.Tween; + switch(animationMode) { + case 'from': + tween = gsap.from(div.current,tweenVars); + break; + case 'to': + tween = gsap.to(div.current,tweenVars); + break; + } + gsapContext?.addAnimation(tween,position) + },[]) + return ( +
+ {children} +
+ ) +} + +export default AnimatedDiv; diff --git a/src/app/_components/Animated/AnimatedPageTitle.tsx b/src/app/_components/Animated/AnimatedPageTitle.tsx index 43b650a..f4461e7 100644 --- a/src/app/_components/Animated/AnimatedPageTitle.tsx +++ b/src/app/_components/Animated/AnimatedPageTitle.tsx @@ -1,22 +1,21 @@ -import { useGSAP } from "@gsap/react"; import { useEffect, useLayoutEffect, useRef } from "react"; +import { useGSAP } from "@gsap/react"; import { useEffect, useLayoutEffect, useRef,type ReactNode } from "react"; import { useGsapContext } from "~/app/_providers/GsapProvicer"; import { SplitText } from "gsap/SplitText"; import gsap from 'gsap' const AnimatedPageTitle = ( - { text, position }: { text: string, position:gsap.Position } + { children, position }: { children: ReactNode, position:gsap.Position } ) => { const el = useRef(null) const gsapContext = useGsapContext(); - useEffect(() => { - console.log("add animated title with:",position) - const split = new SplitText(el.current, { type: "chars" }) + useLayoutEffect(() => { + const split = new SplitText(el.current, { type: "lines,chars", autoSplit:true }) 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(gsap.from(split.chars, { id: 'titlesplit', + stagger: 0.05, rotate: -90, opacity: 0, x: -10, onComplete: () => {split.revert()} }),'>') },[]) return ( -

{text}

+

{children}

) } diff --git a/src/app/_components/TopNav.tsx b/src/app/_components/TopNav.tsx index f9f4716..30e326a 100644 --- a/src/app/_components/TopNav.tsx +++ b/src/app/_components/TopNav.tsx @@ -7,7 +7,7 @@ import { ThemeSwitch } from "./ThemeSwitch" export default function TopNav() { return ( -
+
- )} - {project.techStack?.stackItems && project.techStack.stackItems.length > 0 && ( -
- {project.techStack.stackItems.map((item) => ( - - ))} -
- )} - - )} - + + )} + +
+
))} -
+ ); }