diff --git a/src/app/(user-page)/mypage/page.tsx b/src/app/(user-page)/mypage/page.tsx index 6ba08b1..eee8a6d 100644 --- a/src/app/(user-page)/mypage/page.tsx +++ b/src/app/(user-page)/mypage/page.tsx @@ -16,7 +16,7 @@ export default async function MyPage() { await prefetchProfileData(queryClient); return ( -
+
diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1c330d1..1e25e4a 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,3 +1,4 @@ +import Footer from '@/components/common/Footer'; import Header from '@/components/common/Header'; import { ToastProvider } from '@/components/common/ToastContext'; import ReactQueryProviders from '@/hooks/useReactQuery'; @@ -51,6 +52,7 @@ export default async function RootLayout({
{children}
+
+ + {/* 푸터 섹션 - 중요 클래스: section fp-auto-height */} +
+
-
- {/* Next.js Image 대신 일반 img 태그 사용 */} -
- 모임 스타일에 맞는 모임 개설 - handleImageError(e, '모임 스타일에 맞는 모임 개설') - } - /> -
-
-
- 모임 공개 여부 -
-
- 비공개 -
+ +
+
-
-
- {typeof isMobile !== 'undefined' && - (isMobile ? ( -
- +
+
+ +
+ Copyright @ 2025 DEVING All Rights Reserved
- - -
- -
- {/* 섹션 3: CTA */} -
- -

- DEVING에서 -
- 당신의 모임을 바로 시작하세요! -

-
- - - -
-
- - {/* 푸터 섹션 - 중요 클래스: section fp-auto-height */} -
-
- -
- -
-
- +
+ - -
- Copyright @ 2025 DEVING All Rights Reserved -
-
- -
-
- - ); - }} - /> +
+
+ + ); + }} + /> +
); } diff --git a/src/components/common/Footer.tsx b/src/components/common/Footer.tsx new file mode 100644 index 0000000..6dce498 --- /dev/null +++ b/src/components/common/Footer.tsx @@ -0,0 +1,69 @@ +'use client'; + +import { motion } from 'framer-motion'; +import { usePathname } from 'next/navigation'; + +import DevingLogo from '../../assets/icon/devingLogo.svg'; +import GithubIcon from '../../assets/icon/github_icon.svg'; +import LandingLogo from '../../assets/icon/landing_logo.svg'; +import NotionIcon from '../../assets/icon/notion_icon.svg'; + +// 애니메이션 설정 +const animations = { + fadeIn: { + initial: { opacity: 0 }, + animate: { opacity: 1 }, + transition: { duration: 0.5 }, + }, +}; + +const Footer = () => { + const pathname = usePathname(); + const isLandingPage = pathname === '/'; + + // 랜딩 페이지일 경우 Footer를 렌더링하지 않음 + if (isLandingPage) { + return null; + } + + return ( +
+ +
+ +
+
+ +
+
+
+ Copyright @ 2025 DEVING All Rights Reserved +
+
+
+ + + + + + +
+
+ ); +}; + +export default Footer; diff --git a/src/components/landing/BasketballCat.tsx b/src/components/landing/BasketballCat.tsx new file mode 100644 index 0000000..e2ec10a --- /dev/null +++ b/src/components/landing/BasketballCat.tsx @@ -0,0 +1,113 @@ +'use client'; + +import { motion } from 'framer-motion'; + +import { useIsLargeScreen } from '../../hooks/useLargeScreen'; + +/** + * 농구공을 가진 하얀 고양이 컴포넌트 + * 1920px 이상에서만 애니메이션 작동 + */ +const BasketballCat = () => { + const isLargeScreen = useIsLargeScreen(); + + return ( + <> + {/* 하얀 고양이 - 가만히 있는 모습 */} + + + {/* 헤드셋 - 음악에 맞춰 움직이는 효과 */} + + + {/* 농구공 - 통통 튀는 효과 */} + + + ); +}; + +export default BasketballCat; diff --git a/src/components/landing/BeigeCat.tsx b/src/components/landing/BeigeCat.tsx new file mode 100644 index 0000000..2de2ad6 --- /dev/null +++ b/src/components/landing/BeigeCat.tsx @@ -0,0 +1,63 @@ +'use client'; + +import { motion } from 'framer-motion'; + +import { useIsLargeScreen } from '../../hooks/useLargeScreen'; + +/** + * 베이지색 고양이 컴포넌트 + * 1920px 이상에서만 애니메이션 작동 + */ +const BeigeCat = () => { + const isLargeScreen = useIsLargeScreen(); + + return ( + + ); +}; + +export default BeigeCat; diff --git a/src/components/landing/FrontNotebook.tsx b/src/components/landing/FrontNotebook.tsx new file mode 100644 index 0000000..e3928dd --- /dev/null +++ b/src/components/landing/FrontNotebook.tsx @@ -0,0 +1,64 @@ +'use client'; + +import { motion } from 'framer-motion'; + +import { useIsLargeScreen } from '../../hooks/useLargeScreen'; + +/** + * 앞 노트북 컴포넌트 + * 1920px 이상에서만 애니메이션 작동 + */ +const FrontNotebook = () => { + const isLargeScreen = useIsLargeScreen(); + + return ( + + ); +}; + +export default FrontNotebook; diff --git a/src/components/landing/GrayCat.tsx b/src/components/landing/GrayCat.tsx new file mode 100644 index 0000000..b859426 --- /dev/null +++ b/src/components/landing/GrayCat.tsx @@ -0,0 +1,182 @@ +'use client'; + +import { motion, useAnimationControls } from 'framer-motion'; +import { useEffect } from 'react'; + +import { useIsLargeScreen } from '../../hooks/useLargeScreen'; + +/** + * 회색 고양이 컴포넌트 - 통통 튀는 애니메이션과 고정된 화난 눈 표정 + * 1920px 이상에서만 애니메이션 작동 + * 1920px 미만에서는 눈썹이 보이지 않음 + */ +const GrayCat = () => { + // 화면 크기 감지 + const isLargeScreen = useIsLargeScreen(); + + // 모든 애니메이션을 제어할 컨트롤러 생성 + const controls = useAnimationControls(); + + useEffect(() => { + // 1920px 이상일 때만 애니메이션 실행 + if (isLargeScreen) { + const startAnimation = async () => { + // 초기 진입 애니메이션 + await controls.start('enter'); + + // 반복 애니메이션 시작 (통통 튀는 동작) + controls.start('loop'); + }; + + startAnimation(); + } else { + // 작은 화면에서는 정적 상태로 표시 + controls.start('static'); + } + }, [controls, isLargeScreen]); + + // 고양이 애니메이션에 대한 변형(variants) 정의 + const catVariants = { + initial: { + opacity: 0, + x: 40, + }, + enter: { + opacity: 1, + x: 0, + transition: { + type: 'spring', + stiffness: 60, + damping: 10, + delay: 1.0, + }, + }, + loop: { + y: [0, -8, 0], + transition: { + y: { + duration: 0.5, + repeat: Infinity, + repeatDelay: 2, + ease: 'easeOut', + repeatType: 'loop', + }, + }, + }, + static: { + opacity: 1, + x: 0, + y: 0, + }, + }; + + // 왼쪽 눈에 대한 변형(variants) - 45도 회전 유지 + const leftEyeVariants = { + initial: { + opacity: 0, + rotate: 45, + }, + enter: { + opacity: 1, + rotate: 45, + transition: { + delay: 1.5, + duration: 0.2, + }, + }, + loop: { + opacity: 1, + rotate: 45, + y: [0, -8, 0], + transition: { + y: { + duration: 0.5, + repeat: Infinity, + repeatDelay: 2, + ease: 'easeOut', + repeatType: 'loop', + }, + }, + }, + static: { + opacity: 1, + rotate: 45, + y: 0, + }, + }; + + // 오른쪽 눈에 대한 변형(variants) - -45도 회전 유지 + const rightEyeVariants = { + initial: { + opacity: 0, + rotate: -45, + }, + enter: { + opacity: 1, + rotate: -45, + transition: { + delay: 1.5, + duration: 0.2, + }, + }, + loop: { + opacity: 1, + rotate: -45, + y: [0, -8, 0], + transition: { + y: { + duration: 0.5, + repeat: Infinity, + repeatDelay: 2, + ease: 'easeOut', + repeatType: 'loop', + }, + }, + }, + static: { + opacity: 1, + rotate: -45, + y: 0, + }, + }; + + return ( + <> + {/* 회색 고양이 기본 이미지 - 통통 튀는 애니메이션 */} + + + {/* 화면 크기가 1920px 이상일 때만 눈썹 렌더링 */} + {isLargeScreen && ( + <> + {/* 화난 눈 모양 - 왼쪽 눈 (더 두껍고 아래로) */} + + + {/* 화난 눈 모양 - 오른쪽 눈 (더 두껍고 아래로) */} + + + )} + + ); +}; + +export default GrayCat; diff --git a/src/components/landing/HeartNotebookCat.tsx b/src/components/landing/HeartNotebookCat.tsx new file mode 100644 index 0000000..31c0278 --- /dev/null +++ b/src/components/landing/HeartNotebookCat.tsx @@ -0,0 +1,65 @@ +'use client'; + +import { motion } from 'framer-motion'; + +import { useIsLargeScreen } from '../../hooks/useLargeScreen'; + +/** + * 하트 노트북 컴포넌트 + * 1920px 이상에서만 애니메이션 작동 + */ +const HeartNotebookCat = () => { + const isLargeScreen = useIsLargeScreen(); + + return ( + + ); +}; + +export default HeartNotebookCat; diff --git a/src/components/landing/ReadingCat.tsx b/src/components/landing/ReadingCat.tsx new file mode 100644 index 0000000..c26605b --- /dev/null +++ b/src/components/landing/ReadingCat.tsx @@ -0,0 +1,58 @@ +'use client'; + +import { motion } from 'framer-motion'; + +import { useIsLargeScreen } from '../../hooks/useLargeScreen'; + +const ReadingCat = () => { + const isLargeScreen = useIsLargeScreen(); + + return ( + + ); +}; + +export default ReadingCat; diff --git a/src/components/landing/WhiteNotebookCat.tsx b/src/components/landing/WhiteNotebookCat.tsx new file mode 100644 index 0000000..a91ec52 --- /dev/null +++ b/src/components/landing/WhiteNotebookCat.tsx @@ -0,0 +1,62 @@ +'use client'; + +import { motion } from 'framer-motion'; + +import { useIsLargeScreen } from '../../hooks/useLargeScreen'; + +/** + * 흰색 노트북 고양이 컴포넌트 + * 노트북 화면 깜빡임 대신 키보드 타이핑하는 듯한 효과 + */ +const WhiteNotebookCat = () => { + const isLargeScreen = useIsLargeScreen(); + + return ( + + ); +}; + +export default WhiteNotebookCat; diff --git a/src/components/landing/YellowCat.tsx b/src/components/landing/YellowCat.tsx new file mode 100644 index 0000000..2e54607 --- /dev/null +++ b/src/components/landing/YellowCat.tsx @@ -0,0 +1,64 @@ +'use client'; + +import { motion } from 'framer-motion'; + +import { useIsLargeScreen } from '../../hooks/useLargeScreen'; + +/** + * 노란 고양이 컴포넌트 + * 호기심 많은 고양이가 고개를 갸우뚱하는 애니메이션 + */ +const YellowCat = () => { + const isLargeScreen = useIsLargeScreen(); + + return ( + + ); +}; + +export default YellowCat; diff --git a/src/hooks/useLargeScreen.ts b/src/hooks/useLargeScreen.ts new file mode 100644 index 0000000..b01583f --- /dev/null +++ b/src/hooks/useLargeScreen.ts @@ -0,0 +1,30 @@ +'use client'; + +import { useEffect, useState } from 'react'; + +/** + * 화면이 1920px 이상인지 감지하는 커스텀 훅 + * @returns {boolean} 화면이 1920px 이상이면 true, 아니면 false + */ +export const useIsLargeScreen = () => { + // 초기값은 false로 설정 (SSR 고려) + const [isLargeScreen, setIsLargeScreen] = useState(false); + + useEffect(() => { + // 화면 크기 확인 함수 + const checkScreenSize = () => { + setIsLargeScreen(window.innerWidth >= 1920); + }; + + // 초기 실행 + checkScreenSize(); + + // 화면 크기 변경 이벤트 리스너 + window.addEventListener('resize', checkScreenSize); + + // 클린업 + return () => window.removeEventListener('resize', checkScreenSize); + }, []); + + return isLargeScreen; +};