Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
59f2ff4
feat: add service landing page image assets from Framer
youngminss Mar 3, 2026
9b292c6
feat: move app start page to /app route
youngminss Mar 3, 2026
813e208
feat: scaffold ServiceLandingPage component
youngminss Mar 3, 2026
a545dc4
feat: add sticky navbar to service landing page
youngminss Mar 3, 2026
168550c
feat: add hero section to service landing page
youngminss Mar 3, 2026
a005fd6
fix: apply stagger animation to hero text and type useRef correctly
youngminss Mar 3, 2026
f5dd79d
feat: add feature sections 1-5 to service landing page
youngminss Mar 3, 2026
b0ef2e6
feat: add CTA section and footer to service landing page
youngminss Mar 3, 2026
2ad1218
fix: resolve build errors in service landing page
youngminss Mar 3, 2026
828a60c
fix: constrain Feature3 image widths and clip Feature4 overflow
youngminss Mar 3, 2026
211b3a8
fix: move FeatureText to top in Feature1, Feature2, Feature5 sections
youngminss Mar 3, 2026
5c94e4d
chore: apply pnpm format and remove reference screenshots
youngminss Mar 3, 2026
93584fb
refactor: replace router.push with Next.js Link for SEO
youngminss Mar 3, 2026
b03a7cc
chore: formatting
youngminss Mar 3, 2026
9f8ee6c
feat: redesign service landing page based on Figma design
youngminss Mar 5, 2026
b9364b1
chore: 불필요한 코드 제거
youngminss Mar 11, 2026
180cbe5
style: 랜딩 페이지 footer 영역 수정
youngminss Mar 11, 2026
8d13fd3
style: 랜딩 페이지 CTA 영역 수정
youngminss Mar 11, 2026
031f8f5
style: 랜딩 페이지 section 5 영역 수정
youngminss Mar 11, 2026
e0d8bce
style: 랜딩 페이지 section 4 영역 수정
youngminss Mar 11, 2026
2a11bb3
style: 랜딩 페이지 section 3 영역 수정
youngminss Mar 11, 2026
e92d6b9
style: 랜딩 페이지 section 1,2 영역 수정
youngminss Mar 11, 2026
f803e30
style: 랜딩 페이지 pain point 영역 수정
youngminss Mar 11, 2026
10cea7b
style: 랜딩 페이지 hero 영역 수정
youngminss Mar 11, 2026
99ddc5b
chore: formatting
youngminss Mar 11, 2026
4e0765b
refactor: 서비스 랜딩 페이지 컴포넌트 분리 및 서버 컴포넌트 전환
youngminss Mar 11, 2026
d56588d
refactor: SpeechBubble 컴포넌트 개선
youngminss Mar 11, 2026
d3f329f
fix: 존재하지 않는 DISTANCE_RANGE_LABEL export 제거
youngminss Mar 11, 2026
a2c5dbc
chore: lint 에러 수정 (import/export 정렬)
youngminss Mar 11, 2026
ac90e94
feat: 푸터 이용약관·개인정보 처리방침 외부 링크 적용
youngminss Mar 13, 2026
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
5 changes: 5 additions & 0 deletions app/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { LandingPage } from "#/pageComponents/landing";

