@@ -20,7 +20,6 @@ export function initHeroSectionAnimation({
2020} : HeroSectionRefs ) {
2121 ensurePlugins ( ) ;
2222
23- // gsap.context를 쓰면 cleanup이 아주 깔끔해짐
2423 const ctx = gsap . context ( ( ) => {
2524 gsap . fromTo (
2625 subTextEl ,
@@ -32,12 +31,103 @@ export function initHeroSectionAnimation({
3231 ease : 'power2.out' ,
3332 scrollTrigger : {
3433 trigger : titleEl ,
35- start : 'right bottom+=100 60% ' ,
34+ start : 'top center-=150 ' ,
3635 toggleActions : 'play none none reverse' ,
36+ // markers: true,
3737 } ,
3838 }
3939 ) ;
4040 } ) ;
4141
4242 return ( ) => ctx . revert ( ) ; // ✅ cleanup 반환
4343}
44+ export type FeatureListRefs = {
45+ titleEl : HTMLHeadingElement ; // "개발자 성장을 위한"
46+ subTextEl : HTMLDivElement ; // 서브 텍스트 wrapper
47+ cardsContainerEl : HTMLDivElement ; // grid wrapper
48+ } ;
49+
50+ export function initFeatureListAnimation ( {
51+ titleEl,
52+ subTextEl,
53+ cardsContainerEl,
54+ } : FeatureListRefs ) {
55+ ensurePlugins ( ) ;
56+
57+ const ctx = gsap . context ( ( ) => {
58+ // 카드들: grid 안의 직계 자식(FeatureCard wrapper)을 전부 잡음
59+ const cards = Array . from ( cardsContainerEl . children ) as HTMLElement [ ] ;
60+
61+ // 초기 상태 (깜빡임 방지)
62+ gsap . set ( subTextEl , { opacity : 0 , y : 24 } ) ;
63+ gsap . set ( cards , { opacity : 0 , y : 28 } ) ;
64+
65+ const tl = gsap . timeline ( {
66+ scrollTrigger : {
67+ trigger : titleEl ,
68+ start : 'top center-=50' , // 타이틀 끝이 화면 중간보다 조금 아래에 걸리면
69+ toggleActions : 'play none none reverse' ,
70+ // markers: true,
71+ } ,
72+ } ) ;
73+
74+ // 1) 서브 텍스트 먼저
75+ tl . to ( subTextEl , {
76+ opacity : 1 ,
77+ y : 0 ,
78+ duration : 0.6 ,
79+ ease : 'power2.out' ,
80+ } ) ;
81+
82+ // 2) 카드들 stagger로 아래→위 쓱~
83+ tl . to (
84+ cards ,
85+ {
86+ opacity : 1 ,
87+ y : 0 ,
88+ duration : 0.6 ,
89+ ease : 'power2.out' ,
90+ stagger : 0.12 ,
91+ } ,
92+ '-=0.15' // 서브텍스트 끝나기 조금 전에 겹쳐서 시작
93+ ) ;
94+ } ) ;
95+
96+ return ( ) => ctx . revert ( ) ;
97+ }
98+ export type LandingPreviewRefs = {
99+ rootEl : HTMLElement ; // 섹션 전체
100+ titleEl : HTMLHeadingElement ; // "체계적인 학습으로" h1
101+ contentContainerEl : HTMLDivElement ; // FeatureContent 감싸는 div
102+ } ;
103+
104+ export function initLandingPreviewAnimation ( {
105+ rootEl,
106+ titleEl,
107+ contentContainerEl,
108+ } : LandingPreviewRefs ) {
109+ ensurePlugins ( ) ;
110+
111+ const ctx = gsap . context ( ( ) => {
112+ const items = contentContainerEl . querySelectorAll < HTMLElement > ( '.fc-item' ) ;
113+
114+ // ✅ 레이아웃 유지한 채로 숨김 (grid 비율 절대 안 깨짐)
115+ gsap . set ( items , { opacity : 0 , y : 24 } ) ;
116+
117+ gsap . to ( items , {
118+ opacity : 1 ,
119+ y : 0 ,
120+ duration : 0.6 ,
121+ ease : 'power2.out' ,
122+ stagger : 0.12 ,
123+ scrollTrigger : {
124+ trigger : titleEl ,
125+ start : 'top center-=50' , // 필요하면 75%~85%로 튜닝
126+ toggleActions : 'play none none reverse' ,
127+ // markers: true,
128+ } ,
129+ } ) ;
130+ } , rootEl ) ;
131+
132+ return ( ) => ctx . revert ( ) ;
133+ }
0 commit comments