diff --git a/frontend/src/pages/Home/ui/HomePage.tsx b/frontend/src/pages/Home/ui/HomePage.tsx index d5dcf027..d58bccd4 100644 --- a/frontend/src/pages/Home/ui/HomePage.tsx +++ b/frontend/src/pages/Home/ui/HomePage.tsx @@ -1,18 +1,23 @@ -// PR 2 (feature/home-layout) 단계의 HomePage.tsx import { SiteNav } from '@/widgets/site-nav' +import { HomeHero } from '@/widgets/home-hero' +import { HomeServices } from '@/widgets/home-services' +import { HomeQuote } from '@/widgets/home-quote' +import { HomeFaq } from '@/widgets/home-faq' +import { HomeCta } from '@/widgets/home-cta' import { SiteFooter } from '@/widgets/site-footer' -// 아직 PR 3에서 만들 예정이므로 주석 처리! -// import { HomeHero } from '@/widgets/home-hero' export default function HomePage() { return (
- {/* 글로벌 레이아웃은 추후 분리 예정*/}
- {/* TODO: 다음 PR에서 위젯들 추가 예정 */} + + + + +
) -} \ No newline at end of file +} diff --git a/frontend/src/shared/hooks/index.ts b/frontend/src/shared/hooks/index.ts index e69de29b..f0e17fce 100644 --- a/frontend/src/shared/hooks/index.ts +++ b/frontend/src/shared/hooks/index.ts @@ -0,0 +1,2 @@ +export { useTypewriter } from './useTypewriter' +export type { UseTypewriterOptions } from './useTypewriter' diff --git a/frontend/src/shared/hooks/useTypewriter.ts b/frontend/src/shared/hooks/useTypewriter.ts new file mode 100644 index 00000000..d7f36398 --- /dev/null +++ b/frontend/src/shared/hooks/useTypewriter.ts @@ -0,0 +1,61 @@ +import { useEffect, useState } from 'react' + +export type UseTypewriterOptions = { + startDelayMs?: number + stepMs?: number + respectReducedMotion?: boolean +} + +export function useTypewriter( + text: string, + options: UseTypewriterOptions = {}, +) { + const { + startDelayMs = 0, + stepMs = 100, + respectReducedMotion = true, + } = options + + const [typed, setTyped] = useState('') + + useEffect(() => { + const reduce = + respectReducedMotion && + typeof window !== 'undefined' && + window.matchMedia?.('(prefers-reduced-motion: reduce)').matches + + if (reduce) { + setTyped(text) + return + } + + setTyped('') + let rafId = 0 + let startTime: number | null = null + + const tick = (now: number) => { + if (startTime === null) startTime = now + const elapsed = now - startTime - startDelayMs + if (elapsed < 0) { + rafId = window.requestAnimationFrame(tick) + return + } + const next = Math.min( + Math.floor(elapsed / stepMs) + 1, + text.length, + ) + setTyped(text.slice(0, next)) + if (next < text.length) { + rafId = window.requestAnimationFrame(tick) + } + } + + rafId = window.requestAnimationFrame(tick) + + return () => { + window.cancelAnimationFrame(rafId) + } + }, [text, startDelayMs, stepMs, respectReducedMotion]) + + return { typed, done: typed.length >= text.length } +} diff --git a/frontend/src/widgets/home-cta/index.ts b/frontend/src/widgets/home-cta/index.ts new file mode 100644 index 00000000..408926af --- /dev/null +++ b/frontend/src/widgets/home-cta/index.ts @@ -0,0 +1 @@ +export { HomeCta } from './ui/HomeCta' diff --git a/frontend/src/widgets/home-cta/ui/HomeCta.tsx b/frontend/src/widgets/home-cta/ui/HomeCta.tsx new file mode 100644 index 00000000..953fca4f --- /dev/null +++ b/frontend/src/widgets/home-cta/ui/HomeCta.tsx @@ -0,0 +1,51 @@ +export function HomeCta() { + return ( +
+
+
+ +
+ +
+

+ Ready to level up? +

+

+ 지금 GitHub 계정만 연결하면, 30초 안에 첫 모의면접이 시작됩니다. +

+ + + Get Started + + → + + +
+
+
+
+ ) +} diff --git a/frontend/src/widgets/home-faq/index.ts b/frontend/src/widgets/home-faq/index.ts new file mode 100644 index 00000000..f9e10fb2 --- /dev/null +++ b/frontend/src/widgets/home-faq/index.ts @@ -0,0 +1 @@ +export { HomeFaq } from './ui/HomeFaq' diff --git a/frontend/src/widgets/home-faq/ui/HomeFaq.tsx b/frontend/src/widgets/home-faq/ui/HomeFaq.tsx new file mode 100644 index 00000000..ab517cff --- /dev/null +++ b/frontend/src/widgets/home-faq/ui/HomeFaq.tsx @@ -0,0 +1,86 @@ +const faqs = [ + { + q: 'Stack Up은 무료 크레딧을 제공하나요?', + a: '월 2회의 무료 면접 세션을 제공합니다. 학교 종합설계 프로젝트 기준이라 결제·구독 기능은 포함되지 않아요.', + }, + { + q: '어떤 형식의 이력서를 지원하나요?', + a: 'Phase 1 기준 PDF만 지원합니다. HWP·DOCX는 PDF로 변환 후 업로드해 주세요.', + }, + { + q: 'GitHub 외 다른 로그인은 가능한가요?', + a: '레포 분석이 핵심 기능이라, Phase 1에서는 GitHub OAuth만 제공합니다.', + }, + { + q: '꼬리질문은 얼마나 빨리 받을 수 있나요?', + a: '평균 3초 이내 응답을 목표로 합니다. Flash 모델과 사전 구축 RAG 인덱스로 지연을 최소화해요.', + }, + { + q: '음성·비언어 분석은 어떻게 동작하나요?', + a: 'WebRTC로 마이크·웹캠 스트림을 받아 말 속도(WPM)·간투어·시선·자세를 분석합니다. 권한을 거부하면 텍스트 입력으로 진행할 수 있어요.', + }, +] + +export function HomeFaq() { + return ( +
+
+

+ FAQ +

+ +

+ Questions, answered. +

+ +
+ +
+
+

+ 여기에 없는 궁금증은 Contact 페이지에서 이어서 답해드려요. +

+ + Contact + + → + + +
+ +
    + {faqs.map((f) => ( +
  • +
    + + + {f.q} + + + + + + +
    + {f.a} +
    +
    +
  • + ))} +
+
+
+
+ ) +} diff --git a/frontend/src/widgets/home-hero/index.ts b/frontend/src/widgets/home-hero/index.ts new file mode 100644 index 00000000..ec86fcb7 --- /dev/null +++ b/frontend/src/widgets/home-hero/index.ts @@ -0,0 +1 @@ +export { HomeHero } from './ui/HomeHero' diff --git a/frontend/src/widgets/home-hero/ui/HomeHero.tsx b/frontend/src/widgets/home-hero/ui/HomeHero.tsx new file mode 100644 index 00000000..40e4ecb1 --- /dev/null +++ b/frontend/src/widgets/home-hero/ui/HomeHero.tsx @@ -0,0 +1,38 @@ +import { Laptop } from './Laptop' +import { ScreenContent } from './ScreenContent' + +export function HomeHero() { + return ( +
+
+ +
+ + + + +

+ + + Phase 1 · MVP · 2026 + +

+
+
+ ) +} diff --git a/frontend/src/widgets/home-hero/ui/Laptop.tsx b/frontend/src/widgets/home-hero/ui/Laptop.tsx new file mode 100644 index 00000000..a0c505a0 --- /dev/null +++ b/frontend/src/widgets/home-hero/ui/Laptop.tsx @@ -0,0 +1,59 @@ +import type { ReactNode } from 'react' + +export function Laptop({ children }: { children: ReactNode }) { + return ( +
+ {/* Lid */} +
+ {/* Bezel */} +
+ {/* Camera dot */} +
+ + {/* Screen */} +
+ {children} +
+
+
+ + {/* Base / hinge */} +
+
+
+
+
+ ) +} diff --git a/frontend/src/widgets/home-hero/ui/ScreenContent.tsx b/frontend/src/widgets/home-hero/ui/ScreenContent.tsx new file mode 100644 index 00000000..0d50d1fb --- /dev/null +++ b/frontend/src/widgets/home-hero/ui/ScreenContent.tsx @@ -0,0 +1,60 @@ +import { useTypewriter } from '@/shared/hooks' + +const TYPED_TEXT = 'Stack Up' + +export function ScreenContent() { + const { typed, done } = useTypewriter(TYPED_TEXT, { + startDelayMs: 850, + stepMs: 130, + }) + + return ( +
+

+ IT Interview Solution +

+ +

+ {typed} + +

+ +
+ + Get Started + + → + + +
+
+ ) +} diff --git a/frontend/src/widgets/home-quote/index.ts b/frontend/src/widgets/home-quote/index.ts new file mode 100644 index 00000000..e685ef69 --- /dev/null +++ b/frontend/src/widgets/home-quote/index.ts @@ -0,0 +1 @@ +export { HomeQuote } from './ui/HomeQuote' diff --git a/frontend/src/widgets/home-quote/ui/HomeQuote.tsx b/frontend/src/widgets/home-quote/ui/HomeQuote.tsx new file mode 100644 index 00000000..e27dba30 --- /dev/null +++ b/frontend/src/widgets/home-quote/ui/HomeQuote.tsx @@ -0,0 +1,88 @@ +export function HomeQuote() { + return ( +
+
+
+
+ {/* decorative left panel */} +
+
+
+
+
+ Stack Up +
+
+
+ + {/* quote panel */} +
+
+ “ +
+ +
+

+ Stack Up은 올인원 IT 면접 솔루션입니다. 본인 코드를 가장 잘 아는 + 면접관 — GitHub 레포에서 출발해, 답변의 깊이까지 따라 들어갑니다. +

+
+ +
+
+
+ 박상우 · 신재호 · 정준모 · 조서현 +
+
+ Team StackUp · CNU +
+
+
+ + + 2026 · Phase 1 + +
+
+
+
+
+
+
+ ) +} diff --git a/frontend/src/widgets/home-services/index.ts b/frontend/src/widgets/home-services/index.ts new file mode 100644 index 00000000..b58cf390 --- /dev/null +++ b/frontend/src/widgets/home-services/index.ts @@ -0,0 +1 @@ +export { HomeServices } from './ui/HomeServices' diff --git a/frontend/src/widgets/home-services/ui/HomeServices.tsx b/frontend/src/widgets/home-services/ui/HomeServices.tsx new file mode 100644 index 00000000..e19a1334 --- /dev/null +++ b/frontend/src/widgets/home-services/ui/HomeServices.tsx @@ -0,0 +1,80 @@ +type ServiceCard = { + tag: string + title: string + image: string +} + +const services: ServiceCard[] = [ + { + tag: 'Service', + title: 'Frontend Interview', + image: '/second-section-frontend-interview.png', + }, + { + tag: 'Service', + title: 'Backend Interview', + image: '/second-section-backend-interview.avif', + }, + { + tag: 'Service', + title: 'CS / Full Stack', + image: '/second-section-cs-interview.avif', + }, +] + +export function HomeServices() { + return ( +
+
+

+ Our Services +

+ + +
+ ) +}