diff --git a/frontend/src/features/auth/index.ts b/frontend/src/features/auth/index.ts
index 25e8975f..2f08b46f 100644
--- a/frontend/src/features/auth/index.ts
+++ b/frontend/src/features/auth/index.ts
@@ -1,5 +1,6 @@
export { AuthProvider } from './model/AuthProvider'
export { useAuth } from './model/useAuth'
+export { useGetStartedTarget } from './model/useGetStartedTarget'
export { useLogout } from './model/useLogout'
export type { AuthContextValue, AuthStatus } from './model/AuthContext'
export { GithubLoginButton } from './ui/GithubLoginButton'
diff --git a/frontend/src/features/auth/model/useGetStartedTarget.ts b/frontend/src/features/auth/model/useGetStartedTarget.ts
new file mode 100644
index 00000000..24c42ed0
--- /dev/null
+++ b/frontend/src/features/auth/model/useGetStartedTarget.ts
@@ -0,0 +1,9 @@
+import { useAuth } from './useAuth'
+
+// "Get Started" CTA 목적지. 헤더(SiteNav)와 동일한 분기:
+// 로그인 상태면 워크스페이스, 아니면 로그인 페이지.
+// (loading 중에도 로그인으로 — 로그인 페이지가 인증 사용자를 워크스페이스로 보낸다.)
+export function useGetStartedTarget(): string {
+ const { status } = useAuth()
+ return status === 'authenticated' ? '/workspace' : '/login'
+}
diff --git a/frontend/src/features/interview/ui/live/InterviewStage.tsx b/frontend/src/features/interview/ui/live/InterviewStage.tsx
index 3492b799..317a1cdd 100644
--- a/frontend/src/features/interview/ui/live/InterviewStage.tsx
+++ b/frontend/src/features/interview/ui/live/InterviewStage.tsx
@@ -73,9 +73,11 @@ export function InterviewStage({
className="absolute inset-0 bg-cover bg-center"
style={{ backgroundImage: `url(${BG})` }}
/>
+ {/* 배경 사진이 보이도록 옅은 스크림만. 텍스트 가독성은 헤더·질문 카드·
+ 컴포저가 각자 배경(반투명+blur / solid)으로 확보한다. */}
{/* 진행도 바 */}
diff --git a/frontend/src/widgets/home-cta/ui/HomeCta.tsx b/frontend/src/widgets/home-cta/ui/HomeCta.tsx
index 953fca4f..226afca2 100644
--- a/frontend/src/widgets/home-cta/ui/HomeCta.tsx
+++ b/frontend/src/widgets/home-cta/ui/HomeCta.tsx
@@ -1,4 +1,8 @@
+import { Link } from 'react-router-dom'
+import { useGetStartedTarget } from '@/features/auth'
+
export function HomeCta() {
+ const getStartedTo = useGetStartedTarget()
return (
diff --git a/frontend/src/widgets/home-hero/ui/ScreenContent.tsx b/frontend/src/widgets/home-hero/ui/ScreenContent.tsx
index 0d50d1fb..b2d37f94 100644
--- a/frontend/src/widgets/home-hero/ui/ScreenContent.tsx
+++ b/frontend/src/widgets/home-hero/ui/ScreenContent.tsx
@@ -1,3 +1,5 @@
+import { Link } from 'react-router-dom'
+import { useGetStartedTarget } from '@/features/auth'
import { useTypewriter } from '@/shared/hooks'
const TYPED_TEXT = 'Stack Up'
@@ -7,6 +9,7 @@ export function ScreenContent() {
startDelayMs: 850,
stepMs: 130,
})
+ const getStartedTo = useGetStartedTarget()
return (
@@ -42,8 +45,8 @@ export function ScreenContent() {
)
diff --git a/frontend/src/widgets/home-services/ui/HomeServices.tsx b/frontend/src/widgets/home-services/ui/HomeServices.tsx
index 25b58b1a..e64274e6 100644
--- a/frontend/src/widgets/home-services/ui/HomeServices.tsx
+++ b/frontend/src/widgets/home-services/ui/HomeServices.tsx
@@ -1,11 +1,12 @@
-import { Link } from 'react-router-dom'
+import { useState } from 'react'
+import { Link, useNavigate } from 'react-router-dom'
+import { Modal } from '@/shared/ui'
type ServiceCard = {
tag: string
title: string
image: string
- to: string
-}
+} & ({ to: string } | { modal: 'role' })
const services: ServiceCard[] = [
{
@@ -18,7 +19,8 @@ const services: ServiceCard[] = [
tag: 'Free Plan',
title: '직무 기술 면접\nRole-based Interview',
image: '/second-section-backend-interview.avif',
- to: '/practice/role',
+ // 직무는 프론트/백엔드를 모달에서 고른 뒤 해당 연습으로 이동.
+ modal: 'role',
},
{
tag: 'Free Plan',
@@ -28,7 +30,61 @@ const services: ServiceCard[] = [
},
]
+const ROLE_OPTIONS = [
+ { track: 'frontend', label: '프론트엔드', desc: 'HTML·CSS·JS·React·브라우저' },
+ { track: 'backend', label: '백엔드', desc: 'OS·네트워크·DB·자바·스프링' },
+] as const
+
+const cardClass =
+ 'group relative block rounded-2xl overflow-hidden aspect-[3/4] bg-sage-800 text-left'
+
+function CardInner({ card }: { card: ServiceCard }) {
+ return (
+ <>
+
+
+
+
+ {card.tag}
+
+
+
+
+ {card.title}
+
+
+ →
+
+
+ >
+ )
+}
+
export function HomeServices() {
+ const navigate = useNavigate()
+ const [roleOpen, setRoleOpen] = useState(false)
+
+ const goRole = (track: (typeof ROLE_OPTIONS)[number]['track']) => {
+ setRoleOpen(false)
+ navigate(`/practice/${track}`)
+ }
+
return (
@@ -40,47 +96,43 @@ export function HomeServices() {
- {services.map((s) => (
-
-

-
-
-
- {s.tag}
-
+ {services.map((s) =>
+ 'to' in s ? (
+
+
+
+ ) : (
+
+ ),
+ )}
+
+
-
-
- {s.title}
-
-
- →
-
-
-
+ setRoleOpen(false)} title="직무 기술 면접">
+
+ 연습할 직무를 선택하면 해당 분야 질문이 무작위로 출제됩니다.
+
+
+ {ROLE_OPTIONS.map((opt) => (
+
))}
-
+
)
}
diff --git a/frontend/src/widgets/site-footer/ui/SiteFooter.tsx b/frontend/src/widgets/site-footer/ui/SiteFooter.tsx
index 4bf93371..8e534aa7 100644
--- a/frontend/src/widgets/site-footer/ui/SiteFooter.tsx
+++ b/frontend/src/widgets/site-footer/ui/SiteFooter.tsx
@@ -1,4 +1,5 @@
import { Link } from 'react-router-dom'
+import { useGetStartedTarget } from '@/features/auth'
// 단순 뷰 섹션 widgets 에선 굳이 나누지 않는게 좋다고 판단했습니다.
// 상수, 메세지 등 마찬가지
@@ -33,6 +34,7 @@ const columns: { title: string; links: FooterLink[] }[] = [
]
export function SiteFooter() {
+ const getStartedTo = useGetStartedTarget()
return (