[feature] 모아동 소개 페이지 HTML 전환 + 애니메이션/반응형 적용 (framer-motion, useDevice 훅 도입)#752
Conversation
애니메이션 구현을 위해 framer-motion 의존성을 추가합니다. 향후 컴포넌트 등장, 인터랙션 등 다양한 동적 UI를 구현하는 데 사용될 예정입니다.
소개 페이지의 배경 및 시각적 장식에 사용될 SVG 파일을 추가합니다. 추가된 파일: - background-circle-large.svg - background-circle-small.svg - background-twist-left.svg - background-twist-right.svg
framer-motion을 기반으로 페이지 배경을 꾸미는 Twist 및 Circle 애니메이션 컴포넌트 4종을 추가합니다.
styled-components를 사용하여 소개 페이지의 전체적인 레이아웃과 각 섹션(인트로, 페인포인트, 슬로건 등)에 대한 기본 스타일링을 추가합니다. 이는 초기 버전이며, 추후 반응형 및 디테일한 스타일이 적용될 예정입니다.
소개 페이지의 최상단 히어로 섹션을 구현합니다.
- 히어로 영역에 Phone Mockup & Floating ClubCard 배치 - 문제 제기(ProblemSection) 및 말풍선 UI 추가 - 질문(QuestionSection) 및 캐치프레이즈 섹션 구현 - FeatureSection에 태그 스크롤 애니메이션 적용 - ConvenienceSection 이미지 그리드 추가 (데스크탑/모바일 구분) - ContactSection 배경 도형 및 CTA 버튼 구현 - 공통 애니메이션 variants 정리 (fade, fadeUp, fadeIn, stagger, scrollVariants)
- framer-motion 애니메이션 variants를 constants/animations.ts로 분리 - fadeUp, fade, fadeIn, stagger, scrollVariants 상수화 - VIEWPORT_CONFIG 추가로 viewport 설정 통일
- 동아리 목업 데이터를 constants/mockData.ts로 분리 - floatingClubs, cardPositions, tagsRow1, tagsRow2 상수화
- IntroducePage의 인트로 섹션을 독립 컴포넌트로 분리 - IntroSection.tsx, IntroSection.styles.ts 생성 - 절대 경로 import 적용
- IntroducePage의 문제점 제기 섹션을 독립 컴포넌트로 분리 - ProblemSection.tsx, ProblemSection.styles.ts 생성
- IntroducePage의 질문 섹션을 독립 컴포넌트로 분리 - QuestionSection.tsx, QuestionSection.styles.ts 생성
- IntroducePage의 캐치프레이즈 섹션을 독립 컴포넌트로 분리 - CatchphraseSection.tsx, CatchphraseSection.styles.ts 생성
- IntroducePage의 기능 소개 섹션을 독립 컴포넌트로 분리 - FeatureSection.tsx, FeatureSection.styles.ts 생성
- IntroducePage의 편의 기능 섹션을 독립 컴포넌트로 분리 - ConvenienceSection.tsx, ConvenienceSection.styles.ts 생성
- IntroducePage의 연락처 섹션을 독립 컴포넌트로 분리 - ContactSection.tsx, ContactSection.styles.ts 생성
- 각 섹션을 독립 컴포넌트로 import하여 사용 - 기존 스타일 정리 및 불필요한 코드 제거 - 컴포넌트 구조 개선으로 가독성 향상
- recruitmentStatus를 OPEN/UPCOMING/CLOSED 형태로 통일 - division을 '중동'으로 통합 - 불필요한 cardPositions 데이터 제거
- IntroSection: 카드 배치 구조 조정 및 반응형 위치 스타일 추가 - ProblemSection: 텍스트 크기 및 레이아웃 개선 - QuestionSection: 반응형 여백 및 정렬 최적화 - CatchphraseSection: 폰트 사이즈 및 강조 효과 개선 - FeatureSection: 그리드 레이아웃 반응형 대응 추가 - ConvenienceSection: 이미지 크기 및 비율 스타일 수정 - ContactSection: 중앙 정렬 및 배경 색상 보정
|
Warning
|
| Cohort / File(s) | Summary |
|---|---|
Dependenciesfrontend/package.json |
런타임 의존성 framer-motion: ^12.23.12 추가. |
IntroducePage — 진입점/레이아웃frontend/src/pages/IntroducePage/IntroducePage.tsx, frontend/src/pages/IntroducePage/IntroducePage.styles.ts, frontend/src/pages/IntroducePage/IntroducePage.test.tsx |
기존 이미지/로딩(Spinner) 로직 제거, 섹션 기반 컴포넌트로 재구성(semantic header/main/footer 적용), 테스트 파일(IntroducePage.test.tsx) 삭제. |
섹션 컴포넌트·스타일 (신규)frontend/src/pages/IntroducePage/components/sections/... |
Intro, Problem, Question, Catchphrase, Feature, Convenience, Contact 각 섹션의 .styles.ts 및 .tsx 파일 대량 추가(Framer Motion 애니메이션·반응형 포함). |
배경 도형 컴포넌트frontend/src/pages/IntroducePage/components/BackgroundShapes.tsx |
SVG 기반 Framer Motion 배경 컴포넌트 4종 추가(BackgroundTwistLeft/Right, BackgroundCircleSmall/Large). |
애니메이션·목데이터 상수frontend/src/pages/IntroducePage/constants/animations.ts, frontend/src/pages/IntroducePage/constants/mockData.ts |
애니메이션 variants·transition·VIEWPORT_CONFIG 및 mock 데이터(floatingClubs, tagsRow1, tagsRow2) 추가. |
피처 이미지 인덱스frontend/src/assets/images/introduce/features/index.ts |
데스크톱/모바일 피처 이미지 배열 desktopFeatures, mobileFeatures 추가·내보냄. |
디바이스 훅 및 미디어쿼리frontend/src/hooks/useDevice.ts, frontend/src/styles/mediaQuery.ts |
useDevice 훅 추가(윈도우 리사이즈 구독, isMobile/isTablet/isLaptop/isDesktop 반환). mediaQuery는 포맷 미세 조정. |
Convenience / Feature 구현frontend/src/pages/IntroducePage/components/sections/5.FeatureSection/..., frontend/src/pages/IntroducePage/components/sections/6.ConvenienceSection/... |
태그 자동 스크롤·검색 UI, 피처 카드 그리드 등 UI 컴포넌트 추가 및 반응형 렌더링(데스크톱/모바일 분기). |
UI 컴포넌트 조정frontend/src/components/ClubTag/ClubTag.tsx, frontend/src/components/common/Footer/Footer.styles.ts |
ClubTag에 className?: string prop 추가 및 전달; FooterContainer의 margin-top 제거 및 CopyRightText export 삭제. |
Sequence Diagram(s)
sequenceDiagram
autonumber
actor User
participant Page as IntroducePage
participant Sections as Sections (Intro..Contact)
participant Motion as Framer Motion
participant Hook as useDevice
User->>Page: 페이지 진입
Page->>Sections: 섹션 컴포넌트 렌더
Sections->>Motion: motion 초기 상태 적용 (hidden)
Motion-->>Sections: in-view 감지 → show 애니메이션
Sections->>Hook: (ConvenienceSection) 디바이스 플래그 요청
Hook-->>Sections: {isMobile,isTablet,isLaptop,isDesktop}
Sections-->>User: 반응형 애니메이션 UI 렌더
sequenceDiagram
participant Window
participant Hook as useDevice
participant Comp as ConvenienceSection
Hook->>Window: window resize 이벤트 구독
Window-->>Hook: width 변경 통지
Hook-->>Comp: 디바이스 플래그 갱신
Comp->>Comp: 데스크톱/모바일 피처 이미지 선택 및 렌더
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related issues
- [feature] MOA-197 소개 페이지 정적 HTML 전환 및 애니메이션 적용 #697 — 소개 페이지를 시맨틱 HTML로 전환하고 Framer Motion 애니메이션을 적용하려는 목표와 직접 일치함.
Possibly related PRs
- [feature] 소개페이지 컴포넌트 테스트 코드 작성 #448 — 동일 테스트 파일(
IntroducePage.test.tsx) 변경(본 PR에서 해당 파일 삭제)으로 직접 연관. - [release] FE 1차 배포 #149 —
ClubTag에 prop 추가/전달 관련 변경으로 코드 레벨 연관. - [release] v1.0.2 #388 — IntroducePage 영역의 섹션화·애니메이션 도입 관련 변경과 기능적 연속성 존재.
Suggested reviewers
- lepitaaar
- Zepelown
- seongwon030
Pre-merge checks and finishing touches
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Out of Scope Changes Check | 이 PR에는 MOA-197의 시맨틱 HTML 전환 및 Framer Motion 애니메이션 적용 범위를 벗어난 변경 사항이 포함되어 있습니다. IntroducePage 테스트 파일 삭제, Footer 스타일 조정 및 CopyRightText 제거, useDevice 훅 도입, mockData.ts 추가, ClubTag에 className 지원 등이 이슈의 주요 목표와 직접 관련이 없습니다. | 시맨틱 HTML 전환과 애니메이션 적용에 집중하도록 관련 없는 테스트 삭제 및 스타일·훅 추가를 분리하여 별도 PR로 제출해주세요. |
✅ Passed checks (4 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title Check | ✅ Passed | 제목은 소개 페이지의 HTML 전환, framer-motion 애니메이션 적용 및 반응형 지원을 위한 useDevice 훅 도입이라는 주요 변경 사항을 명확하게 요약하고 있어 PR의 핵심을 잘 드러냅니다. |
| Linked Issues Check | ✅ Passed | 이 PR은 <header>, <main>, <section> 등의 시맨틱 HTML 구조로 변환되었고 각 섹션에 Framer Motion 모션 래퍼와 애니메이션 variants를 적용하여 MOA-197의 요구 사항을 충족합니다. |
| Docstring Coverage | ✅ Passed | No functions found in the changes. Docstring coverage check skipped. |
✨ Finishing touches
- 📝 Generate Docstrings
🧪 Generate unit tests
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
feature/#697-intro-html-animation-MOA-197
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (42)
frontend/src/components/ClubTag/ClubTag.tsx (3)
29-29: 불필요한 백틱 잔여물 제거 필요
``;는 의미 없는 표현식으로 남아 있고 린트/가독성에 악영향을 줍니다. 삭제해 주세요.-``; +
3-12: 타입 안정성 강화 + 기본 색상 상수화
- TagColors를
as const로 고정하고,type을 유효 키의 유니온으로 제한하면 오타를 빌드 타임에 잡을 수 있습니다.- 기본 색상 리터럴을 상수로 분리해 “매직 넘버”를 제거하세요.
-const TagColors: Record<string, string> = { +const DEFAULT_TAG_COLOR = 'rgba(237, 237, 237, 1)'; +const TagColors = { 중동: 'rgba(230, 247, 255, 1)', 봉사: 'rgba(255, 187, 187, 0.5)', 종교: 'rgba(255, 230, 147, 0.5)', 취미교양: 'rgba(159, 220, 214, 0.48)', 학술: 'rgba(177, 189, 241, 0.5)', 운동: 'rgba(253, 173, 60, 0.4)', 공연: 'rgba(205, 241, 165, 0.5)', 자유: 'rgba(237, 237, 237, 0.8)', -}; +} as const; -interface TagProps { - type: string; +type TagType = keyof typeof TagColors; +interface TagProps { + type: TagType; children?: React.ReactNode; className?: string; } @@ - const backgroundColor = TagColors[type] || 'rgba(237, 237, 237, 1)'; + const backgroundColor = TagColors[type] ?? DEFAULT_TAG_COLOR;Also applies to: 14-18, 32-32
3-12: 테마 색상 사용 고려디자인 시스템이 있다면 rgba 대신 theme 토큰(예:
({theme}) => theme.colors.tag[타입])을 사용해 일관성을 유지하세요.frontend/src/pages/IntroducePage/components/BackgroundShapes.tsx (3)
3-18: 장식용 SVG의 스크린리더 노이즈 차단모든 도형은 순수 장식입니다.
aria-hidden,focusable="false",pointer-events: none을 적용해 접근성을 보완하세요.-import { motion } from 'framer-motion'; +import { motion } from 'framer-motion'; + +const DECORATIVE_SVG_PROPS = { + 'aria-hidden': true as const, + focusable: false, + style: { pointerEvents: 'none' } as const, +};각 컴포넌트의
<motion.svg>에 전개:- <motion.svg + <motion.svg + {...DECORATIVE_SVG_PROPS} width='496' height='439'(다른 세 컴포넌트에도 동일 적용)
Also applies to: 20-36, 38-50, 52-65
4-11: 애니메이션 트랜지션 상수화로 “매직 넘버” 제거
duration: 1,ease: 'easeInOut'/'easeOut'가 반복됩니다. 파일 상단에 상수로 추출해 재사용하세요.-const DECORATIVE_SVG_PROPS = { +const DECORATIVE_SVG_PROPS = { 'aria-hidden': true as const, focusable: false, style: { pointerEvents: 'none' } as const, }; +const TRANS_FADE_IN_OUT = { duration: 1, ease: 'easeInOut' } as const; +const TRANS_FADE_OUT = { duration: 1, ease: 'easeOut' } as const;- transition={{ duration: 1, ease: 'easeInOut' }} + transition={TRANS_FADE_IN_OUT}- transition={{ duration: 1, ease: 'easeOut' }} + transition={TRANS_FADE_OUT}Also applies to: 22-29, 44-47, 59-60
3-18: 재사용성 향상: SVG props 수용크기/클래스 오버라이드가 필요할 수 있습니다.
props를 받아motion.svg에 전개하면 유연성이 커집니다.-export const BackgroundTwistLeft = () => ( - <motion.svg +export const BackgroundTwistLeft = (props: React.ComponentProps<typeof motion.svg>) => ( + <motion.svg + {...props} {...DECORATIVE_SVG_PROPS}(다른 컴포넌트에도 동일 패턴 권장)
frontend/src/pages/IntroducePage/components/sections/3.QuestionSection/QuestionSection.tsx (1)
12-14: 백그라운드 물음표를 스크린리더에서 숨기기장식용 문자는 보조기기에 잡음이 됩니다.
aria-hidden을 추가하세요.- <Styled.BackgroundQuestionMark variants={fade}> + <Styled.BackgroundQuestionMark variants={fade} aria-hidden> ? </Styled.BackgroundQuestionMark>frontend/src/pages/IntroducePage/components/sections/4.CatchphraseSection/CatchphraseSection.tsx (1)
13-17: 장식용 배경 이미지의 대체 텍스트 비우기 + 지연 로딩브랜드 배경은 정보 전달이 아니므로
alt=""로 스크린리더 제외하고, 성능을 위해 지연 로딩/비동기 디코딩을 권장합니다.- <Styled.BackgroundBrandImage - src={moadong_logo_bg} - alt='모아동 로고 배경' - variants={fade} - /> + <Styled.BackgroundBrandImage + src={moadong_logo_bg} + alt="" + loading="lazy" + decoding="async" + aria-hidden + variants={fade} + />frontend/src/hooks/useDevice.ts (2)
5-11: SSR/초기 렌더 안전성 보강직접적인
window참조는 SSR/프리렌더 환경에서 크래시/수화 불일치를 유발할 수 있습니다. 안전 가드를 추가하세요.- const [width, setWidth] = useState(window.innerWidth); + const getWindowWidth = () => + typeof window === 'undefined' ? BREAKPOINT.laptop + 1 : window.innerWidth; + const [width, setWidth] = useState<number>(getWindowWidth()); @@ - useEffect(() => { - const onResize = () => setWidth(window.innerWidth); + useEffect(() => { + if (typeof window === 'undefined') return; + const onResize = () => setWidth(window.innerWidth); window.addEventListener('resize', onResize); return () => window.removeEventListener('resize', onResize); }, []);
7-11: 빈번한 리사이즈 이벤트에 대한 부하 완화전체 앱에서 재사용된다면 rAF/throttle로 리렌더를 줄이는 것이 좋습니다.
- const onResize = () => setWidth(window.innerWidth); + let ticking = false; + const onResize = () => { + if (ticking) return; + ticking = true; + requestAnimationFrame(() => { + setWidth(window.innerWidth); + ticking = false; + }); + };frontend/src/styles/mediaQuery.ts (1)
7-12: desktop 영역 명시 상수 추가 검토필요 시
desktopMin = BREAKPOINT.laptop + 1같은 상수를 노출하면 훅/로직에서 재사용하기 수월합니다.frontend/src/pages/IntroducePage/components/sections/7.ContactSection/ContactSection.tsx (2)
24-35: 도형 위치 “매직 넘버” 상수화위치 값이 하드코딩되어 있습니다. 가독성과 유지보수를 위해 상수로 추출하세요.
+const SHAPE_POS = { + leftTwist: { top: '-40px', left: '20px' }, + rightTwist: { top: '163px', right: '-10px' }, + smallCircle: { top: '575px', left: '411px' }, + largeCircle: { top: '380px', right: '477px' }, +} as const; @@ - <Styled.Shape top='-40px' left='20px'> + <Styled.Shape {...SHAPE_POS.leftTwist}> <BackgroundTwistLeft /> </Styled.Shape> - <Styled.Shape top='163px' right='-10px'> + <Styled.Shape {...SHAPE_POS.rightTwist}> <BackgroundTwistRight /> </Styled.Shape> - <Styled.Shape top='575px' left='411px'> + <Styled.Shape {...SHAPE_POS.smallCircle}> <BackgroundCircleSmall /> </Styled.Shape> - <Styled.Shape top='380px' right='477px'> + <Styled.Shape {...SHAPE_POS.largeCircle}> <BackgroundCircleLarge /> </Styled.Shape>
9-9: 상수 위치/관리 개선(선택)
kakaoLink를 환경변수 혹은 상수 모듈로 이동하면 환경별(스테이징/프로덕션) 전환이 쉬워집니다.frontend/src/pages/IntroducePage/components/sections/4.CatchphraseSection/CatchphraseSection.styles.ts (3)
32-43: 배경 브랜드 이미지에 상호작용 차단 필요배경용 이미지가 포커스/클릭을 가로채지 않도록 pointer-events를 명시해 주세요.
적용 diff:
export const BackgroundBrandImage = styled(motion.img)` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 0; width: 100%; max-width: 1400px; opacity: 0.05; user-select: none; + pointer-events: none; `;
5-15: 매직 넘버 정리: 간격/색상 토큰화여러 구간에서 픽셀/색상 매직 넘버가 반복됩니다. 가독성과 일관성을 위해 파일 상단에 상수로 선언해 재사용해 주세요(가이드라인: magic number 대체).
예시 diff(요지):
import styled from 'styled-components'; import { motion } from 'framer-motion'; import { media } from '@/styles/mediaQuery'; +const SECTION_PADDING = { desktop: '120px', laptop: '100px', tablet: '80px', mobile: '60px' } as const; +const SECTION_MARGIN_TOP = { desktop: '114px', laptop: '80px', tablet: '60px', mobile: '40px' } as const; +const BRAND_ORANGE_BG = 'rgba(255, 84, 20, 0.08)'; +const BRAND_ORANGE = '#ff5414'; export const CatchphraseSection = styled(motion.section)` width: 100%; - padding: 120px 0; - background-color: rgba(255, 84, 20, 0.08); + padding: ${SECTION_PADDING.desktop} 0; + background-color: ${BRAND_ORANGE_BG}; @@ - margin-top: 114px; + margin-top: ${SECTION_MARGIN_TOP.desktop}; ${media.laptop} { - padding: 100px 0; - margin-top: 80px; + padding: ${SECTION_PADDING.laptop} 0; + margin-top: ${SECTION_MARGIN_TOP.laptop}; } @@ ${media.tablet} { - padding: 80px 0; - margin-top: 60px; + padding: ${SECTION_PADDING.tablet} 0; + margin-top: ${SECTION_MARGIN_TOP.tablet}; } @@ ${media.mobile} { - padding: 60px 0; - margin-top: 40px; + padding: ${SECTION_PADDING.mobile} 0; + margin-top: ${SECTION_MARGIN_TOP.mobile}; } `; @@ export const CatchphraseSubtitle = styled(motion.p)` - font-size: 30px; + font-size: 30px; color: #121212; font-weight: bold; margin-bottom: 2px; @@ export const CatchphraseTitle = styled(motion.h2)` font-size: 48px; font-weight: bold; - color: #ff5414; + color: ${BRAND_ORANGE};Also applies to: 16-29, 53-70, 72-88
53-70: 폰트 리사이징에 clamp() 고려여러 브레이크포인트에서 단계적으로 폰트 크기를 조정하고 있습니다. CSS clamp()로 유연 리사이징을 적용하면 코드가 단순해지고 전환이 부드러워집니다.
Also applies to: 72-88
frontend/src/assets/images/introduce/features/index.ts (1)
13-18: 타입 명시로 소비측 안전성 강화소비측에서 인덱스 접근/구조분해 시 안정성을 높이기 위해 명시 타입을 부여해 주세요.
적용 diff:
+export type FeatureAsset = { src: string; alt: string }; - -export const desktopFeatures = [ +export const desktopFeatures: FeatureAsset[] = [ { src: feature_category_mockup_desktop, alt: '분과별 카테고리' }, { src: feature_recruitment_mockup_desktop, alt: '모집 상태 확인' }, { src: feature_info_mockup_desktop, alt: '지원/정보 확인' }, { src: feature_introduction_mockup_desktop, alt: '동아리 소개' }, ]; -export const mobileFeatures = [ +export const mobileFeatures: FeatureAsset[] = [ { src: feature_category_mockup_mobile, alt: '분과별 카테고리' }, { src: feature_recruitment_mockup_mobile, alt: '모집 상태 확인' }, { src: feature_info_mockup_mobile, alt: '지원/정보 확인' }, { src: feature_introduction_mockup_mobile, alt: '동아리 소개' }, ];Also applies to: 20-25
frontend/src/pages/IntroducePage/components/sections/6.ConvenienceSection/ConvenienceSection.tsx (2)
23-31: 이미지 지연 로딩으로 페인트 비용 절감접근 시점에 따라 보이는 섹션이므로 lazy/async 디코딩을 권장합니다.
적용 diff:
<Styled.Card1 src={features[0].src} alt={features[0].alt} + loading='lazy' + decoding='async' variants={cardVariants.top} @@ <Styled.Card2 src={features[1].src} alt={features[1].alt} + loading='lazy' + decoding='async' variants={cardVariants.left} @@ <Styled.Card3 src={features[2].src} alt={features[2].alt} + loading='lazy' + decoding='async' variants={cardVariants.right} @@ <Styled.Card4 src={features[3].src} alt={features[3].alt} + loading='lazy' + decoding='async' variants={cardVariants.bottom}Also applies to: 31-38, 39-46, 47-54
22-55: features 길이 가정에 대한 안전장치 확인 요청정확히 4개라는 전제에 의존합니다. 데이터 변경 시 즉시 런타임 에러가 납니다. 최소한 개발 모드 경고 또는 길이 검증을 고려해 주세요.
frontend/src/pages/IntroducePage/components/sections/2.ProblemSection/ProblemSection.tsx (2)
6-11: 섹션 접근성 향상: aria-labelledby 연결섹션 제목과의 연계를 위해 aria-labelledby와 id를 추가해 주세요.
적용 diff:
- <Styled.ProblemSection + <Styled.ProblemSection variants={stagger} - initial='hidden' - whileInView='show' + initial='hidden' + whileInView='show' viewport={VIEWPORT_CONFIG} + aria-labelledby='problem-title' > - <Styled.ProblemSectionTitle variants={fadeUp}> + <Styled.ProblemSectionTitle variants={fadeUp} id='problem-title'> 동아리 찾기 너무 불편하지 않으셨나요 </Styled.ProblemSectionTitle>Also applies to: 12-15
16-39: 하드코드 문구 분리 제안(추후 i18n 대비)버블 문구를 constants/mockData로 분리하면 복수 언어/카피 A/B 테스트 시 유지보수가 쉬워집니다.
frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.tsx (3)
1-9: reduced-motion 지원사용자 선호도(prefers-reduced-motion)를 존중하도록 애니메이션을 비활성화하세요. 초기 상태 숨김을 피하기 위해 initial도 함께 제어합니다.
적용 diff:
import { useNavigate } from 'react-router-dom'; import * as Styled from './IntroSection.styles'; +import { useReducedMotion } from 'framer-motion'; @@ const IntroSection = () => { const navigate = useNavigate(); + const prefersReducedMotion = useReducedMotion(); @@ <Styled.IntroSection variants={stagger} - initial='hidden' - whileInView='show' + initial={prefersReducedMotion ? false : 'hidden'} + whileInView={prefersReducedMotion ? undefined : 'show'} viewport={VIEWPORT_CONFIG} aria-labelledby='intro-title' >Also applies to: 67-76
92-99: 네비게이션 요소의 시맨틱 확인단순 라우팅이라면 버튼 대신 Link(또는 a) 사용이 더 시맨틱합니다. 버튼 유지 시 role/aria를 검토해 주세요.
21-26: 배치 좌표 매직 넘버 정리cardPositions/SHAPES의 픽셀 좌표가 다수 하드코딩되어 있습니다. 상수/토큰화 또는 clamp()/container query로의 전환을 검토해 주세요(가이드라인: magic number 대체).
Also applies to: 28-65, 108-117
frontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.tsx (3)
31-34: 아이콘 전용 버튼 접근성/기본 타입 설정아이콘만 있는 버튼에 aria-label을 추가하고, 의도치 않은 submit을 막기 위해 type='button'을 지정해 주세요.
적용 diff:
- <Styled.SearchButton> - <img src={search_button_icon} alt='검색 아이콘' /> + <Styled.SearchButton type='button' aria-label='검색'> + <img src={search_button_icon} alt='검색 아이콘' /> </Styled.SearchButton>
39-44: DOM 전달 프롭 확인(CustomTag의 type)CustomTag에 전달되는 type 프롭이 실제 DOM 요소로 전달되면 경고가 발생할 수 있습니다. transient prop(예: $tagType)으로 변경하거나 shouldForwardProp 설정을 확인해 주세요.
Also applies to: 48-55
36-56: 무한 스크롤 애니메이션의 reduced-motion 대응scrollVariants의 무한 애니메이션은 멀미를 유발할 수 있습니다. reduced-motion 시 애니메이션 중단(또는 duration 0) 처리를 권장합니다.
frontend/src/pages/IntroducePage/constants/mockData.ts (1)
47-55: 리터럴 유지 및 오타 방지용 as const태그 라벨/타입은 변경 가능성이 낮아 보입니다. as const로 고정하면 오타 검출에 유리합니다(소비처 타입 충돌 시에는 보류).
적용 diff:
-export const tagsRow1 = [ +export const tagsRow1 = [ { type: '운동', label: '운동' }, { type: '취미교양', label: '취미교양' }, { type: '공연', label: '공연' }, { type: '자유', label: '자유' }, { type: '봉사', label: '봉사' }, { type: '학술', label: '학술' }, { type: '종교', label: '종교' }, -]; +] as const; -export const tagsRow2 = [ +export const tagsRow2 = [ { type: '자유', label: '친목' }, { type: '자유', label: '스터디' }, { type: '자유', label: '공모전' }, { type: '자유', label: '봉사' }, { type: '자유', label: '음악' }, { type: '자유', label: '여행' }, { type: '자유', label: '창업' }, { type: '자유', label: '자격증' }, { type: '자유', label: '언어' }, -]; +] as const;Also applies to: 57-67
frontend/src/pages/IntroducePage/IntroducePage.styles.ts (1)
3-16: 헤더/푸터 색상·보더 매직 넘버 상수화 + Main의 정렬 재검토
#fff,#eee는 상수/토큰으로 치환해 가독성과 일관성을 높여주세요.Main에justify-content: center가 있으면 섹션들이 수직 중앙정렬되어 의도치 않은 여백/스크롤 거동이 생길 수 있습니다. 페이지 컨테이너라면 제거를 권장합니다.적용 예시(해당 범위 diff):
export const IntroducePageHeader = styled.header` width: 100%; - background: #fff; + background: ${COLOR_SURFACE}; `; export const IntroducePageFooter = styled.footer` - background: #fff; - border-top: 1px solid #eee; + background: ${COLOR_SURFACE}; + border-top: 1px solid ${COLOR_DIVIDER}; `; export const Main = styled.main` - background: #fff; + background: ${COLOR_SURFACE}; display: flex; flex-direction: column; align-items: center; - justify-content: center; `;상단에 상수 추가(파일 상단에 배치):
const COLOR_SURFACE = '#fff'; const COLOR_DIVIDER = '#eee';frontend/src/pages/IntroducePage/components/sections/3.QuestionSection/QuestionSection.styles.ts (2)
50-77: 배경 장식 요소가 인터랙션을 가로채지 않도록 보호큰 물음표가 섹션을 덮는 절대 배치이며
z-index: 0이지만 스택 컨텍스트에 따라 클릭을 가로챌 수 있습니다.pointer-events: none추가를 권장합니다. TSX에서는aria-hidden/role="presentation"도 함께 지정해주세요.export const BackgroundQuestionMark = styled(motion.div)` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 0; + pointer-events: none; font-size: 400px; font-weight: 400; color: rgba(255, 84, 20, 0.2); filter: blur(19px); user-select: none; line-height: 619px;
5-27: 섹션 패딩·폰트 크기 매직 넘버 정리
150/120/100/80px,30/26/22/18px등 하드코딩 값이 반복됩니다. 공용 spacing/typography 스케일 또는 파일 상단 상수로 통일해 주세요(예:SECTION_PADDING = { desktop: 150, laptop: 120, ... }).Also applies to: 29-48
frontend/src/pages/IntroducePage/components/sections/2.ProblemSection/ProblemSection.styles.ts (2)
5-24: 반응형 폭 정의 단순화(중복 미디어쿼리 제거)고정 폭을 브레이크포인트마다 바꾸기보다
min()과 하단 마진만 단계적으로 조절하면 동일 표현력으로 코드가 짧아집니다. 모바일 패딩만 유지하세요.export const ProblemSection = styled(motion.section)` - width: 700px; - margin: 0 auto 150px; - - ${media.laptop} { - width: 600px; - margin: 0 auto 120px; - } - - ${media.tablet} { - width: 500px; - margin: 0 auto 100px; - } - - ${media.mobile} { - width: 90vw; - margin: 0 auto 80px; - padding: 0 20px; - } + width: min(700px, 90vw); + margin: 0 auto 150px; + + ${media.laptop} { margin-bottom: 120px; } + ${media.tablet} { margin-bottom: 100px; } + ${media.mobile} { + margin-bottom: 80px; + padding: 0 20px; + } `;
74-100: 버블 타이포·색상 토큰화
rgba(255, 84, 20, 0.08),20/18/16/14px등은 공용 토큰(또는 파일 상단 상수)로 치환하면 유지보수성이 좋아집니다.frontend/src/pages/IntroducePage/components/sections/6.ConvenienceSection/ConvenienceSection.styles.ts (2)
42-46: 이미지 래퍼의 기본 규격 보장
motion.img는 뷰포트·그리드 전환 시 레이아웃 점프가 생기기 쉬우니 기본 규격을 명시하세요. 또한 TSX에서loading="lazy" decoding="async"지정 권장.export const FeatureCard = styled(motion.img)` + display: block; + width: 100%; + height: auto; + object-fit: contain; ${media.laptop} { - width: 100%; + /* 상단 기본값으로 커버되지만 유지 */ + width: 100%; } `;
5-22: 컨테이너 폭·여백 매직 넘버 상수화
1180/700/500,150/100/40px등은 공용 레이아웃 스케일 또는 상수로 관리해 주세요(예:MAX_CONTENT_LG = 1180).frontend/src/pages/IntroducePage/components/sections/7.ContactSection/ContactSection.styles.ts (1)
91-103: Shape 컴포넌트 중복 정의 해소IntroSection에도 동일한
Shape가 있습니다. 공용 유틸(예:Background/Shape.ts)로 분리하거나 하나를 import하여 중복을 제거하세요. 또한 장식 용도면pointer-events: none권장.export const Shape = styled.div<{ @@ }>` position: absolute; z-index: 0; + pointer-events: none;frontend/src/pages/IntroducePage/constants/animations.ts (2)
15-18: 타입 명시 누락(fadeIn) — Variants로 명시다른 프리셋과 일관되게
Variants타입을 지정해주세요.-export const fadeIn = { +export const fadeIn: Variants = { hidden: { opacity: 0 }, show: { opacity: 1, transition: { duration: 0.5 } }, };
20-23: 매직 넘버 상수화 및 키 타입 안정성 강화
staggerChildren=0.08,delayChildren=0.1등 수치 상수는 상수로 추출.cardVariants는Record<string, Variants>대신 키 유니온 타입으로 엄격화하세요.+export const STAGGER_CHILDREN = 0.08; +export const DELAY_CHILDREN = 0.1; export const stagger: Variants = { hidden: {}, - show: { transition: { staggerChildren: 0.08, delayChildren: 0.1 } }, + show: { transition: { staggerChildren: STAGGER_CHILDREN, delayChildren: DELAY_CHILDREN } }, }; @@ -export const cardVariants: Record<string, Variants> = { +export type CardVariant = 'left' | 'right' | 'top' | 'bottom'; +export const cardVariants: Record<CardVariant, Variants> = {Also applies to: 36-53
frontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.styles.ts (2)
102-122: 검색 버튼 접근성: 키보드 포커스/아이콘 색 제어
:focus-visible스타일이 없어 키보드 사용성이 떨어집니다.- 아이콘이
<img>라 색상 제어가 어렵습니다. 인라인 SVG(혹은mask/currentColor)로 전환을 권장합니다.export const SearchButton = styled.button` background: none; border: none; cursor: pointer; margin-left: 8px; + &:focus-visible { + outline: 3px solid #111; + outline-offset: 3px; + } img {
191-238: TagWindow 그라데이션 배경 색상 하드코딩 제거페이지가 다크테마/비백색 배경으로 확장될 경우 대비가 무너질 수 있습니다.
#ffffff대신 테마 토큰(또는 상수) 사용을 권장합니다.frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.styles.ts (2)
49-88: 장식 Shape 공용화 + 포인터 이벤트 비활성화Contact와 동일한 Shape 정의가 중복됩니다. 공용 컴포넌트로 추출하거나 하나만 사용하세요. 장식물은
pointer-events: none추가를 권장합니다.export const Shape = styled.div<{ @@ }>` position: absolute; z-index: 0; + pointer-events: none;
197-203: Visual 영역 최소 높이의 기기별 조정
min-height: 600px은 소형 화면에서 과도한 공백을 유발할 수 있습니다. 브레이크포인트별로 낮춰 주세요.export const VisualWrapper = styled(motion.div)` @@ - min-height: 600px; + min-height: 600px; + ${media.laptop} { min-height: 520px; } + ${media.tablet} { min-height: 440px; } + ${media.mobile} { min-height: 360px; } `;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (15)
frontend/package-lock.jsonis excluded by!**/package-lock.jsonfrontend/src/assets/images/introduce/background-circle-large.svgis excluded by!**/*.svgfrontend/src/assets/images/introduce/background-circle-small.svgis excluded by!**/*.svgfrontend/src/assets/images/introduce/background-twist-left.svgis excluded by!**/*.svgfrontend/src/assets/images/introduce/background-twist-right.svgis excluded by!**/*.svgfrontend/src/assets/images/introduce/features/desktop/feature_category_mockup.pngis excluded by!**/*.pngfrontend/src/assets/images/introduce/features/desktop/feature_info_mockup.pngis excluded by!**/*.pngfrontend/src/assets/images/introduce/features/desktop/feature_introduction_mockup.pngis excluded by!**/*.pngfrontend/src/assets/images/introduce/features/desktop/feature_recruitment_mockup.pngis excluded by!**/*.pngfrontend/src/assets/images/introduce/features/mobile/feature_category_mockup.pngis excluded by!**/*.pngfrontend/src/assets/images/introduce/features/mobile/feature_info_mockup.pngis excluded by!**/*.pngfrontend/src/assets/images/introduce/features/mobile/feature_introduction_mockup.pngis excluded by!**/*.pngfrontend/src/assets/images/introduce/features/mobile/feature_recruitment_mockup.pngis excluded by!**/*.pngfrontend/src/assets/images/introduce/introduce_phone_mockup.pngis excluded by!**/*.pngfrontend/src/assets/images/logos/moadong_logo_bg.svgis excluded by!**/*.svg
📒 Files selected for processing (26)
frontend/package.json(1 hunks)frontend/src/assets/images/introduce/features/index.ts(1 hunks)frontend/src/components/ClubTag/ClubTag.tsx(2 hunks)frontend/src/components/common/Footer/Footer.styles.ts(0 hunks)frontend/src/hooks/useDevice.ts(1 hunks)frontend/src/pages/IntroducePage/IntroducePage.styles.ts(1 hunks)frontend/src/pages/IntroducePage/IntroducePage.test.tsx(0 hunks)frontend/src/pages/IntroducePage/IntroducePage.tsx(1 hunks)frontend/src/pages/IntroducePage/components/BackgroundShapes.tsx(1 hunks)frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.styles.ts(1 hunks)frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.tsx(1 hunks)frontend/src/pages/IntroducePage/components/sections/2.ProblemSection/ProblemSection.styles.ts(1 hunks)frontend/src/pages/IntroducePage/components/sections/2.ProblemSection/ProblemSection.tsx(1 hunks)frontend/src/pages/IntroducePage/components/sections/3.QuestionSection/QuestionSection.styles.ts(1 hunks)frontend/src/pages/IntroducePage/components/sections/3.QuestionSection/QuestionSection.tsx(1 hunks)frontend/src/pages/IntroducePage/components/sections/4.CatchphraseSection/CatchphraseSection.styles.ts(1 hunks)frontend/src/pages/IntroducePage/components/sections/4.CatchphraseSection/CatchphraseSection.tsx(1 hunks)frontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.styles.ts(1 hunks)frontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.tsx(1 hunks)frontend/src/pages/IntroducePage/components/sections/6.ConvenienceSection/ConvenienceSection.styles.ts(1 hunks)frontend/src/pages/IntroducePage/components/sections/6.ConvenienceSection/ConvenienceSection.tsx(1 hunks)frontend/src/pages/IntroducePage/components/sections/7.ContactSection/ContactSection.styles.ts(1 hunks)frontend/src/pages/IntroducePage/components/sections/7.ContactSection/ContactSection.tsx(1 hunks)frontend/src/pages/IntroducePage/constants/animations.ts(1 hunks)frontend/src/pages/IntroducePage/constants/mockData.ts(1 hunks)frontend/src/styles/mediaQuery.ts(1 hunks)
💤 Files with no reviewable changes (2)
- frontend/src/components/common/Footer/Footer.styles.ts
- frontend/src/pages/IntroducePage/IntroducePage.test.tsx
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.
Files:
frontend/src/pages/IntroducePage/components/sections/6.ConvenienceSection/ConvenienceSection.tsxfrontend/src/assets/images/introduce/features/index.tsfrontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.tsxfrontend/src/hooks/useDevice.tsfrontend/src/pages/IntroducePage/components/sections/3.QuestionSection/QuestionSection.styles.tsfrontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.tsxfrontend/src/pages/IntroducePage/components/sections/2.ProblemSection/ProblemSection.styles.tsfrontend/src/components/ClubTag/ClubTag.tsxfrontend/src/pages/IntroducePage/IntroducePage.styles.tsfrontend/src/pages/IntroducePage/components/BackgroundShapes.tsxfrontend/src/pages/IntroducePage/components/sections/4.CatchphraseSection/CatchphraseSection.styles.tsfrontend/src/pages/IntroducePage/components/sections/4.CatchphraseSection/CatchphraseSection.tsxfrontend/src/pages/IntroducePage/IntroducePage.tsxfrontend/src/pages/IntroducePage/components/sections/6.ConvenienceSection/ConvenienceSection.styles.tsfrontend/src/pages/IntroducePage/constants/mockData.tsfrontend/src/pages/IntroducePage/components/sections/2.ProblemSection/ProblemSection.tsxfrontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.styles.tsfrontend/src/pages/IntroducePage/components/sections/7.ContactSection/ContactSection.tsxfrontend/src/pages/IntroducePage/constants/animations.tsfrontend/src/styles/mediaQuery.tsfrontend/src/pages/IntroducePage/components/sections/7.ContactSection/ContactSection.styles.tsfrontend/src/pages/IntroducePage/components/sections/3.QuestionSection/QuestionSection.tsxfrontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.styles.ts
frontend/**/*.tsx
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.tsx: Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Separate significantly different conditional UI/logic into distinct components.
Colocate simple, localized logic or use inline definitions to reduce context switching.
Choose field-level or form-level cohesion based on form requirements.
Break down broad state management into smaller, focused hooks or contexts.
Use component composition instead of props drilling.
Files:
frontend/src/pages/IntroducePage/components/sections/6.ConvenienceSection/ConvenienceSection.tsxfrontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.tsxfrontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.tsxfrontend/src/components/ClubTag/ClubTag.tsxfrontend/src/pages/IntroducePage/components/BackgroundShapes.tsxfrontend/src/pages/IntroducePage/components/sections/4.CatchphraseSection/CatchphraseSection.tsxfrontend/src/pages/IntroducePage/IntroducePage.tsxfrontend/src/pages/IntroducePage/components/sections/2.ProblemSection/ProblemSection.tsxfrontend/src/pages/IntroducePage/components/sections/7.ContactSection/ContactSection.tsxfrontend/src/pages/IntroducePage/components/sections/3.QuestionSection/QuestionSection.tsx
🧠 Learnings (1)
📚 Learning: 2025-07-19T05:05:10.196Z
Learnt from: seongwon030
PR: Moadong/moadong#548
File: frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx:17-57
Timestamp: 2025-07-19T05:05:10.196Z
Learning: ClubDetailPage.tsx에서 notJoinedClubNames 배열의 하드코딩은 의도적인 설계 결정입니다. 개발자가 명시적으로 하드코딩을 선택했으므로 이에 대한 리팩토링 제안을 하지 않아야 합니다.
Applied to files:
frontend/src/pages/IntroducePage/constants/mockData.ts
🧬 Code graph analysis (17)
frontend/src/pages/IntroducePage/components/sections/6.ConvenienceSection/ConvenienceSection.tsx (3)
frontend/src/pages/IntroducePage/components/sections/6.ConvenienceSection/ConvenienceSection.styles.ts (1)
ConvenienceSection(5-23)frontend/src/assets/images/introduce/features/index.ts (2)
desktopFeatures(13-18)mobileFeatures(20-25)frontend/src/pages/IntroducePage/constants/animations.ts (2)
cardVariants(36-53)VIEWPORT_CONFIG(55-58)
frontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.tsx (3)
frontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.styles.ts (1)
FeatureSection(6-29)frontend/src/pages/IntroducePage/constants/animations.ts (5)
stagger(20-23)VIEWPORT_CONFIG(55-58)fadeUp(5-8)fade(10-13)scrollVariants(25-34)frontend/src/pages/IntroducePage/constants/mockData.ts (2)
tagsRow1(47-55)tagsRow2(57-67)
frontend/src/hooks/useDevice.ts (1)
frontend/src/styles/mediaQuery.ts (1)
BREAKPOINT(1-5)
frontend/src/pages/IntroducePage/components/sections/3.QuestionSection/QuestionSection.styles.ts (1)
frontend/src/styles/mediaQuery.ts (1)
media(7-12)
frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.tsx (4)
frontend/src/pages/IntroducePage/components/BackgroundShapes.tsx (4)
BackgroundTwistLeft(3-18)BackgroundTwistRight(20-36)BackgroundCircleSmall(38-50)BackgroundCircleLarge(52-64)frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.styles.ts (2)
IntroSection(5-28)Shape(49-88)frontend/src/pages/IntroducePage/constants/animations.ts (5)
stagger(20-23)VIEWPORT_CONFIG(55-58)fadeIn(15-18)fade(10-13)fadeUp(5-8)frontend/src/pages/IntroducePage/constants/mockData.ts (1)
floatingClubs(4-45)
frontend/src/pages/IntroducePage/components/sections/2.ProblemSection/ProblemSection.styles.ts (1)
frontend/src/styles/mediaQuery.ts (1)
media(7-12)
frontend/src/pages/IntroducePage/components/sections/4.CatchphraseSection/CatchphraseSection.styles.ts (1)
frontend/src/styles/mediaQuery.ts (1)
media(7-12)
frontend/src/pages/IntroducePage/components/sections/4.CatchphraseSection/CatchphraseSection.tsx (2)
frontend/src/pages/IntroducePage/components/sections/4.CatchphraseSection/CatchphraseSection.styles.ts (1)
CatchphraseSection(5-30)frontend/src/pages/IntroducePage/constants/animations.ts (4)
stagger(20-23)VIEWPORT_CONFIG(55-58)fade(10-13)fadeUp(5-8)
frontend/src/pages/IntroducePage/IntroducePage.tsx (7)
frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.styles.ts (1)
IntroSection(5-28)frontend/src/pages/IntroducePage/components/sections/2.ProblemSection/ProblemSection.styles.ts (1)
ProblemSection(5-24)frontend/src/pages/IntroducePage/components/sections/3.QuestionSection/QuestionSection.styles.ts (1)
QuestionSection(5-27)frontend/src/pages/IntroducePage/components/sections/4.CatchphraseSection/CatchphraseSection.styles.ts (1)
CatchphraseSection(5-30)frontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.styles.ts (1)
FeatureSection(6-29)frontend/src/pages/IntroducePage/components/sections/6.ConvenienceSection/ConvenienceSection.styles.ts (1)
ConvenienceSection(5-23)frontend/src/pages/IntroducePage/components/sections/7.ContactSection/ContactSection.styles.ts (1)
ContactSection(5-33)
frontend/src/pages/IntroducePage/components/sections/6.ConvenienceSection/ConvenienceSection.styles.ts (1)
frontend/src/styles/mediaQuery.ts (1)
media(7-12)
frontend/src/pages/IntroducePage/constants/mockData.ts (1)
frontend/src/types/club.ts (1)
Club(3-12)
frontend/src/pages/IntroducePage/components/sections/2.ProblemSection/ProblemSection.tsx (2)
frontend/src/pages/IntroducePage/components/sections/2.ProblemSection/ProblemSection.styles.ts (1)
ProblemSection(5-24)frontend/src/pages/IntroducePage/constants/animations.ts (3)
stagger(20-23)VIEWPORT_CONFIG(55-58)fadeUp(5-8)
frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.styles.ts (2)
frontend/src/styles/mediaQuery.ts (1)
media(7-12)frontend/src/pages/IntroducePage/components/sections/7.ContactSection/ContactSection.styles.ts (1)
Shape(91-103)
frontend/src/pages/IntroducePage/components/sections/7.ContactSection/ContactSection.tsx (2)
frontend/src/pages/IntroducePage/components/sections/7.ContactSection/ContactSection.styles.ts (1)
ContactSection(5-33)frontend/src/pages/IntroducePage/components/BackgroundShapes.tsx (4)
BackgroundTwistLeft(3-18)BackgroundTwistRight(20-36)BackgroundCircleSmall(38-50)BackgroundCircleLarge(52-64)
frontend/src/pages/IntroducePage/components/sections/7.ContactSection/ContactSection.styles.ts (2)
frontend/src/styles/mediaQuery.ts (1)
media(7-12)frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.styles.ts (1)
Shape(49-88)
frontend/src/pages/IntroducePage/components/sections/3.QuestionSection/QuestionSection.tsx (2)
frontend/src/pages/IntroducePage/components/sections/3.QuestionSection/QuestionSection.styles.ts (1)
QuestionSection(5-27)frontend/src/pages/IntroducePage/constants/animations.ts (4)
stagger(20-23)VIEWPORT_CONFIG(55-58)fade(10-13)fadeUp(5-8)
frontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.styles.ts (1)
frontend/src/styles/mediaQuery.ts (1)
media(7-12)
🔇 Additional comments (11)
frontend/src/components/ClubTag/ClubTag.tsx (1)
31-38: className 포워딩 추가는 적절합니다스타일 오버라이드를 위한 className 전달이 깔끔하게 처리되었습니다. 컴포넌트 공용성 향상에 👍
frontend/src/pages/IntroducePage/components/sections/3.QuestionSection/QuestionSection.tsx (2)
6-11: 섹션 애니메이션 구성은 적절합니다stagger/fade/fadeUp과 VIEWPORT_CONFIG 사용이 일관되고 가독성 좋습니다. 👍
Also applies to: 15-17
15-17: 제목 요소가 h2로 렌더링됩니다 — 확인 완료Styled.QuestionTitle은 frontend/src/pages/IntroducePage/components/sections/3.QuestionSection/QuestionSection.styles.ts에서 styled(motion.h2)로 정의되어 h2로 렌더링됩니다.
frontend/src/pages/IntroducePage/components/sections/4.CatchphraseSection/CatchphraseSection.tsx (1)
7-12: 애니메이션/뷰포트 구성 일관성 좋습니다stagger와 VIEWPORT 설정(amount 0.3)이 의도에 맞고 구성 명확합니다.
Also applies to: 18-25
frontend/src/hooks/useDevice.ts (1)
13-18: 브레이크포인트 로직은 명확합니다CSS
max-width와 동일한 경계 처리(≤/>)가 일치합니다. 👍frontend/src/styles/mediaQuery.ts (1)
2-5: 브레이크포인트 재정의 승인모바일/태블릿/랩톱 경계가 명확하고 주석이 일치합니다. IntroducePage 전역 스타일과의 일관성 유지에 유용합니다.
frontend/src/pages/IntroducePage/components/sections/7.ContactSection/ContactSection.tsx (1)
17-23: 외부 링크 보안 속성 적절
target="_blank"와rel="noopener noreferrer"를 함께 사용한 점 좋습니다.frontend/package.json (1)
29-29: framer-motion v12.23.12 React 19 호환성 및 Webpack 5 설정 확인 요청
- peerDependencies에 react/react-dom "^18.0.0 || ^19.0.0"으로 공식 지원됨 (app.unpkg.com)
- TypeScript 최소버전은 명시되지 않아, TS 4.7+ 환경에서 ESM/exports 관련 과거 이슈 검토 필요 (app.unpkg.com)
- GitHub 이슈에서 React 19 환경의 애니메이션·타입 호환성 문제 다수 보고됨. React 19 프로젝트에서 실제 동작·타입 검증 필요 (github.com)
- 번들 포맷은 main(CJS)과 module(ESM .mjs) 진입점을 모두 제공하는 exports 맵 구조 (app.unpkg.com)
- Webpack 5 설정 시 .mjs 확장자 fullySpecified 이슈 주의. resolve.extensions에 .mjs 포함 또는 resolve.conditionNames 설정 확인, 필요 시 CJS entrypoint 강제 alias 권장 (docs.dynamic.xyz)
frontend/src/pages/IntroducePage/IntroducePage.tsx (2)
18-26: Main의 시맨틱 요소 확인Styled.Main이 실제로
을 렌더링하는지 확인 부탁드립니다. 아니라면 스크린리더/SEO를 위해 main 요소로 교체를 권장합니다.
4-10: 구성 전환 좋습니다스피너/단일 이미지에서 섹션 합성 구조로의 전환이 명확하고 유지보수성이 좋아졌습니다.
Also applies to: 18-26
frontend/src/pages/IntroducePage/constants/mockData.ts (1)
4-45: 목업 데이터 구성 적절타입 준수(floatingClubs: Club[])와 키 안정성(id) 확보가 잘 되어 있습니다.
frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.styles.ts
Show resolved
Hide resolved
...tend/src/pages/IntroducePage/components/sections/6.ConvenienceSection/ConvenienceSection.tsx
Show resolved
Hide resolved
frontend/src/pages/IntroducePage/components/sections/7.ContactSection/ContactSection.styles.ts
Show resolved
Hide resolved
frontend/src/pages/IntroducePage/components/sections/7.ContactSection/ContactSection.styles.ts
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
frontend/src/pages/IntroducePage/IntroducePage.styles.ts (2)
3-6: 시맨틱 header 전환은 적절합니다. 다만 색상 매직 넘버 제거 및 불필요한 width 정리 권장
#fff는 토큰/변수로 치환해 테마·다크모드에 대비하세요.header는 block 요소라 기본이 100% 폭입니다.width: 100%는 제거 가능.-export const IntroducePageHeader = styled.header` - width: 100%; - background: #fff; +export const IntroducePageHeader = styled.header` + /* block 요소 기본 폭이 100%이므로 width 지정 생략 가능 */ + background: var(--color-bg, #fff); `
7-10: footer도 색/보더 매직 넘버를 토큰/변수로 치환하여 일관성 확보다크모드 및 테마 전환을 고려해 CSS 변수(또는 theme 객체) 사용을 권장합니다.
-export const IntroducePageFooter = styled.footer` - background: #fff; - border-top: 1px solid #eee; +export const IntroducePageFooter = styled.footer` + background: var(--color-bg, #fff); + border-top: 1px solid var(--color-border-subtle, #eee); `
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
frontend/src/pages/IntroducePage/IntroducePage.styles.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.
Files:
frontend/src/pages/IntroducePage/IntroducePage.styles.ts
🔇 Additional comments (1)
frontend/src/pages/IntroducePage/IntroducePage.styles.ts (1)
11-18: Main 정렬/오버플로우 재검토·컴포넌트명 충돌 방지 — 사용처 확인 필요File: frontend/src/pages/IntroducePage/IntroducePage.styles.ts (lines 11–18)
justify-content: center는 세로 중앙 정렬로 긴 페이지에서 의도치 않은 레이아웃을 유발함 →justify-content: flex-start권장.overflow-x: hidden은 포커스 링/애니메이션이 잘릴 수 있음 → 지원 시overflow-x: clip검토(미지원 환경은 현행 유지).- export 이름
Main은 범용적이라 충돌/혼동 가능 →IntroduceMain등 서술적 고유명으로 변경 권장.-export const Main = styled.main` +export const IntroduceMain = styled.main` background: #fff; display: flex; flex-direction: column; align-items: center; - justify-content: center; - overflow-x: hidden; + justify-content: flex-start; + /* 지원 시: clip, 미지원 환경은 현행 hidden 유지 검토 */ + overflow-x: clip; `검증: 제출한 검색 스크립트가 출력이 없어 참조를 찾지 못했습니다. 영향 범위 확인을 위해 아래 명령 중 하나 이상을 실행해 사용처(명시적 import / namespace import / JSX 태그 / 프로퍼티 접근)를 확인하세요:
rg -nP -C2 "from\s+['\"][^'\"]*IntroducePage\.styles['\"]" frontend rg -nP -C2 "import\s+{\s*[^}]*\bMain\b[^}]*}\s+from\s+['\"][^'\"]*IntroducePage\.styles['\"]" frontend rg -nP -C2 "import\s+\*\s+as\s+\w+\s+from\s+['\"][^'\"]*IntroducePage\.styles['\"]" frontend rg -nP -C2 "<\s*Main\b" frontend rg -nP -C2 "\b\w+\.Main\b" frontend
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
frontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.tsx (2)
37-41: 비효율적인 key 사용과 배열 중복으로 인한 React 렌더링 문제배열을 두 번 복사하여
[...tagsRow1, ...tagsRow1]로 사용하면서key={index}를 사용하면 React가 요소들을 제대로 식별하지 못할 수 있습니다. 또한 매 렌더링마다 새로운 배열이 생성되어 성능에 영향을 줄 수 있습니다.다음과 같이 수정하여 고유한 key와 메모이제이션을 적용하세요:
+import { useMemo } from 'react'; + const FeatureSection = () => { + const duplicatedTagsRow1 = useMemo(() => [...tagsRow1, ...tagsRow1], []); + const duplicatedTagsRow2 = useMemo(() => [...tagsRow2, ...tagsRow2], []); + return ( // ... <Styled.TagRow variants={scrollVariants} animate='left'> - {[...tagsRow1, ...tagsRow1].map((tag, index) => ( - <Styled.CustomTag key={index} type={tag.type}> + {duplicatedTagsRow1.map((tag, index) => ( + <Styled.CustomTag key={`row1-${index}-${tag.type}-${tag.label}`} type={tag.type}> {tag.label} </Styled.CustomTag> ))} </Styled.TagRow> // ... <Styled.TagRow variants={scrollVariants} animate='right'> - {[...tagsRow2, ...tagsRow2].map((tag, index) => ( - <Styled.CustomTag key={index} type={tag.type}> + {duplicatedTagsRow2.map((tag, index) => ( + <Styled.CustomTag key={`row2-${index}-${tag.type}-${tag.label}`} type={tag.type}> {tag.label} </Styled.CustomTag> ))} </Styled.TagRow>Also applies to: 47-51
28-28: 하드코딩된 텍스트를 상수로 분리"축구하는 동아리"라는 텍스트가 하드코딩되어 있어 유지보수성과 일관성이 떨어집니다. 다른 텍스트들이 mockData에서 관리되고 있으므로 동일한 패턴을 따르는 것이 좋겠습니다.
mockData.ts에 검색 플레이스홀더 텍스트를 추가하고 import하여 사용하세요:
// mockData.ts에 추가 +export const searchPlaceholder = '축구하는 동아리'; // FeatureSection.tsx에서 import 및 사용 -import { tagsRow1, tagsRow2 } from '@/pages/IntroducePage/constants/mockData'; +import { tagsRow1, tagsRow2, searchPlaceholder } from '@/pages/IntroducePage/constants/mockData'; - <Styled.TypingText>축구하는 동아리</Styled.TypingText> + <Styled.TypingText>{searchPlaceholder}</Styled.TypingText>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
frontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.tsx(1 hunks)frontend/src/pages/IntroducePage/components/sections/6.ConvenienceSection/ConvenienceSection.styles.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/pages/IntroducePage/components/sections/6.ConvenienceSection/ConvenienceSection.styles.ts
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.
Files:
frontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.tsx
frontend/**/*.tsx
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.tsx: Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Separate significantly different conditional UI/logic into distinct components.
Colocate simple, localized logic or use inline definitions to reduce context switching.
Choose field-level or form-level cohesion based on form requirements.
Break down broad state management into smaller, focused hooks or contexts.
Use component composition instead of props drilling.
Files:
frontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.tsx
🧬 Code graph analysis (1)
frontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.tsx (3)
frontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.styles.ts (1)
FeatureSection(6-29)frontend/src/pages/IntroducePage/constants/animations.ts (5)
stagger(20-23)VIEWPORT_CONFIG(55-58)fadeUp(5-8)fade(10-13)scrollVariants(25-34)frontend/src/pages/IntroducePage/constants/mockData.ts (2)
tagsRow1(47-55)tagsRow2(57-67)
🔇 Additional comments (1)
frontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.tsx (1)
12-59: 컴포넌트 구조와 애니메이션 구현이 잘 설계됨framer-motion의 stagger, fadeUp, fade, scrollVariants를 적절히 활용하여 순차적인 애니메이션 효과를 구현했습니다. 반응형 스타일링과 함께 사용자 경험을 향상시키는 좋은 구현입니다.
lepitaaar
left a comment
There was a problem hiding this comment.
와우 수고하셨습니다... 애니메이션 적용 정말 힘들었을꺼같네요
frontend/src/pages/IntroducePage/components/BackgroundShapes.tsx
Outdated
Show resolved
Hide resolved
suhyun113
left a comment
There was a problem hiding this comment.
애니메이션 적용한 거 보니까 진짜 예쁘네요~~
수고하셨어요!!
…{...}> import 방식으로 변경
frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.styles.ts
Outdated
Show resolved
Hide resolved
frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.styles.ts
Show resolved
Hide resolved
frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.tsx
Outdated
Show resolved
Hide resolved
frontend/src/pages/IntroducePage/components/sections/5.FeatureSection/FeatureSection.styles.ts
Show resolved
Hide resolved
…ithub.com/Moadong/moadong into feature/#697-intro-html-animation-MOA-197
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.styles.ts(1 hunks)frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.styles.ts
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.
Files:
frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.tsx
frontend/**/*.tsx
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.tsx: Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Separate significantly different conditional UI/logic into distinct components.
Colocate simple, localized logic or use inline definitions to reduce context switching.
Choose field-level or form-level cohesion based on form requirements.
Break down broad state management into smaller, focused hooks or contexts.
Use component composition instead of props drilling.
Files:
frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.tsx
🧬 Code graph analysis (1)
frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.tsx (4)
frontend/src/pages/IntroducePage/components/BackgroundShapes.tsx (4)
BackgroundTwistLeft(7-17)BackgroundTwistRight(19-29)BackgroundCircleSmall(31-41)BackgroundCircleLarge(43-53)frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.styles.ts (2)
IntroSection(5-28)Shape(49-88)frontend/src/pages/IntroducePage/constants/animations.ts (5)
stagger(20-23)VIEWPORT_CONFIG(55-58)fadeIn(15-18)fade(10-13)fadeUp(5-8)frontend/src/pages/IntroducePage/constants/mockData.ts (1)
floatingClubs(4-45)
frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.styles.ts (3)
1-4: 브랜드 색상 하드코딩 제거(매직 넘버 → 상수/테마화)#ff5414가 여러 곳에 하드코딩되어 있습니다. 상수 또는 테마 변수로 추출해 재사용성을 높이세요.
As per coding guidelines
import { media } from '@/styles/mediaQuery'; +const BRAND_PRIMARY = '#ff5414'; + export const IntroSection = styled(motion.section)`export const IntroTitle = styled(motion.h1)` - color: #ff5414; + color: ${BRAND_PRIMARY};padding: 14px 46px 14px 50px; - background: #ff5414; + background: ${BRAND_PRIMARY};Also applies to: 96-96, 136-136
183-189: VisualWrapper 고정 min-height의 모바일 과다 공백 가능성600px 고정은 작은 뷰포트에서 공백을 유발할 수 있습니다. 브레이크포인트별로 단계 하향 또는 clamp 도입을 권장합니다.
export const VisualWrapper = styled(motion.div)` display: flex; align-items: center; justify-content: center; margin-top: 29px; min-height: 600px; + ${media.laptop} { min-height: 520px; } + ${media.tablet} { min-height: 480px; } + ${media.mobile} { min-height: 440px; } `;
35-74: 섹션 간 Shape API 일관화(중복/불일치 최소화)ContactSection의 Shape와 API가 다릅니다. 공용 PositionedShape(브레이크포인트 override 포함)로 추출해 중복과 불일치 리스크를 줄이는 것을 권장합니다.
참고: frontend/src/pages/IntroducePage/components/sections/7.ContactSection/ContactSection.styles.ts Lines 90-102
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.styles.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.
Files:
frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.styles.ts
🧬 Code graph analysis (1)
frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.styles.ts (2)
frontend/src/styles/mediaQuery.ts (1)
media(8-14)frontend/src/pages/IntroducePage/components/sections/7.ContactSection/ContactSection.styles.ts (1)
Shape(91-103)
🔇 Additional comments (2)
frontend/src/pages/IntroducePage/components/sections/1.IntroSection/IntroSection.styles.ts (2)
112-129: 서브타이틀 대비 확인 필요rgba(0,0,0,0.5)는 배경 그라데이션 구간에 따라 대비가 낮아질 수 있습니다. 모든 브레이크포인트에서 최소 기준(일반 텍스트 4.5:1, Large 3:1)을 만족하는지 시각 테스트/도구 측정을 요청합니다. 미달 시 알파값 상향(예: 0.62~0.68) 또는 색상 톤 조정이 필요합니다.
131-181: CTA 버튼 접근성(대비·포커스·트랜지션 위치) — 기존 코멘트와 동일 이슈transition은 루트에 선언하고, focus-visible 가시성 제공이 필요합니다. 색상 대비(배경 #ff5414 + 흰 텍스트 16px)는 AA 실패 가능성이 높으므로 폰트 키우기(≥18.66px, Large) 또는 더 어두운 톤 사용 중 하나는 필수 검토 대상입니다.
export const IntroButton = styled(motion.button)` display: flex; align-items: center; gap: 12px; padding: 14px 46px 14px 50px; background: #ff5414; color: #fff; font-weight: bold; font-size: 16px; border: none; border-radius: 50px; margin-top: 28px; cursor: pointer; + transition: background 0.3s, box-shadow 0.3s; + &:focus-visible { + outline: 3px solid #111; + outline-offset: 3px; + } .icon { width: 14px; height: 14px; filter: brightness(0) invert(1); } &:hover { background: #e04e0f; - transition: - background 0.3s, - box-shadow 0.3s; }참고: 색상 대비를 유지하려면 기본 font-size를 18.666px로 상향하거나, 브랜드 컬러를 더 어둡게 조정하세요. 모바일 등 하위 브레이크포인트의 font-size 규칙도 함께 재검토가 필요합니다.
#️⃣연관된 이슈
📝작업 내용
기존에 하나의 덩어리 이미지로만 되어 있던 소개 페이지를
HTML 기반으로 재구성하고, 애니메이션과 반응형 레이아웃을 적용한 작업입니다.
고봉밥 PR ㅎㅎ ㅜㅜ
작업 보러가기
1. 구조 소개
사진만 봐도 구조를 알겠쥬? 네 그렇습니다.
소개페이지 내부를 섹션으로 나누고 나열 순서대로 숫자를 붙여서 파일명/순서가 한눈에 들어오도록 정리했습니다.
숫자가 없으면 멋대로 정렬되는데 이렇게 해두면 추후 누군가 유지보수하기 쉬워요.
2. 상수 파일 분리 (2가지)
하드코딩을 최대한 피하기 위해 숫자 값과 소개 페이지 콘텐츠를 따로 관리하게 만들었습니다.
앞으로 수정 시 이 파일만 고치면 구조 전체에 반영됩니다.
animations.ts
fadeUp,fade,stagger등 framer-motion variants 모음mockData.ts
3. useIsMobile → useDevice 확장
기존에는
mobile여부만 판별했는데, 이번에 desktop / laptop / tablet / mobile 네 가지로 확장했습니다.디자인 기준은 1280px 이상을 데스크탑 기본으로 잡았습니다.
기존
useIsMobile훅은 아직 삭제하지 않았습니다.다만 import 경로만
useDevice로 교체하면 그대로 동작하기 때문에 바로 교체하면 됩니다.그리고 미디어 쿼리도 함께 확장했습니다.
4. ClubTag 컴포넌트 태그 크기 조정
classname props를 추가해서 소개 페이지의 태그를 조금 더 크고 눈에 띄게 만들었습니다.
중점적으로 리뷰받고 싶은 부분(선택)
칭찬과 피드백 환영합니다 🙏
논의하고 싶은 부분(선택)
AI 활용 사례
처음에는 모든 코드를 한 페이지에 몰아넣고 만들었는데 리팩토링 과정에서 Cursor AI를 활용했습니다.
각 섹션을
JSX와styles.ts로 깔끔하게 분리해줘서 정말 한 치의 오차도 없이 잘 나눠져 편리했습니다 🙌🫡 참고사항
“더 편리하게 모아동 사용하기” 섹션
원래는 휴대폰 이미지만 쓰고 싶었는데 현재는 전체 덩어리 이미지로밖에 구현이 불가능했습니다.
검색 아이콘(svg)
색상 변경 기능을 적용해야 하는데 아직 남아 있습니다.
SEO 관련 태그 순서
디자인 시안상 순서는 다르지만,
h1태그를 위쪽에 배치해서 SEO를 고려해 구조를 바꿔두었습니다.
Summary by CodeRabbit
신기능
스타일
테스트
작업