Skip to content

Commit 3892b07

Browse files
authored
Merge pull request #9 from Team-StackUp/feature/home-layout
Feature/home layout
2 parents 553db09 + 4752b98 commit 3892b07

12 files changed

Lines changed: 286 additions & 15 deletions

File tree

frontend/src/App.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
1-
import DesignSystemDemo from '@/pages/DesignSystem'
1+
import { lazy, Suspense } from 'react'
2+
import HomePage from '@/pages/Home'
3+
4+
const DesignSystemDemo = lazy(() => import('@/pages/DesignSystem'))
25

36
export default function App() {
4-
return <DesignSystemDemo />
7+
const path =
8+
typeof window !== 'undefined' ? window.location.pathname : '/'
9+
10+
if (path.startsWith('/design-system')) {
11+
return (
12+
<Suspense fallback={null}>
13+
<DesignSystemDemo />
14+
</Suspense>
15+
)
16+
}
17+
18+
return <HomePage />
519
}

frontend/src/app/styles/global.css

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
* - html/body 기본
44
* - 접근성 기본 (focus-visible, prefers-reduced-motion)
55
*
6-
* Google Fonts (Fira Sans Extra Condensed, Geist, Geist Mono, Inter) 는
7-
* `index.html` 의 `<link>` 로 로드 (성능상 CSS @import url 보다 권장).
6+
* 폰트는 `index.html` 의 `<link>` 로 로드.
7+
* - Pretendard Variable (한·영 본문/서브헤딩)
8+
* - Bricolage Grotesque (디스플레이 헤딩)
9+
* - Caveat (스크립트 강조)
10+
* - Geist Mono (코드/모노)
811
*/
912

1013
@layer base {
@@ -26,8 +29,8 @@
2629
h1, h2, h3 {
2730
font-family: var(--font-heading);
2831
color: var(--color-fg-strong);
29-
text-transform: uppercase;
3032
margin: 0;
33+
letter-spacing: -0.01em;
3134
}
3235

3336
h4, h5, h6 {
@@ -58,3 +61,55 @@
5861
}
5962
}
6063
}
64+
65+
@layer utilities {
66+
/* Hero stagger animation */
67+
@keyframes hero-rise {
68+
from {
69+
opacity: 0;
70+
transform: translateY(16px);
71+
}
72+
to {
73+
opacity: 1;
74+
transform: translateY(0);
75+
}
76+
}
77+
@keyframes hero-pop {
78+
from {
79+
opacity: 0;
80+
transform: translateY(10px) rotate(-3deg) scale(0.92);
81+
}
82+
to {
83+
opacity: 1;
84+
transform: translateY(0) rotate(-2deg) scale(1);
85+
}
86+
}
87+
@keyframes laptop-rise {
88+
from {
89+
opacity: 0;
90+
transform: translateY(24px);
91+
}
92+
to {
93+
opacity: 1;
94+
transform: translateY(0);
95+
}
96+
}
97+
@keyframes screen-glow {
98+
0%, 100% { opacity: 0.85; }
99+
50% { opacity: 1; }
100+
}
101+
@keyframes caret-blink {
102+
0%, 49% { opacity: 1; }
103+
50%, 100% { opacity: 0; }
104+
}
105+
106+
.anim-hero-rise {
107+
animation: hero-rise 0.8s var(--ease-decelerate) both;
108+
}
109+
.anim-hero-pop {
110+
animation: hero-pop 0.9s var(--ease-decelerate) both;
111+
}
112+
.anim-laptop-rise {
113+
animation: laptop-rise 0.9s var(--ease-decelerate) both;
114+
}
115+
}

frontend/src/app/styles/tokens.css

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,17 @@
9696
* font-heading, font-subheading, font-sans, font-mono
9797
* ========================================================= */
9898
--font-heading:
99-
'Fira Sans Extra Condensed', 'Pretendard Variable', system-ui, sans-serif;
99+
'Bricolage Grotesque', 'Pretendard Variable', system-ui, sans-serif;
100100
--font-display:
101-
'Fira Sans Extra Condensed', 'Pretendard Variable', system-ui, sans-serif;
101+
'Bricolage Grotesque', 'Pretendard Variable', system-ui, sans-serif;
102102
--font-subheading:
103-
'Geist', 'Pretendard Variable', system-ui, sans-serif;
103+
'Pretendard Variable', system-ui, sans-serif;
104104
--font-sans:
105-
'Inter', 'Pretendard Variable', system-ui, sans-serif;
105+
'Pretendard Variable', system-ui, sans-serif;
106106
--font-body:
107-
'Inter', 'Pretendard Variable', system-ui, sans-serif;
107+
'Pretendard Variable', system-ui, sans-serif;
108+
--font-script:
109+
'Caveat', 'Pretendard Variable', system-ui, sans-serif;
108110
--font-mono:
109111
'Geist Mono', ui-monospace, 'JetBrains Mono', Consolas, monospace;
110112

frontend/src/pages/Home/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './ui/HomePage'
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// PR 2 (feature/home-layout) 단계의 HomePage.tsx
2+
import { SiteNav } from '@/widgets/site-nav'
3+
import { SiteFooter } from '@/widgets/site-footer'
4+
// 아직 PR 3에서 만들 예정이므로 주석 처리!
5+
// import { HomeHero } from '@/widgets/home-hero'
6+
7+
export default function HomePage() {
8+
return (
9+
<div className="min-h-svh bg-bg text-fg">
10+
{/* 글로벌 레이아웃은 추후 분리 예정*/}
11+
<SiteNav />
12+
<main>
13+
{/* TODO: 다음 PR에서 위젯들 추가 예정 */}
14+
</main>
15+
<SiteFooter />
16+
</div>
17+
)
18+
}

