From f6963545cf07cb016ecc4cbc5bdb9bd48f099a6d Mon Sep 17 00:00:00 2001 From: Jaeho Date: Wed, 13 May 2026 00:17:32 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat=20:=20=EC=9E=84=EC=8B=9C=20=ED=97=A4?= =?UTF-8?q?=EB=8D=94=20=EC=A0=95=EC=9D=98(=ED=99=88=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/widgets/site-nav/ui/SiteNav.tsx | 66 ++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 frontend/src/widgets/site-nav/ui/SiteNav.tsx diff --git a/frontend/src/widgets/site-nav/ui/SiteNav.tsx b/frontend/src/widgets/site-nav/ui/SiteNav.tsx new file mode 100644 index 00000000..fcc1a775 --- /dev/null +++ b/frontend/src/widgets/site-nav/ui/SiteNav.tsx @@ -0,0 +1,66 @@ +import { useEffect, useState } from 'react' + +const items = [ + { href: '#services', label: 'Services' }, + { href: '#quote', label: 'About' }, + { href: '#faq', label: 'FAQ' }, +] + +export function SiteNav() { + const [scrolled, setScrolled] = useState(false) + + useEffect(() => { + const onScroll = () => setScrolled(window.scrollY > 8) + onScroll() + window.addEventListener('scroll', onScroll, { passive: true }) + return () => window.removeEventListener('scroll', onScroll) + }, []) + + return ( +
+
+ + Stack Up + + + + +
+ + Get Started + + → + + +
+
+
+ ) +} From a0878d3b610fd94d3b7e9c93fa51b6f86450550f Mon Sep 17 00:00:00 2001 From: Jaeho Date: Wed, 13 May 2026 00:18:05 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat=20:=20SiteNav=20=EC=9D=B8=EB=8D=B1?= =?UTF-8?q?=EC=8A=A4=20=ED=8C=8C=EC=9D=BC=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/widgets/site-nav/index.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 frontend/src/widgets/site-nav/index.ts diff --git a/frontend/src/widgets/site-nav/index.ts b/frontend/src/widgets/site-nav/index.ts new file mode 100644 index 00000000..9ebd3419 --- /dev/null +++ b/frontend/src/widgets/site-nav/index.ts @@ -0,0 +1 @@ +export { SiteNav } from './ui/SiteNav' From 9c67fe2fe0205c476a634c25480dfa983830406f Mon Sep 17 00:00:00 2001 From: Jaeho Date: Wed, 13 May 2026 00:19:44 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat=20:=20=EC=9E=84=EC=8B=9C=20Footer=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98(=ED=99=88=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/widgets/site-footer/ui/SiteFooter.tsx | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 frontend/src/widgets/site-footer/ui/SiteFooter.tsx diff --git a/frontend/src/widgets/site-footer/ui/SiteFooter.tsx b/frontend/src/widgets/site-footer/ui/SiteFooter.tsx new file mode 100644 index 00000000..d3ae1cec --- /dev/null +++ b/frontend/src/widgets/site-footer/ui/SiteFooter.tsx @@ -0,0 +1,113 @@ +// 단순 뷰 섹션 widgets 에선 굳이 나누지 않는게 좋다고 판단했습니다. +// 상수, 메세지 등 마찬가지 +const columns = [ + { + title: 'Company', + links: [ + { label: 'Home', href: '#top' }, + { label: 'About', href: '#quote' }, + { label: 'FAQ', href: '#faq' }, + { label: 'Team', href: '#quote' }, + ], + }, + { + title: 'Services', + links: [ + { label: 'Frontend Interview', href: '#services' }, + { label: 'Backend Interview', href: '#services' }, + { label: 'CS / Full Stack', href: '#services' }, + { label: 'Reports', href: '#services' }, + ], + }, + { + title: 'Other', + links: [ + { label: 'Design System', href: '/design-system' }, + { label: 'GitHub', href: '#' }, + { label: 'Privacy', href: '#' }, + { label: 'Get Started', href: '#cta' }, + ], + }, +] + +export function SiteFooter() { + return ( +
+
+
+

+ One smart step +

+ + Get Started + + → + + +
+ +
+ +
+
+
+ Stack Up +
+

+ IT 직군 멀티모달 AI 면접 시뮬레이터. GitHub 레포와 이력서를 분석해 + 개인 맞춤 면접과 음성·비언어적 피드백을 제공합니다. +

+
+ + +
+ +
+
+ © 2026 StackUp · CNU 종합설계. All rights reserved. +
+ +
+
+
+ ) +} From f562c97ef1788c1455206b0969ef36782586c873 Mon Sep 17 00:00:00 2001 From: Jaeho Date: Wed, 13 May 2026 00:20:48 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat=20:=20SiteFooter=20=EC=9D=B8?= =?UTF-8?q?=EB=8D=B1=EC=8A=A4=20=ED=8C=8C=EC=9D=BC=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/widgets/site-footer/index.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 frontend/src/widgets/site-footer/index.ts diff --git a/frontend/src/widgets/site-footer/index.ts b/frontend/src/widgets/site-footer/index.ts new file mode 100644 index 00000000..291f5dd6 --- /dev/null +++ b/frontend/src/widgets/site-footer/index.ts @@ -0,0 +1 @@ +export { SiteFooter } from './ui/SiteFooter' From 8998f9e79b7f0e158dcc1f9d35aa1eaa013aa0da Mon Sep 17 00:00:00 2001 From: Jaeho Date: Wed, 13 May 2026 00:21:20 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat=20:=20=ED=99=88=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98=20(=EA=B8=80=EB=A1=9C=EB=B2=8C=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=EB=A7=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Home/ui/HomePage.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 frontend/src/pages/Home/ui/HomePage.tsx diff --git a/frontend/src/pages/Home/ui/HomePage.tsx b/frontend/src/pages/Home/ui/HomePage.tsx new file mode 100644 index 00000000..d5dcf027 --- /dev/null +++ b/frontend/src/pages/Home/ui/HomePage.tsx @@ -0,0 +1,18 @@ +// PR 2 (feature/home-layout) 단계의 HomePage.tsx +import { SiteNav } from '@/widgets/site-nav' +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 From 810af89fff8cdd641b4a172d3e7ecb991abee84d Mon Sep 17 00:00:00 2001 From: Jaeho Date: Wed, 13 May 2026 00:22:50 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat=20:=20=ED=99=88=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=9D=B8=EB=8D=B1=EC=8A=A4=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Home/index.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 frontend/src/pages/Home/index.ts diff --git a/frontend/src/pages/Home/index.ts b/frontend/src/pages/Home/index.ts new file mode 100644 index 00000000..38c68496 --- /dev/null +++ b/frontend/src/pages/Home/index.ts @@ -0,0 +1 @@ +export { default } from './ui/HomePage' From 0614fc2ef98fa339c65cd2569939b8c91b07fa78 Mon Sep 17 00:00:00 2001 From: Jaeho Date: Wed, 13 May 2026 00:23:27 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat=20:=20App=EC=97=90=20=ED=99=88=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 78deb8ec..cd9f4def 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,19 @@ -import DesignSystemDemo from '@/pages/DesignSystem' +import { lazy, Suspense } from 'react' +import HomePage from '@/pages/Home' + +const DesignSystemDemo = lazy(() => import('@/pages/DesignSystem')) export default function App() { - return + const path = + typeof window !== 'undefined' ? window.location.pathname : '/' + + if (path.startsWith('/design-system')) { + return ( + + + + ) + } + + return } From b0070e10d2ec5b4910541c3088bcda631dd3ca15 Mon Sep 17 00:00:00 2001 From: Jaeho Date: Wed, 13 May 2026 00:24:33 +0900 Subject: [PATCH 08/10] =?UTF-8?q?style=20:=20=EC=8A=A4=ED=83=80=EC=9D=BC?= =?UTF-8?q?=20=ED=86=A0=ED=81=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/styles/global.css | 61 ++++++++++++++++++++++++++++-- frontend/src/app/styles/tokens.css | 12 +++--- 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/styles/global.css b/frontend/src/app/styles/global.css index 8d56d56d..b2899d23 100644 --- a/frontend/src/app/styles/global.css +++ b/frontend/src/app/styles/global.css @@ -3,8 +3,11 @@ * - html/body 기본 * - 접근성 기본 (focus-visible, prefers-reduced-motion) * - * Google Fonts (Fira Sans Extra Condensed, Geist, Geist Mono, Inter) 는 - * `index.html` 의 `` 로 로드 (성능상 CSS @import url 보다 권장). + * 폰트는 `index.html` 의 `` 로 로드. + * - Pretendard Variable (한·영 본문/서브헤딩) + * - Bricolage Grotesque (디스플레이 헤딩) + * - Caveat (스크립트 강조) + * - Geist Mono (코드/모노) */ @layer base { @@ -26,8 +29,8 @@ h1, h2, h3 { font-family: var(--font-heading); color: var(--color-fg-strong); - text-transform: uppercase; margin: 0; + letter-spacing: -0.01em; } h4, h5, h6 { @@ -58,3 +61,55 @@ } } } + +@layer utilities { + /* Hero stagger animation */ + @keyframes hero-rise { + from { + opacity: 0; + transform: translateY(16px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + @keyframes hero-pop { + from { + opacity: 0; + transform: translateY(10px) rotate(-3deg) scale(0.92); + } + to { + opacity: 1; + transform: translateY(0) rotate(-2deg) scale(1); + } + } + @keyframes laptop-rise { + from { + opacity: 0; + transform: translateY(24px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + @keyframes screen-glow { + 0%, 100% { opacity: 0.85; } + 50% { opacity: 1; } + } + @keyframes caret-blink { + 0%, 49% { opacity: 1; } + 50%, 100% { opacity: 0; } + } + + .anim-hero-rise { + animation: hero-rise 0.8s var(--ease-decelerate) both; + } + .anim-hero-pop { + animation: hero-pop 0.9s var(--ease-decelerate) both; + } + .anim-laptop-rise { + animation: laptop-rise 0.9s var(--ease-decelerate) both; + } +} diff --git a/frontend/src/app/styles/tokens.css b/frontend/src/app/styles/tokens.css index 549b97b5..e1a67264 100644 --- a/frontend/src/app/styles/tokens.css +++ b/frontend/src/app/styles/tokens.css @@ -96,15 +96,17 @@ * font-heading, font-subheading, font-sans, font-mono * ========================================================= */ --font-heading: - 'Fira Sans Extra Condensed', 'Pretendard Variable', system-ui, sans-serif; + 'Bricolage Grotesque', 'Pretendard Variable', system-ui, sans-serif; --font-display: - 'Fira Sans Extra Condensed', 'Pretendard Variable', system-ui, sans-serif; + 'Bricolage Grotesque', 'Pretendard Variable', system-ui, sans-serif; --font-subheading: - 'Geist', 'Pretendard Variable', system-ui, sans-serif; + 'Pretendard Variable', system-ui, sans-serif; --font-sans: - 'Inter', 'Pretendard Variable', system-ui, sans-serif; + 'Pretendard Variable', system-ui, sans-serif; --font-body: - 'Inter', 'Pretendard Variable', system-ui, sans-serif; + 'Pretendard Variable', system-ui, sans-serif; + --font-script: + 'Caveat', 'Pretendard Variable', system-ui, sans-serif; --font-mono: 'Geist Mono', ui-monospace, 'JetBrains Mono', Consolas, monospace; From a3eddd51f8f44dade84048c92c623d101a11584f Mon Sep 17 00:00:00 2001 From: Jaeho Date: Wed, 13 May 2026 00:25:50 +0900 Subject: [PATCH 09/10] =?UTF-8?q?refactor=20:=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=B0=94=EC=9A=B4=EB=8D=94=EB=A6=AC=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/shared/lib/AsyncBoundary/ErrorBoundary.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/shared/lib/AsyncBoundary/ErrorBoundary.tsx b/frontend/src/shared/lib/AsyncBoundary/ErrorBoundary.tsx index 81e2d5a7..df2431f8 100644 --- a/frontend/src/shared/lib/AsyncBoundary/ErrorBoundary.tsx +++ b/frontend/src/shared/lib/AsyncBoundary/ErrorBoundary.tsx @@ -1,23 +1,23 @@ import { Component, type ErrorInfo, type ReactNode } from 'react'; -interface Props { +interface ErrorBoundaryProps { children: ReactNode; fallback: ReactNode | ((props: { error: Error; reset: () => void }) => ReactNode); onReset?: () => void; } -interface State { +interface ErrorBoundaryState { hasError: boolean; error: Error | null; } -export class ErrorBoundary extends Component { - constructor(props: Props) { +export class ErrorBoundary extends Component { + constructor(props: ErrorBoundaryProps) { super(props); this.state = { hasError: false, error: null }; } - static getDerivedStateFromError(error: Error): State { + static getDerivedStateFromError(error: Error): ErrorBoundaryState { return { hasError: true, error }; } From 4752b9858aadd064d79af3adce7d2ccd5198bdb3 Mon Sep 17 00:00:00 2001 From: Jaeho Date: Wed, 13 May 2026 15:33:46 +0900 Subject: [PATCH 10/10] =?UTF-8?q?refactor=20:=20=EB=94=94=EB=A0=89?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/{AsyncBoundary => async-boundary}/AsyncBoundary.tsx | 0 .../lib/{AsyncBoundary => async-boundary}/ErrorBoundary.tsx | 0 .../src/shared/lib/{AsyncBoundary => async-boundary}/index.ts | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename frontend/src/shared/lib/{AsyncBoundary => async-boundary}/AsyncBoundary.tsx (100%) rename frontend/src/shared/lib/{AsyncBoundary => async-boundary}/ErrorBoundary.tsx (100%) rename frontend/src/shared/lib/{AsyncBoundary => async-boundary}/index.ts (100%) diff --git a/frontend/src/shared/lib/AsyncBoundary/AsyncBoundary.tsx b/frontend/src/shared/lib/async-boundary/AsyncBoundary.tsx similarity index 100% rename from frontend/src/shared/lib/AsyncBoundary/AsyncBoundary.tsx rename to frontend/src/shared/lib/async-boundary/AsyncBoundary.tsx diff --git a/frontend/src/shared/lib/AsyncBoundary/ErrorBoundary.tsx b/frontend/src/shared/lib/async-boundary/ErrorBoundary.tsx similarity index 100% rename from frontend/src/shared/lib/AsyncBoundary/ErrorBoundary.tsx rename to frontend/src/shared/lib/async-boundary/ErrorBoundary.tsx diff --git a/frontend/src/shared/lib/AsyncBoundary/index.ts b/frontend/src/shared/lib/async-boundary/index.ts similarity index 100% rename from frontend/src/shared/lib/AsyncBoundary/index.ts rename to frontend/src/shared/lib/async-boundary/index.ts