Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -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 <DesignSystemDemo />
const path =
typeof window !== 'undefined' ? window.location.pathname : '/'

if (path.startsWith('/design-system')) {
return (
<Suspense fallback={null}>
<DesignSystemDemo />
</Suspense>
)
}

return <HomePage />
}
61 changes: 58 additions & 3 deletions frontend/src/app/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
* - html/body ๊ธฐ๋ณธ
* - ์ ‘๊ทผ์„ฑ ๊ธฐ๋ณธ (focus-visible, prefers-reduced-motion)
*
* Google Fonts (Fira Sans Extra Condensed, Geist, Geist Mono, Inter) ๋Š”
* `index.html` ์˜ `<link>` ๋กœ ๋กœ๋“œ (์„ฑ๋Šฅ์ƒ CSS @import url ๋ณด๋‹ค ๊ถŒ์žฅ).
* ํฐํŠธ๋Š” `index.html` ์˜ `<link>` ๋กœ ๋กœ๋“œ.
* - Pretendard Variable (ํ•œยท์˜ ๋ณธ๋ฌธ/์„œ๋ธŒํ—ค๋”ฉ)
* - Bricolage Grotesque (๋””์Šคํ”Œ๋ ˆ์ด ํ—ค๋”ฉ)
* - Caveat (์Šคํฌ๋ฆฝํŠธ ๊ฐ•์กฐ)
* - Geist Mono (์ฝ”๋“œ/๋ชจ๋…ธ)
*/

@layer base {
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
}
}
12 changes: 7 additions & 5 deletions frontend/src/app/styles/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions frontend/src/pages/Home/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './ui/HomePage'
18 changes: 18 additions & 0 deletions frontend/src/pages/Home/ui/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="min-h-svh bg-bg text-fg">
{/* ๊ธ€๋กœ๋ฒŒ ๋ ˆ์ด์•„์›ƒ์€ ์ถ”ํ›„ ๋ถ„๋ฆฌ ์˜ˆ์ •*/}
<SiteNav />
<main>
{/* TODO: ๋‹ค์Œ PR์—์„œ ์œ„์ ฏ๋“ค ์ถ”๊ฐ€ ์˜ˆ์ • */}
</main>
<SiteFooter />
</div>
)
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { Component, type ErrorInfo, type ReactNode } from 'react';