frontend/src/shared/lib/AsyncBoundary/AsyncBoundary.tsx renamed to frontend/src/shared/lib/async-boundary/AsyncBoundary.tsx

File renamed without changes.

frontend/src/shared/lib/AsyncBoundary/ErrorBoundary.tsx renamed to frontend/src/shared/lib/async-boundary/ErrorBoundary.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
import { Component, type ErrorInfo, type ReactNode } from 'react';
22

3-
interface Props {
3+
interface ErrorBoundaryProps {
44
children: ReactNode;
55
fallback: ReactNode | ((props: { error: Error; reset: () => void }) => ReactNode);
66
onReset?: () => void;
77
}
88

9-
interface State {
9+
interface ErrorBoundaryState {
1010
hasError: boolean;
1111
error: Error | null;
1212
}
1313

14-
export class ErrorBoundary extends Component<Props, State> {
15-
constructor(props: Props) {
14+
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
15+
constructor(props: ErrorBoundaryProps) {
1616
super(props);
1717
this.state = { hasError: false, error: null };
1818
}
1919

20-
static getDerivedStateFromError(error: Error): State {
20+
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
2121
return { hasError: true, error };
2222
}
2323

File renamed without changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { SiteFooter } from './ui/SiteFooter'
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// 단순 뷰 섹션 widgets 에선 굳이 나누지 않는게 좋다고 판단했습니다.
2+
// 상수, 메세지 등 마찬가지
3+
const columns = [
4+
{
5+
title: 'Company',
6+
links: [
7+
{ label: 'Home', href: '#top' },
8+
{ label: 'About', href: '#quote' },
9+
{ label: 'FAQ', href: '#faq' },
10+
{ label: 'Team', href: '#quote' },
11+
],
12+
},
13+
{
14+
title: 'Services',
15+
links: [
16+
{ label: 'Frontend Interview', href: '#services' },
17+
{ label: 'Backend Interview', href: '#services' },
18+
{ label: 'CS / Full Stack', href: '#services' },
19+
{ label: 'Reports', href: '#services' },
20+
],
21+
},
22+
{
23+
title: 'Other',
24+
links: [
25+
{ label: 'Design System', href: '/design-system' },
26+
{ label: 'GitHub', href: '#' },
27+
{ label: 'Privacy', href: '#' },
28+
{ label: 'Get Started', href: '#cta' },
29+
],
30+
},
31+
]
32+
33+
export function SiteFooter() {
34+
return (
35+
<footer
36+
id="footer"
37+
className="relative text-white"
38+
style={{ background: 'var(--color-sage-800)' }}
39+
>
40+
<div className="mx-auto max-w-content px-6 lg:px-12 pt-20 lg:pt-28 pb-12">
41+
<div className="flex flex-col lg:flex-row lg:items-end gap-8 lg:gap-12">
42+
<h2
43+
className="font-heading font-extrabold uppercase text-white leading-[0.95] tracking-tight flex-1"
44+
style={{ fontSize: 'clamp(40px, 6vw, 88px)' }}
45+
>
46+
One smart step
47+
</h2>
48+
<a
49+
href="#cta"
50+
className="inline-flex self-start lg:self-end items-center gap-2 pl-5 pr-2 py-2.5 rounded-pill bg-[#e6dfd4] text-sage-900 text-button hover:bg-white transition-colors duration-fast"
51+
>
52+
Get Started
53+
<span
54+
aria-hidden
55+
className="inline-flex items-center justify-center w-6 h-6 rounded-pill bg-sage-900 text-white text-[11px]"
56+
>
57+
58+
</span>
59+
</a>
60+
</div>
61+
62+
<div className="mt-12 h-px bg-sage-600/70" />
63+
64+
<div className="mt-12 grid gap-12 lg:grid-cols-12">
65+
<div className="lg:col-span-5">
66+
<div className="font-heading font-extrabold uppercase tracking-[0.06em] text-white text-[22px]">
67+
Stack Up
68+
</div>
69+
<p className="mt-4 text-sage-200 max-w-sm leading-relaxed">
70+
IT 직군 멀티모달 AI 면접 시뮬레이터. GitHub 레포와 이력서를 분석해
71+
개인 맞춤 면접과 음성·비언어적 피드백을 제공합니다.
72+
</p>
73+
</div>
74+
75+
<nav
76+
aria-label="Footer"
77+
className="lg:col-span-7 grid grid-cols-2 sm:grid-cols-3 gap-8"
78+
>
79+
{columns.map((col) => (
80+
<div key={col.title}>
81+
<div className="text-caption font-mono uppercase tracking-[0.22em] text-sage-300">
82+
{col.title}
83+
</div>
84+
<ul className="mt-4 space-y-3">
85+
{col.links.map((l) => (
86+
<li key={l.label}>
87+
<a
88+
href={l.href}
89+
className="text-white/90 hover:text-white transition-colors duration-fast"
90+
>
91+
{l.label}
92+
</a>
93+
</li>
94+
))}
95+
</ul>
96+
</div>
97+
))}
98+
</nav>
99+
</div>
100+
101+
<div className="mt-16 pt-8 border-t border-sage-600/60 flex flex-col sm:flex-row gap-3 sm:items-center sm:justify-between">
102+
<div className="text-caption font-mono text-sage-300">
103+
© 2026 StackUp · CNU 종합설계. All rights reserved.
104+
</div>
105+
<ul className="flex gap-6 text-caption text-sage-300">
106+
<li><a href="#" className="hover:text-white transition-colors duration-fast">Privacy</a></li>
107+
<li><a href="#" className="hover:text-white transition-colors duration-fast">Terms</a></li>
108+
</ul>
109+
</div>
110+
</div>
111+
</footer>
112+
)
113+
}

0 commit comments

Comments
 (0)