@@ -5,6 +5,7 @@ import { motion, AnimatePresence } from "framer-motion";
55import Image from "next/image" ;
66import Lenis from "@studio-freight/lenis" ;
77import { useMediaQuery } from "react-responsive" ;
8+ import TypewriterText from "../components/animation/typewriterText" ;
89
910const slides = [
1011 {
@@ -37,7 +38,7 @@ const slides = [
3738 content : "언제든 다시 볼 수 있게\n공고를 스크랩하세요" ,
3839 image : "/images/land/step2-2.jpg" ,
3940 blackAreaTitle : "사장님 이용 방법" ,
40- blackAreaContent : "워크채널 우측의 [폼 만들기] 버튼을 클릭하고\n인재 채용을 시작하세요" ,
41+ blackAreaContent : "워크채널 우의 [폼 만들기] 버튼을 클릭하고\n인재 채용을 시작하세요" ,
4142 blackAreaImage : "/images/land/step2-1.jpg" ,
4243 } ,
4344 {
@@ -60,6 +61,21 @@ const bounceAnimation = {
6061 } ,
6162} ;
6263
64+ // 배경 그라데이션 애니메이션 추가
65+ const backgroundVariants = {
66+ initial : {
67+ background : "linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%)" ,
68+ } ,
69+ animate : {
70+ background : "linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%)" ,
71+ transition : {
72+ duration : 20 ,
73+ repeat : Infinity ,
74+ repeatType : "reverse" as const ,
75+ } ,
76+ } ,
77+ } ;
78+
6379export default function LandingPage ( ) {
6480 const isLargeScreen = useMediaQuery ( { minWidth : 641 } ) ;
6581 const [ currentSlide , setCurrentSlide ] = useState ( 0 ) ;
@@ -71,12 +87,12 @@ export default function LandingPage() {
7187 useEffect ( ( ) => {
7288 setIsLoaded ( true ) ;
7389 lenisRef . current = new Lenis ( {
74- duration : 0.8 , // 1.2에서 0.8로 변경
75- easing : ( t : number ) => Math . min ( 1 , 1.001 - Math . pow ( 2 , - 10 * t ) ) ,
90+ duration : 1.2 ,
91+ easing : ( t ) => Math . min ( 1 , 1.001 - Math . pow ( 2 , - 10 * t ) ) ,
7692 smoothWheel : true ,
7793 wheelMultiplier : 2 ,
78- touchMultiplier : 1. 2,
79- infinite : false ,
94+ touchMultiplier : 2 ,
95+ infinite : true ,
8096 } ) ;
8197
8298 function raf ( time : number ) {
@@ -87,36 +103,38 @@ export default function LandingPage() {
87103 requestAnimationFrame ( raf ) ;
88104
89105 let lastScrollTime = 0 ;
90- const scrollThreshold = 500 ; // ms
106+ const scrollThreshold = 150 ;
91107
92108 lenisRef . current . on ( "scroll" , ( { progress } : { progress : number } ) => {
93109 const currentTime = Date . now ( ) ;
94110 if ( currentTime - lastScrollTime < scrollThreshold ) return ;
95111
96112 if ( ! isScrollingRef . current ) {
97113 const totalSlides = slides . length ;
98- const newSlideIndex = Math . min ( slides . length - 1 , Math . max ( 0 , Math . round ( progress * ( totalSlides - 1 ) ) ) ) ;
114+ const progressPerSlide = 1 / ( totalSlides - 1 ) ;
115+ const currentProgress = progress / progressPerSlide ;
116+ let newSlideIndex = Math . round ( currentProgress ) ;
117+
118+ if ( progress >= 0.99 ) {
119+ newSlideIndex = 0 ;
120+ setTimeout ( ( ) => {
121+ lenisRef . current ?. scrollTo ( 0 , {
122+ duration : 1.2 ,
123+ easing : ( t ) => t * ( 2 - t ) ,
124+ } ) ;
125+ } , 100 ) ;
126+ }
99127
100128 if ( newSlideIndex !== currentSlide ) {
101129 setCurrentSlide ( newSlideIndex ) ;
102130 isScrollingRef . current = true ;
103- const targetScroll =
104- ( newSlideIndex / ( totalSlides - 1 ) ) *
105- ( isLargeScreen ? containerRef . current ! . scrollHeight : containerRef . current ! . scrollWidth ) ;
106-
107- lenisRef . current ?. scrollTo ( targetScroll , {
108- immediate : false ,
109- duration : 600 , // 800에서 600으로 변경
110- easing : ( t : number ) => t * ( 2 - t ) ,
111- } ) ;
112-
113- lastScrollTime = currentTime ;
114131
115132 setTimeout ( ( ) => {
116133 isScrollingRef . current = false ;
117- } , 600 ) ; // 800에서 600으로 변경
134+ } , 200 ) ;
118135 }
119136 }
137+ lastScrollTime = currentTime ;
120138 } ) ;
121139
122140 document . documentElement . style . scrollbarWidth = "none" ;
@@ -143,34 +161,41 @@ export default function LandingPage() {
143161 < AnimatePresence >
144162 { isLoaded && (
145163 < motion . div
146- initial = { { opacity : 0 } }
147- animate = { { opacity : 1 } }
164+ variants = { backgroundVariants }
165+ initial = "initial"
166+ animate = "animate"
148167 exit = { { opacity : 0 } }
149168 transition = { { duration : 1 } }
150169 ref = { containerRef }
151- className = { `h-[400vh] min-h-[768px] overflow-hidden bg-gradient-to-br from-[#1a1a1a] to-[#2a2a2a] ${
152- isLargeScreen ? "" : "flex flex-col"
153- } `}
170+ className = { `relative h-[400vh] min-h-[768px] overflow-hidden ${ isLargeScreen ? "" : "flex flex-col" } ` }
154171 >
155- < div className = { `fixed inset-0 ${ isLargeScreen ? "flex" : "flex flex-col" } ` } >
172+ < div className = "absolute inset-0 opacity-20" >
173+ < div className = "particles-container" />
174+ </ div >
175+
176+ < motion . div
177+ className = { `fixed inset-0 ${ isLargeScreen ? "flex" : "flex flex-col" } ` }
178+ style = { {
179+ backdropFilter : "blur(8px)" ,
180+ WebkitBackdropFilter : "blur(8px)" ,
181+ } }
182+ >
156183 < motion . div
157- className = { `relative ${ isLargeScreen ? "w-1/2" : "flex h-1/2 w-full items-center justify-center overflow-hidden" } ` }
184+ className = { `relative ${
185+ isLargeScreen ? "w-1/2" : "flex h-1/2 w-full items-center justify-center overflow-hidden"
186+ } `}
158187 animate = { {
159188 width : currentSlide === 0 ? "100%" : isLargeScreen ? "50%" : "100%" ,
160189 height : currentSlide === 0 ? "100%" : isLargeScreen ? "100%" : "50%" ,
161190 } }
162191 transition = { { duration : 0.8 , ease : "easeInOut" } }
163192 >
164193 < motion . div
165- className = "absolute inset-0 flex items-center justify-center"
166- initial = { { opacity : 0 } }
194+ className = "relative flex h-full w-full items-center justify-center"
167195 animate = { {
168- opacity : 1 ,
169- } }
170- transition = { {
171- duration : 0.8 , // Updated transition duration
172- ease : "easeInOut" ,
196+ scale : currentSlide === 0 ? 1 : 0.8 ,
173197 } }
198+ transition = { { duration : 0.8 , ease : "easeInOut" } }
174199 >
175200 { currentSlide === 0 ? (
176201 < Image
@@ -179,6 +204,7 @@ export default function LandingPage() {
179204 width = { 800 }
180205 height = { 800 }
181206 className = "h-full max-h-[800px] w-full max-w-[800px] object-contain"
207+ priority
182208 />
183209 ) : currentSlide === 4 || currentSlide === 5 ? (
184210 < motion . div
@@ -187,20 +213,20 @@ export default function LandingPage() {
187213 initial = { { opacity : 0 , y : 20 } }
188214 animate = { { opacity : 1 , y : 0 } }
189215 exit = { { opacity : 0 , y : - 20 } }
190- transition = { { duration : 1.2 , ease : "easeInOut" } } // Update 1
216+ transition = { { duration : 1.2 , ease : "easeInOut" } }
191217 >
192218 < motion . h2
193- className = "mb-2 mt-0 text-center text-xl font-semibold text-gray-100 max-[640px]:mb-1 max-[640px]:px-4 md:mb-4 md:mt-0 md:text-3xl"
219+ className = "mb-2 mt-0 text-center text-xl font-semibold text-gray-100 drop-shadow-[0_2px_2px_rgba(0,0,0,0.3)] max-[640px]:mb-1 max-[640px]:px-4 md:mb-4 md:mt-0 md:text-3xl"
194220 initial = { { opacity : 0 , y : 20 } }
195221 animate = { { opacity : 1 , y : 0 } }
196- transition = { { delay : 0.3 , duration : 0.6 } } // Update 2
222+ transition = { { delay : 0.3 , duration : 0.6 } }
197223 >
198- { slides [ currentSlide ] . blackAreaTitle }
224+ < TypewriterText text = { slides [ currentSlide ] . blackAreaTitle || "" } />
199225 </ motion . h2 >
200226 < motion . div
201227 initial = { { opacity : 0 , y : 20 } }
202228 animate = { { opacity : 1 , y : 0 } }
203- transition = { { delay : 0.4 , duration : 0.6 } } // Update 2
229+ transition = { { delay : 0.4 , duration : 0.6 } }
204230 className = "mb-2 w-full max-w-[600px] max-[640px]:max-w-[75%] max-[640px]:px-4 md:mb-4"
205231 style = { { maxHeight : "calc(100% - 12rem)" } }
206232 >
@@ -221,28 +247,27 @@ export default function LandingPage() {
221247 className = "mb-0 max-w-[600px] whitespace-pre-wrap text-center text-sm text-gray-200 max-[640px]:mt-1 max-[640px]:px-4 md:text-xl"
222248 initial = { { opacity : 0 , y : 20 } }
223249 animate = { { opacity : 1 , y : 0 } }
224- transition = { { delay : 0.5 , duration : 0.6 } } // Update 2
250+ transition = { { delay : 0.5 , duration : 0.6 } }
225251 >
226- { slides [ currentSlide ] . blackAreaContent }
252+ < TypewriterText text = { slides [ currentSlide ] . blackAreaContent || "" } />
227253 </ motion . p >
228254 </ motion . div >
229255 ) : (
230256 < motion . div
231- className = "absolute inset-0 flex items-center justify-center "
257+ className = "relative h-full max-h-[600px] w-full max-w-[600px] "
232258 initial = { { opacity : 0 } }
233259 animate = { { opacity : 1 } }
234260 exit = { { opacity : 0 } }
235- transition = { { duration : 0.8 } } // Update 3
261+ transition = { { duration : 0.8 } }
236262 >
237- < div className = "relative h-full max-h-[600px] w-full max-w-[600px]" >
238- < Image
239- src = "/brand.png"
240- alt = "Brand Logo"
241- fill
242- className = "object-contain"
243- sizes = "(max-width: 600px) 100vw, 600px"
244- />
245- </ div >
263+ < Image
264+ src = "/brand.png"
265+ alt = "Brand Logo"
266+ fill
267+ className = "object-contain"
268+ sizes = "(max-width: 600px) 100vw, 600px"
269+ priority
270+ />
246271 </ motion . div >
247272 ) }
248273 </ motion . div >
@@ -267,75 +292,103 @@ export default function LandingPage() {
267292 < AnimatePresence mode = "wait" >
268293 { currentSlide > 0 && (
269294 < motion . div
270- key = { currentSlide }
271- className = { `relative z-40 flex ${
272- isLargeScreen ? "w-1/2" : "h-1/2 w-full"
273- } flex-col items-center justify-center p-4 pb-6 pt-4 max-[640px]:px-12 max-[640px]:py-3 md:p-6 md:pb-8 md:pt-6`}
295+ className = { `relative z-40 flex ${ isLargeScreen ? "w-1/2" : "h-1/2 w-full" } ` }
274296 style = { {
275297 background : "linear-gradient(135deg, #71db77 0%, #56c45d 100%)" ,
276298 display : "flex" ,
277299 flexDirection : "column" ,
278300 justifyContent : "center" ,
279301 alignItems : "center" ,
280302 height : isLargeScreen ? "100%" : "50%" ,
303+ overflow : "hidden" ,
281304 } }
282- initial = { { opacity : 0 , [ isLargeScreen ? "y" : "x" ] : isLargeScreen ? "100%" : "100%" } }
283- animate = { { opacity : 1 , [ isLargeScreen ? "y" : "x" ] : 0 } }
284- exit = { { opacity : 0 , [ isLargeScreen ? "y" : "x" ] : isLargeScreen ? "-100%" : "-100%" } }
285- transition = { { duration : 0.4 , ease : "easeInOut" } } // Updated transition duration
305+ initial = { { width : 0 } }
306+ animate = { { width : isLargeScreen ? "50%" : "100%" } }
307+ transition = { { duration : 0.8 , ease : "easeInOut" } }
286308 >
287- < motion . h2
288- className = "mb-2 mt-0 text-center text-xl font-semibold text-[#1a1a1a] max-[640px]:mb-1 max-[640px]:px-4 md:mb-4 md:mt-0 md:text-3xl"
289- initial = { { opacity : 0 , y : 20 } }
290- animate = { { opacity : 1 , y : 0 } }
291- transition = { { delay : 0.05 , duration : 0.3 } } // Updated transition
292- >
293- { slides [ currentSlide ] . title }
294- </ motion . h2 >
295- < motion . div
296- initial = { { opacity : 0 , y : 20 } }
297- animate = { { opacity : 1 , y : 0 } }
298- transition = { { delay : 0.1 , duration : 0.3 } } // Updated transition
299- className = "mb-2 w-full max-w-[600px] max-[640px]:max-w-[75%] max-[640px]:px-4 md:mb-4"
300- style = { { maxHeight : "calc(100% - 12rem)" } }
301- >
302- < div
303- className = "relative w-full overflow-hidden rounded-lg shadow-lg"
304- style = { { paddingBottom : "56.25%" } }
309+ < AnimatePresence mode = "wait" >
310+ < motion . div
311+ key = { currentSlide }
312+ className = "flex h-full w-full flex-col items-center justify-center p-4 pb-6 pt-4 max-[640px]:px-12 max-[640px]:py-3 md:p-6 md:pb-8 md:pt-6"
313+ initial = { { y : "100%" } }
314+ animate = { { y : 0 } }
315+ exit = { { y : "-100%" } }
316+ transition = { { duration : 0.4 , ease : "easeInOut" } }
305317 >
306- < Image
307- src = { slides [ currentSlide ] . image || "" }
308- alt = { slides [ currentSlide ] . title || "" }
309- fill
310- style = { { objectFit : "cover" } }
311- sizes = "(max-width: 768px) 100vw, 600px"
312- />
313- </ div >
314- </ motion . div >
315- < motion . p
316- className = "mb-0 max-w-[600px] whitespace-pre-wrap text-center text-sm text-[#1a1a1a] max-[640px]:mt-1 max-[640px]:px-4 md:text-xl"
317- initial = { { opacity : 0 , y : 20 } }
318- animate = { { opacity : 1 , y : 0 } }
319- transition = { { delay : 0.15 , duration : 0.3 } } // Updated transition
320- >
321- { slides [ currentSlide ] . content }
322- </ motion . p >
318+ < motion . h2
319+ className = "mb-2 mt-0 text-center text-xl font-semibold text-[#1a1a1a] max-[640px]:mb-1 max-[640px]:px-4 md:mb-4 md:mt-0 md:text-3xl"
320+ initial = { { opacity : 0 , y : 20 } }
321+ animate = { { opacity : 1 , y : 0 } }
322+ transition = { { delay : 0.05 , duration : 0.3 } }
323+ >
324+ < TypewriterText text = { slides [ currentSlide ] . title } />
325+ </ motion . h2 >
326+ < motion . div
327+ initial = { { opacity : 0 , y : 20 } }
328+ animate = { { opacity : 1 , y : 0 } }
329+ transition = { { delay : 0.1 , duration : 0.3 } }
330+ className = "mb-2 w-full max-w-[600px] max-[640px]:max-w-[75%] max-[640px]:px-4 md:mb-4"
331+ style = { { maxHeight : "calc(100% - 12rem)" } }
332+ >
333+ < div
334+ className = "relative w-full overflow-hidden rounded-lg shadow-lg transition-shadow duration-300 hover:shadow-2xl"
335+ style = { { paddingBottom : "56.25%" } }
336+ >
337+ < Image
338+ src = { slides [ currentSlide ] . image || "" }
339+ alt = { slides [ currentSlide ] . title || "" }
340+ fill
341+ style = { { objectFit : "cover" } }
342+ sizes = "(max-width: 768px) 100vw, 600px"
343+ className = "transition-transform duration-300 hover:scale-105"
344+ />
345+ </ div >
346+ </ motion . div >
347+ < motion . p
348+ className = "mb-0 max-w-[600px] whitespace-pre-wrap text-center text-sm text-[#1a1a1a] max-[640px]:mt-1 max-[640px]:px-4 md:text-xl"
349+ initial = { { opacity : 0 , y : 20 } }
350+ animate = { { opacity : 1 , y : 0 } }
351+ transition = { { delay : 0.15 , duration : 0.3 } }
352+ >
353+ < TypewriterText text = { slides [ currentSlide ] . content } />
354+ </ motion . p >
355+ </ motion . div >
356+ </ AnimatePresence >
323357 </ motion . div >
324358 ) }
325359 </ AnimatePresence >
326- </ div >
360+ </ motion . div >
327361
328362 { currentSlide > 0 && (
329- < div
330- className = { `fixed ${ isLargeScreen ? "right-4 top-1/2 -translate-y-1/2 space-y-2" : "bottom-4 left-1/2 flex -translate-x-1/2 space-x-2" } ` }
363+ < motion . div
364+ className = { `fixed ${
365+ isLargeScreen
366+ ? "right-8 top-1/2 -translate-y-1/2 space-y-3"
367+ : "bottom-8 left-1/2 flex -translate-x-1/2 space-x-3"
368+ } `}
331369 >
332370 { slides . slice ( 1 ) . map ( ( _ , index ) => (
333- < div
371+ < motion . div
334372 key = { index + 1 }
335- className = { `h-4 w-4 rounded-full ${ index + 1 === currentSlide ? "bg-[#108b2d]" : "bg-[#f8fff8]" } ` }
373+ className = "h-3 w-3 cursor-pointer rounded-full border-2 border-white/50"
374+ whileHover = { { scale : 1.2 } }
375+ animate = { {
376+ backgroundColor : index + 1 === currentSlide ? "#108b2d" : "transparent" ,
377+ scale : index + 1 === currentSlide ? 1.2 : 1 ,
378+ } }
379+ onClick = { ( ) => {
380+ if ( ! isScrollingRef . current ) {
381+ const targetScroll =
382+ ( ( index + 1 ) / ( slides . length - 1 ) ) * ( containerRef . current ?. scrollHeight || 0 ) ;
383+ lenisRef . current ?. scrollTo ( targetScroll , {
384+ duration : 1.2 ,
385+ easing : ( t ) => t * ( 2 - t ) ,
386+ } ) ;
387+ }
388+ } }
336389 />
337390 ) ) }
338- </ div >
391+ </ motion . div >
339392 ) }
340393 </ motion . div >
341394 ) }
0 commit comments