export default function AppHomePage() {
return <LandingPage />;
}
2 changes: 1 addition & 1 deletion app/gathering/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default function GatheringCreatePage() {

const handleBackward = () => {
if (isFirstStep) {
router.push("/");
router.push("/app");
} else {
back();
}
Expand Down
4 changes: 2 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { LandingPage } from "#/pageComponents/landing";
import { ServiceLandingPage } from "#/pageComponents/serviceLanding";

export default function Home() {
return <LandingPage />;
return <ServiceLandingPage />;
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 2 additions & 3 deletions src/constants/gathering/opinion/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
export {
CATEGORY,
type Category,
CATEGORY_LABEL,
CATEGORY_LIST,
CATEGORY_VALUES,
type Category,
} from "./category";
export {
DISTANCE_OPTIONS,
DISTANCE_RANGE,
DISTANCE_RANGE_LABEL,
DISTANCE_RANGE_WALKING_MINUTES,
type DistanceRange,
} from "./distance";
export { OPINION_STEP_ORDER, OPINION_TOTAL_STEPS } from "./funnel";
export { MOCK_MEETING_DATA } from "./meeting";
export { RANK, RANK_LABEL, RANK_LIST, type RankKey } from "./rank";
export { RecommendationResultStatus } from "./recommendationResultStatus";
export { REGION, REGION_LABEL, REGION_OPTIONS, type Region } from "./region";
export { REGION, type Region, REGION_LABEL, REGION_OPTIONS } from "./region";
export { TIME_SLOT_LABEL } from "./timeSlot";
export { UI_TEXT } from "./ui-text";
5 changes: 3 additions & 2 deletions src/hooks/gathering/useOpinionForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export function useOpinionForm() {
const router = useRouter();
const { accessKey } = useParams<{ accessKey: string }>();

const { refetch: refetchRecommendResult } = useGetRecommendResult(accessKey);
const { refetch: refetchRecommendResult } =
useGetRecommendResult(accessKey);
const { mutateAsync: createParticipant, isPending } =
useCreateParticipant();

Expand All @@ -41,7 +42,7 @@ export function useOpinionForm() {
const handleClickShowResultButton = async () => {
await refetchRecommendResult();
router.push(`/gathering/${accessKey}/opinion/result`);
}
};

const handleSubmit = methods.handleSubmit(async (data) => {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const ToastLinkButton = ({ label, onClick }: ToastLinkButtonProps) => {
className={twJoin(
"ygi:ml-auto ygi:flex ygi:items-center ygi:justify-center ygi:gap-0.5",
"ygi:cursor-pointer ygi:body-14-sb ygi:text-palette-primary-500",
"ygi:text-nowrap"
"ygi:text-nowrap",
)}
onClick={onClick}
>
Expand Down
36 changes: 36 additions & 0 deletions src/pageComponents/serviceLanding/CtaSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"use client";

import { motion } from "motion/react";
import Link from "next/link";

import { useScrollReveal } from "./useScrollReveal";

export const CtaSection = () => {
const { ref, isInView } = useScrollReveal();

return (
<section
ref={ref}
className="ygi:bg-palette-gray-800 ygi:px-6 ygi:py-15.5"
>
<motion.div
initial={{ opacity: 0, y: 40 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease: "easeOut" }}
className="ygi:mx-auto ygi:flex ygi:flex-col ygi:items-center ygi:gap-10 ygi:text-center"
>
<p className="ygi:heading-22-bd ygi:whitespace-pre-line ygi:text-text-inverse">
{"지금 바로 "}
<span className="ygi:text-button-secondary">요기잇</span>
{"으로\n맛집을 추천 받아요"}
</p>
<Link
href="/gathering/create"
className="ygi:flex ygi:cursor-pointer ygi:items-center ygi:justify-center ygi:rounded-full ygi:bg-white ygi:px-6 ygi:py-3 ygi:heading-18-bd ygi:text-text-primary ygi:transition-colors ygi:hover:bg-white/90"
>
바로 시작하기
</Link>
</motion.div>
</section>
);
};
56 changes: 56 additions & 0 deletions src/pageComponents/serviceLanding/Feature1Section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"use client";

import { motion } from "motion/react";
import Image from "next/image";

import { FeatureText } from "./FeatureText";
import { useScrollReveal } from "./useScrollReveal";

export const Feature1Section = () => {
const { ref, isInView } = useScrollReveal();

return (
<section
ref={ref}
className="ygi:overflow-hidden ygi:bg-palette-gray-100 ygi:px-6 ygi:pt-13.5"
>
<div className="ygi:mx-auto ygi:flex ygi:flex-col ygi:items-center">
<FeatureText
caption="모임 링크 만들기"
headline="우리 모임 정보 입력 한번에"
isDark={false}
isInView={isInView}
/>
{/* overflow-hidden으로 하단 overflow 초기 상태 클리핑 */}
<div className="ygi:relative ygi:-bottom-14">
<motion.div
initial={{ opacity: 1, y: 72, scale: 0.88 }}
animate={isInView ? { opacity: 1, y: 0, scale: 1 } : {}}
transition={{
duration: 0.7,
ease: [0.25, 0.46, 0.45, 0.94],
}}
className="ygi:flex ygi:flex-row ygi:gap-3"
>
{(
[
"feature-1-screen-a",
"feature-1-screen-b",
] as const
).map((name) => (
<Image
key={name}
src={`/images/service-landing/${name}.png`}
alt="모임 생성 화면"
width={148}
height={241}
className="ygi:rounded-[10px] ygi:shadow-lg"
priority
/>
))}
</motion.div>
</div>
</div>
</section>
);
};
65 changes: 65 additions & 0 deletions src/pageComponents/serviceLanding/Feature2Section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"use client";

import { motion } from "motion/react";
import Image from "next/image";

import { FeatureText } from "./FeatureText";
import { useScrollReveal } from "./useScrollReveal";

export const Feature2Section = () => {
const { ref, isInView } = useScrollReveal();

return (
<section
ref={ref}
className="ygi:overflow-hidden ygi:px-6 ygi:pt-13.5"
style={{ backgroundColor: "#d5dae2" }}
>
<div className="ygi:mx-auto ygi:flex ygi:flex-col ygi:items-center">
<FeatureText
caption="먹기 싫은거, 먹고 싶은거"
headline={
<>
{"나의 "}
<span className="ygi:text-button-secondary">
의견 입력
</span>
{" 한번에"}
</>
}
isDark={false}
isInView={isInView}
/>
{/* overflow-hidden으로 하단 overflow 초기 상태 클리핑 */}
<div className="ygi:relative ygi:-bottom-14">
<motion.div
initial={{ opacity: 1, y: 72, scale: 0.88 }}
animate={isInView ? { opacity: 1, y: 0, scale: 1 } : {}}
transition={{
duration: 0.7,
ease: [0.25, 0.46, 0.45, 0.94],
}}
className="ygi:flex ygi:flex-row ygi:gap-3"
>
{(
[
"feature-2-screen-a",
"feature-2-screen-b",
] as const
).map((name) => (
<Image
key={name}
src={`/images/service-landing/${name}.png`}
alt="의견 입력 화면"
width={148}
height={321}
className="ygi:rounded-[10px] ygi:shadow-lg"
priority
/>
))}
</motion.div>
</div>
</div>
</section>
);
};
59 changes: 59 additions & 0 deletions src/pageComponents/serviceLanding/Feature3Section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"use client";

import { motion } from "motion/react";
import Image from "next/image";

import { FeatureText } from "./FeatureText";
import { useScrollReveal } from "./useScrollReveal";

const feature3Screens = [
{ name: "feature-3-screen-a", direction: -1, height: 151 },
{ name: "feature-3-screen-b", direction: 1, height: 146 },
{ name: "feature-3-screen-c", direction: -1, height: 139 },
];

export const Feature3Section = () => {
const { ref, isInView } = useScrollReveal();

return (
<section
ref={ref}
className="ygi:overflow-hidden ygi:bg-palette-gray-100 ygi:px-6 ygi:py-13.5"
>
<div className="ygi:mx-auto ygi:flex ygi:flex-col ygi:items-center ygi:gap-10">
<FeatureText
caption="친구들은 뭘 먹고 싶어 했을까?"
headline="투표 결과로 확인해요"
isDark={false}
isInView={isInView}
/>
<div className="ygi:flex ygi:w-full ygi:flex-col ygi:items-center ygi:gap-2.5">
{feature3Screens.map(
({ name, direction, height }, index) => (
<motion.div
key={name}
initial={{ opacity: 0, x: direction * 60 }}
animate={isInView ? { opacity: 1, x: 0 } : {}}
transition={{
duration: 0.65,
ease: "easeOut",
delay: 0.3 + index * 0.2,
}}
className="ygi:w-full ygi:max-w-57.5"
>
<Image
src={`/images/service-landing/${name}.png`}
alt={`투표 결과 화면 ${index + 1}`}
width={654}
height={height}
className="ygi:w-full ygi:shadow-lg"
priority
/>
</motion.div>
),
)}
</div>
</div>
</section>
);
};
73 changes: 73 additions & 0 deletions src/pageComponents/serviceLanding/Feature4Section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"use client";

import { motion } from "motion/react";
import Image from "next/image";

import { FeatureText } from "./FeatureText";
import { useScrollReveal } from "./useScrollReveal";

export const Feature4Section = () => {
const { ref, isInView } = useScrollReveal();

return (
<section
ref={ref}
className="ygi:overflow-hidden ygi:bg-palette-gray-700 ygi:px-6 ygi:pt-13.5"
>
<div className="ygi:mx-auto ygi:flex ygi:max-w-300 ygi:flex-col ygi:items-center">
<FeatureText
caption="투표 결과에 따라"
headline="모임에 맞게 맛집 추천해요"
isDark={true}
isInView={isInView}
/>
<div className="ygi:relative ygi:-bottom-8 ygi:flex ygi:flex-col ygi:items-center ygi:gap-4">
{/* 투표 필터 요약 카드 - 아래에서 위로 fade + scale */}
<motion.div
initial={{ opacity: 0, y: 40, scale: 0.5 }}
animate={isInView ? { opacity: 1, y: 0, scale: 1 } : {}}
transition={{
duration: 0.45,
ease: [0.25, 0.46, 0.45, 0.94],
delay: 0.2,
}}
className="ygi:mx-auto ygi:w-full ygi:max-w-55.25"
>
<Image
src="/images/service-landing/feature-4-tooltip.png"
alt="투표 결과 요약"
width={654}
height={247}
className="ygi:w-full"
priority
/>
</motion.div>
{/* 맛집 추천 리스트 - bottom overflow + scale 등장 */}
<div className="ygi:relative">
<motion.div
initial={{ opacity: 1, y: 80, scale: 0.88 }}
animate={
isInView ? { opacity: 1, y: 0, scale: 1 } : {}
}
transition={{
duration: 0.75,
ease: [0.25, 0.46, 0.45, 0.94],
delay: 0.4,
}}
className="ygi:w-full ygi:max-w-65.5"
>
<Image
src="/images/service-landing/feature-4-screen.png"
alt="맛집 추천 화면"
width={654}
height={752}
className="ygi:w-full ygi:shadow-2xl"
priority
/>
</motion.div>
</div>
</div>
</div>
</section>
);
};
Loading
Loading