interface Props {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[๋‹จ์ˆœ ์งˆ๋ฌธ] ๋””๋ ‰ํ† ๋ฆฌ๋ช…์ด ์—ฌ๊ธฐ์„œ๋Š” AsyncBoundary ๋„ค์š”.
๋‹ค๋ฅธ ๊ณณ์—์„œ๋Š” site-footer ์ฒ˜๋Ÿผ kebab-case ์ธ๋ฐ, ์—ฌ๊ธฐ์„œ๋Š” PascalCase๋กœ ์„ค์ •ํ•œ ์ด์œ ๊ฐ€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์˜ค ํ™•์ธํ•˜์ง€ ๋ชปํ–ˆ๋Š”๋ฐ, ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ํ•˜๋‚˜๋กœ ํ†ต์ผํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์ˆ˜์ •ํ• ๊ฒŒ์š”.

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<Props, State> {
constructor(props: Props) {
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
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 };
}

Expand Down
1 change: 1 addition & 0 deletions frontend/src/widgets/site-footer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { SiteFooter } from './ui/SiteFooter'
113 changes: 113 additions & 0 deletions frontend/src/widgets/site-footer/ui/SiteFooter.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<footer
id="footer"
className="relative text-white"
style={{ background: 'var(--color-sage-800)' }}
>
<div className="mx-auto max-w-content px-6 lg:px-12 pt-20 lg:pt-28 pb-12">
<div className="flex flex-col lg:flex-row lg:items-end gap-8 lg:gap-12">
<h2
className="font-heading font-extrabold uppercase text-white leading-[0.95] tracking-tight flex-1"
style={{ fontSize: 'clamp(40px, 6vw, 88px)' }}
>
One smart step
</h2>
<a
href="#cta"
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"
>
Get Started
<span
aria-hidden
className="inline-flex items-center justify-center w-6 h-6 rounded-pill bg-sage-900 text-white text-[11px]"
>
โ†’
</span>
</a>
</div>

<div className="mt-12 h-px bg-sage-600/70" />

<div className="mt-12 grid gap-12 lg:grid-cols-12">
<div className="lg:col-span-5">
<div className="font-heading font-extrabold uppercase tracking-[0.06em] text-white text-[22px]">
Stack Up
</div>
<p className="mt-4 text-sage-200 max-w-sm leading-relaxed">
IT ์ง๊ตฐ ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ AI ๋ฉด์ ‘ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ. GitHub ๋ ˆํฌ์™€ ์ด๋ ฅ์„œ๋ฅผ ๋ถ„์„ํ•ด

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์ด๋Ÿฐ ํ…์ŠคํŠธ ํ•˜๋“œ์ฝ”๋”ฉ์€ ์ง€๊ธˆ์ฒ˜๋Ÿผ ์ฒ˜๋ฆฌํ•˜๋Š”๊ฒŒ ์ข‹์„๊นŒ์š”?
์•„๋‹ˆ๋ฉด ๋”ฐ๋กœ ๋บ€ ๋‹ค์Œ ๊ฑฐ๊ธฐ์— ๋ชฐ์•„๋„ฃ๋Š”๊ฒŒ ์ข‹์„๊นŒ์š”?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์–ด๋”˜๊ฐ€์— ์ฃผ์„์„ ๋‹ฌ์•„๋‘๊ธฐ๋„ ํ–ˆ๋Š”๋ฐ์š”, widgets์˜ ๋‹จ์ˆœ ๋ทฐ ์ปดํฌ๋„ŒํŠธ์˜ ๊ฒฝ์šฐ์—” ์„ธ๊ทธ๋จผํŠธ๋ฅผ ๋ถ„๋ฆฌํ•˜๊ธฐ ๋ณด๋‹จ ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ์— ๋‘๋Š”๊ฒƒ์ด ๊ฐ€๋…์„ฑ๋„ ์ข‹๊ณ  ๊ด€๋ฆฌ๋„ ํŽธํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.
features์ฒ˜๋Ÿผ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ๋“ค์–ด๊ฐ€๊ฑฐ๋‚˜ ๋ณต์žก๋„๊ฐ€ ์ฆ๊ฐ€ํ•˜๋ฉด ๋ถ„๋ฆฌํ•˜๋Š”๊ฒƒ์ด ์ข‹๋‹ค๊ณ  ์ƒ๊ฐํ•ด์š”. ์˜๊ฒฌ ๋‚˜๋ˆ ๋ณด๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋„ค์š”.

๊ฐœ์ธ ๋งž์ถค ๋ฉด์ ‘๊ณผ ์Œ์„ฑยท๋น„์–ธ์–ด์  ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
</p>
</div>

<nav
aria-label="Footer"
className="lg:col-span-7 grid grid-cols-2 sm:grid-cols-3 gap-8"
>
{columns.map((col) => (
<div key={col.title}>
<div className="text-caption font-mono uppercase tracking-[0.22em] text-sage-300">
{col.title}
</div>
<ul className="mt-4 space-y-3">
{col.links.map((l) => (
<li key={l.label}>
<a
href={l.href}
className="text-white/90 hover:text-white transition-colors duration-fast"
>
{l.label}
</a>
</li>
))}
</ul>
</div>
))}
</nav>
</div>

<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">
<div className="text-caption font-mono text-sage-300">
ยฉ 2026 StackUp ยท CNU ์ข…ํ•ฉ์„ค๊ณ„. All rights reserved.
</div>
<ul className="flex gap-6 text-caption text-sage-300">
<li><a href="#" className="hover:text-white transition-colors duration-fast">Privacy</a></li>
<li><a href="#" className="hover:text-white transition-colors duration-fast">Terms</a></li>
</ul>
</div>
</div>
</footer>
)
}
1 change: 1 addition & 0 deletions frontend/src/widgets/site-nav/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { SiteNav } from './ui/SiteNav'
66 changes: 66 additions & 0 deletions frontend/src/widgets/site-nav/ui/SiteNav.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<header
className={[
'sticky top-0 w-full transition-colors duration-normal ease-standard',
scrolled
? 'bg-bg/85 backdrop-blur-md border-b border-border'
: 'bg-transparent border-b border-transparent',
].join(' ')}
style={{ zIndex: 'var(--z-sticky)' }}
>
<div className="mx-auto max-w-content px-6 lg:px-12 h-16 flex items-center justify-between">
<a
href="#top"
className="font-heading font-extrabold tracking-[0.04em] text-sage-900 text-[15px] uppercase"
>
Stack Up
</a>

<nav aria-label="Primary" className="hidden md:flex items-center gap-1">
{items.map((it) => (
<a
key={it.href}
href={it.href}
className="px-3 py-2 text-button text-fg-strong/80 hover:text-fg-strong transition-colors duration-fast"
>
{it.label}
</a>
))}
</nav>

<div className="flex items-center">
<a
href="#cta"
className="inline-flex items-center gap-2 pl-5 pr-2 py-2 rounded-pill bg-[#e6dfd4] text-sage-900 text-button hover:bg-[#dcd4c6] transition-colors duration-fast"
>
Get Started
<span
aria-hidden
className="inline-flex items-center justify-center w-6 h-6 rounded-pill bg-sage-900 text-white text-[11px]"
>
โ†’
</span>
</a>
</div>
</div>
</header>
)
}