4646. badge span { color : var ( --dim ) ; font-weight : 400 ; text-transform : none; letter-spacing : 0 }
4747h1 { margin : 18px 0 0 ; font-weight : 500 ; font-size : 44px ; line-height : 1 ; letter-spacing : -.04em ; color : # fff; text-wrap : balance}
4848html [data-lang = "ko" ] h1 { font-size : 40px ; letter-spacing : -.03em ; line-height : 1.1 }
49- @media ( min-width : 760px ) { h1 { font-size : 86 px ; letter-spacing : -.046em } html [data-lang = "ko" ] h1 { font-size : 72 px ; letter-spacing : -.035em } }
49+ @media ( min-width : 760px ) { h1 { font-size : 72 px ; letter-spacing : -.046em } html [data-lang = "ko" ] h1 { font-size : 64 px ; letter-spacing : -.035em } }
5050h1 em { font-style : normal; color : # 9fd4ff}
5151h2 { margin : 16px 0 0 ; font-weight : 500 ; font-size : 32px ; line-height : 1.02 ; letter-spacing : -.04em ; color : # fff; text-wrap : balance}
5252html [data-lang = "ko" ] h2 { font-size : 28px ; letter-spacing : -.025em ; line-height : 1.15 }
7070. hero . lead { margin : 18px auto 0 ; max-width : 560px ; color : var ( --mist ) ; font-size : 17px }
7171. hero . sub { font-size : 17px }
7272. ctas { display : flex; justify-content : center; gap : 10px ; flex-wrap : wrap; margin-top : 26px }
73- . hero . badge , . hero h1 , . hero . sub , . hero . ctas { opacity : 0 ; transform : translateY ( 12px ) ; transition : opacity 1.2 s cubic-bezier ( .165 , .84 , .44 , 1 ) 2 .2s , transform 1.2 s cubic-bezier ( .165 , .84 , .44 , 1 ) 2 .2s }
74- . hero h1 { transition-delay : 2 .3s } . hero . sub { transition-delay : 2 .5s } . hero . ctas { transition-delay : 2 .7s }
73+ . hero . badge , . hero h1 , . hero . sub , . hero . ctas { opacity : 0 ; transform : translateY ( 12px ) ; transition : opacity 1 s cubic-bezier ( .165 , .84 , .44 , 1 ) 1 .2s , transform 1 s cubic-bezier ( .165 , .84 , .44 , 1 ) 1 .2s }
74+ . hero h1 { transition-delay : 1 .3s } . hero . sub { transition-delay : 1 .5s } . hero . ctas { transition-delay : 1 .7s }
7575. hero . in . badge , . hero . in h1 , . hero . in . sub , . hero . in . ctas { opacity : 1 ; transform : none}
7676@media ( prefers-reduced-motion : reduce) { . hero . badge , . hero h1 , . hero . sub , . hero . ctas { opacity : 1 ; transform : none; transition : none} }
7777
@@ -507,12 +507,12 @@ <h2><k>20분 진단 하나로 시작하세요.</k><e>Start with one 20-minute ch
507507const BG = [ ] ; for ( let i = 0 ; i < 18 ; i ++ ) { BG . push ( { y :.04 + i * .053 + ( Math . random ( ) - .5 ) * .03 , amp :6 + Math . random ( ) * 34 , len :220 + Math . random ( ) * 800 , sp :( .2 + Math . random ( ) * .5 ) * ( i % 2 ?1 :- 1 ) , w :.6 + Math . random ( ) * 1.6 , a :.04 + Math . random ( ) * .09 , ph :Math . random ( ) * 6.28 } ) ; }
508508function drawBg ( el ) { for ( const b of BG ) { c . beginPath ( ) ; for ( let x = - 20 ; x <= W + 20 ; x += 8 ) { const y = H * b . y + Math . sin ( ( x / b . len ) * 6.283 + el * b . sp + b . ph ) * b . amp + Math . sin ( ( x / ( b . len * .37 ) ) * 6.283 - el * b . sp * 1.7 ) * b . amp * .25 ; x === - 20 ?c . moveTo ( x , y ) :c . lineTo ( x , y ) ; } c . strokeStyle = `rgba(190,228,255,${ b . a } )` ; c . lineWidth = b . w ; c . stroke ( ) ; } }
509509let start = null , mx = 0 , my = 0 , tx = 0 , ty = 0 ; addEventListener ( 'pointermove' , e => { tx = ( e . clientX / W - .5 ) * .5 ; ty = ( e . clientY / H - .5 ) * .35 } ) ;
510- const ease = x => x < .5 ?4 * x * x * x :1 - Math . pow ( - 2 * x + 2 , 3 ) / 2 ;
510+ const ease = x => x < .5 ?16 * x * x * x * x * x :1 - Math . pow ( - 2 * x + 2 , 5 ) / 2 ;
511511let running = true ; new IntersectionObserver ( es => { running = es [ 0 ] . isIntersecting } , { threshold :0 } ) . observe ( cv ) ;
512512function frame ( ts ) { if ( start === null ) start = ts ; let el = ( ts - start ) / 1000 ; if ( still ) el = Math . max ( el , 4 ) ;
513- const m = ease ( Math . min ( 1 , Math . max ( 0 , ( el - 1 .1) / 1.9 ) ) ) ; mx += ( tx - mx ) * .05 ; my += ( ty - my ) * .05 ;
513+ const g = ease ( Math . min ( 1 , Math . max ( 0 , ( el - .1 ) / 2.4 ) ) ) ; const m = g ; // g: 구의 성장(0→1). 파도 점은 각자 다른 시각에 감겨 들어온다mx +=(tx-mx)*.05;my+=(ty-my)*.05;
514514 c . clearRect ( 0 , 0 , W , H ) ; drawBg ( el ) ;
515- const R = Math . min ( W , H ) * ( mobile ? .27 :.23 ) , cx = W / 2 , cy = H * .30 , rot = el * .22 + mx * 2.2 , tilt = .35 + my ;
515+ const R = mobile ? Math . min ( W , H ) * .27 :Math . min ( W * .19 , H * .21 ) , cx = W / 2 , cy = mobile ? H * .30 : H * .25 , rot = el * .22 + mx * 2.2 , tilt = .35 + my ;
516516 if ( m > 0 ) { // 몸체: 바다색 구 + 왼쪽 위 하이라이트 + 대기 빛
517517 const g = c . createRadialGradient ( cx - R * .4 , cy - R * .4 , R * .05 , cx , cy , R ) ; g . addColorStop ( 0 , `rgba(120,190,255,${ m } )` ) ; g . addColorStop ( .5 , `rgba(22,96,190,${ m } )` ) ; g . addColorStop ( 1 , `rgba(5,30,80,${ m } )` ) ;
518518 c . fillStyle = g ; c . beginPath ( ) ; c . arc ( cx , cy , R * m , 0 , Math . PI * 2 ) ; c . fill ( ) ;
@@ -531,12 +531,20 @@ <h2><k>20분 진단 하나로 시작하세요.</k><e>Start with one 20-minute ch
531531 // 여섯 줄 물결
532532 const ct = Math . cos ( tilt ) , st = Math . sin ( tilt ) ;
533533 for ( const w of wav ) {
534- const wx = ( ( w . t + el * .08 ) % 1 ) * W , amp = H * .035 , wy = H * ( .16 + w . row * .09 ) + Math . sin ( w . t * 12.57 + el * 1.6 + w . row * .9 ) * amp ;
534+ // 각자 도착 시각: 파도 위 위치와 줄에 따라 0.1~1.9초 사이에 출발, 1초 동안 감겨 들어온다
535+ const mi = ease ( Math . min ( 1 , Math . max ( 0 , ( el - ( .1 + w . t * 1.4 + ( w . row % 3 ) * .15 ) ) / 1.0 ) ) ) ;
536+ const sp = el * .08 + g * .6 ; const wx = ( ( w . t + sp ) % 1 ) * W , amp = H * .035 * ( 1 + g * 1.2 ) , wy = H * ( .16 + w . row * .09 ) + Math . sin ( w . t * 12.57 + el * ( 1.6 + g * 6 ) + w . row * .9 ) * amp ;
535537 const lon = w . t * 6.283 + el * w . sp * 2 + rot , lat = w . lat + Math . sin ( lon * 3 + w . ph ) * .05 ;
536538 let x = Math . cos ( lat ) * Math . cos ( lon ) , z = Math . cos ( lat ) * Math . sin ( lon ) , y = Math . sin ( lat ) ; const y2 = y * ct - z * st , z2 = y * st + z * ct ;
537- const gx = cx + x * R , gy = cy - y2 * R , vis = m < .5 ?1 :( z2 > 0 ?1 :0 ) ; if ( ! vis ) continue ;
538- const X = wx + ( gx - wx ) * m , Y = wy + ( gy - wy ) * m , d = m < .5 ?.8 :( z2 + 1 ) / 2 ;
539- c . fillStyle = `rgba(255,255,255,${ ( .35 + .55 * d ) * ( m < .5 ?1 :.9 ) } )` ; c . beginPath ( ) ; c . arc ( X , Y , 1.1 + 1.4 * d , 0 , Math . PI * 2 ) ; c . fill ( ) ; }
539+ const Rg = R * Math . max ( g , .05 ) , gx = cx + x * Rg , gy = cy - y2 * Rg ;
540+ if ( mi >= 1 && z2 <= 0 ) continue ; // 다 감긴 뒤 뒷면은 안 보임
541+ let X , Y ;
542+ if ( mi >= 1 ) { X = gx ; Y = gy ; }
543+ else { // 솜사탕: 중심을 향해 나선으로 감겨 들어간다
544+ const r0 = Math . hypot ( wx - cx , wy - cy ) , a0 = Math . atan2 ( wy - cy , wx - cx ) , r1 = Math . hypot ( gx - cx , gy - cy ) , a1 = Math . atan2 ( gy - cy , gx - cx ) ;
545+ const r = r0 + ( r1 - r0 ) * mi , ang = a0 + ( a1 - a0 ) * mi + ( 1 - mi ) * mi * 4 * 3.2 ; X = cx + Math . cos ( ang ) * r ; Y = cy + Math . sin ( ang ) * r ; }
546+ const d = mi < 1 ?.85 :( z2 + 1 ) / 2 ;
547+ c . fillStyle = `rgba(255,255,255,${ ( .35 + .55 * d ) } )` ; c . beginPath ( ) ; c . arc ( X , Y , 1.1 + 1.4 * d , 0 , Math . PI * 2 ) ; c . fill ( ) ; }
540548 if ( running || el < 4 ) requestAnimationFrame ( frame ) ; else setTimeout ( ( ) => requestAnimationFrame ( frame ) , 400 ) ; }
541549requestAnimationFrame ( frame ) ;
542550} ) ( ) ;
0 commit comments