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
77 changes: 77 additions & 0 deletions src/assets/icons/main1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions src/assets/icons/main2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import AIImage from '@/assets/icons/ai_image.svg?react';
import HomeBanner from '@/assets/icons/home_banner.jpg';
import KakaoLogin from '@/assets/icons/kakao_login.png';
import LoginImage from '@/assets/icons/login_image.svg?react';
import MainImage1 from '@/assets/icons/main1.svg?react';
import MainImage2 from '@/assets/icons/main2.svg?react';

export {
InfoIcon,
Expand All @@ -36,4 +38,6 @@ export {
HomeBanner,
LoginImage,
KakaoLogin,
MainImage1,
MainImage2,
};
4 changes: 3 additions & 1 deletion src/component/common/Badge/Badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export default function Badge({ type, children, color, count, percent, size = 's

const label =
type === 'parkingTag' ? (
<span className="min-w-0 flex-1 truncate text-center">{children}</span>
<span className="w-[17ch] min-w-0 truncate text-center">{children}</span>
) : type === 'parkingCount' ? (
<span className="w-[3ch]">{children}</span>
) : (
<>{children}</>
);
Expand Down
18 changes: 18 additions & 0 deletions src/component/common/Footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export default function Footer() {
return (
<footer className="mt-auto">
<div className="mx-auto mb-3 w-full max-w-screen-sm text-center">
<div className="text-caption1 text-green1 mt-2">@2025 SoomTeum</div>
<div className="text-caption5 text-green-muted item-center flex justify-center">
<a
href="https://platinum-blender-439.notion.site/271c6326f64a80a39e38e2df0039bb62?pvs=74"
className="hover:underline"
>
Privacy&terms
</a>
<div className="ml-2">Contacts: [email protected]</div>
</div>
</div>
</footer>
);
}
122 changes: 122 additions & 0 deletions src/component/home/FeatureSlides.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { useEffect, useRef, useState } from 'react';
import { Image } from '@/component';

type SlideItem = {
id: string | number;
src: string;
title?: string;
desc?: string;
};

type FeatureSlidesProps = {
title?: string;
slides: SlideItem[];
};

export default function FeatureSlides({
title = 'AI 맞춤 여행지 탐색',
slides,
}: FeatureSlidesProps) {
const trackRef = useRef<HTMLDivElement>(null);
const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
const [active, setActive] = useState(0);

useEffect(() => {
const root = trackRef.current;
if (!root) return;
const items = itemRefs.current.filter(Boolean) as HTMLDivElement[];
if (!items.length) return;

const io = new IntersectionObserver(
(entries) => {
const visible = entries
.filter((e) => e.isIntersecting)
.sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0];
if (!visible) return;
const idx = items.findIndex((el) => el === visible.target);
if (idx !== -1) setActive(idx);
},
{ root, threshold: [0.6] },
);

items.forEach((el) => io.observe(el));
return () => io.disconnect();
}, [slides.length]);

const goTo = (idx: number) => {
itemRefs.current[idx]?.scrollIntoView({
behavior: 'smooth',
inline: 'start',
block: 'nearest',
});
};

const scrollByDir = (dir: 'left' | 'right') => {
const el = trackRef.current;
if (!el) return;
const amount = el.clientWidth * 0.9;
el.scrollBy({ left: dir === 'left' ? -amount : amount, behavior: 'smooth' });
};

return (
<section className="px-5 pb-12">
<h2 className="text-title1 text-green1 pb-3">{title}</h2>

<div className="relative">
<div
ref={trackRef}
className="flex touch-pan-x snap-x snap-mandatory gap-4 overflow-x-auto overscroll-x-contain scroll-smooth [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
>
{slides.map((s, idx) => (
<div
key={s.id}
ref={(el: HTMLDivElement | null) => {
itemRefs.current[idx] = el;
}}
className="w-full shrink-0 snap-center items-center justify-center"
>
<div className="flex w-full items-center justify-center rounded-2xl">
<Image
src={s.src}
alt={s.title ?? ''}
className="block max-h-[65%] max-w-[65%] object-contain"
/>
</div>
{s.title && <div className="text-body2 text-green1 pt-2">{s.title}</div>}
{s.desc && <div className="text-caption2 text-green1/70">{s.desc}</div>}
</div>
))}
</div>

{/*좌우 화살표(오버레이)*/}
<button
aria-label="이전"
onClick={() => scrollByDir('left')}
className="text-title5 text-green1 absolute top-1/2 left-1 z-10 -translate-y-1/2 rounded-full bg-white/90 px-2.5 py-1.5 shadow-sm ring-1 ring-black/5 active:scale-95"
>
</button>
<button
aria-label="다음"
onClick={() => scrollByDir('right')}
className="text-title5 text-green1 absolute top-1/2 right-1 z-10 -translate-y-1/2 rounded-full bg-white/90 px-2.5 py-1.5 shadow-sm ring-1 ring-black/5 active:scale-95"
>
</button>
</div>

{/*페이지 도트*/}
<div className="mt-3 flex items-center justify-center gap-1">
{slides.map((_, idx) => (
<button
key={idx}
aria-label={`${idx + 1}번째 슬라이드로 이동`}
aria-current={active === idx}
onClick={() => goTo(idx)}
className={`h-1 w-1 rounded-full transition ${active === idx ? 'bg-green2' : 'bg-gray1'} `}
/>
))}
</div>
</section>
);
}
51 changes: 48 additions & 3 deletions src/pages/home/Homepage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { MainImage1, MainImage2 } from '@/assets';
import HomeBanner from '@/assets/icons/home_banner.jpg';
import { Header, Sidebar, Image, Button } from '@/component';
import Footer from '@/component/common/Footer/Footer';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';

Expand All @@ -18,12 +20,12 @@ export default function HomePage() {
<div className="min-h-screen">
<Header onMenuClick={handleMenuClick} />
<Sidebar isOpen={isSidebarOpen} onClose={handleSidebarClose} position="left" />{' '}
<div className="mx-auto flex w-full max-w-[480px] flex-col pt-14">
<div className="mx-auto flex w-full flex-col pt-14">
<Image src={HomeBanner} className="h-60 w-full" />
<div className="text-caption2 text-green1 px-5 pt-1 text-right">
<div className="text-caption2 text-green1 px-5 pt-2 text-right">
*해당 사진 속 장소는 강릉입니다.
</div>
<div className="px-5 pt-4 pb-6 text-center">
<div className="px-5 pt-5 pb-38 text-center">
<div className="text-title2 text-green1">혼잡한 도시는 이제 그만</div>
<div className="text-title5 text-green1 pt-1">
지금 가장 여유롭고 한적한 여행지를 찾아드립니다.
Expand All @@ -32,6 +34,49 @@ export default function HomePage() {
바로가기
</Button>
</div>
{/*메인 카드: 관광지 탐색*/}
<div className="relative isolate px-5 pb-38 after:absolute after:top-20 after:left-1/2 after:-z-10 after:h-[180px] after:w-[170px] after:-translate-x-1/2 after:rounded-[9999px] after:bg-[radial-gradient(ellipse_at_center,_rgba(227,235,212,0.99)_0%,_rgba(227,235,212,0.9)_95%)] after:blur-2xl after:content-['']">
<div className="mx-auto w-full max-w-[200px] text-center">
<MainImage1 className="z-10 mx-auto justify-center" />
<div className="text-title2 text-green1">관광지 탐색</div>
<div className="text-body1 mt-4 text-black">
원하는 분위기, 가고 싶은 지역의
<br /> 조용한 여행지를 찾아보세요.
</div>
<div className="text-body1 mt-4 text-black">
내가 원하는 조용한 여행지,
<br /> 이제 손쉽게 찾을 수 있어요.
</div>
<Button variant="sm" className="mt-5" onClick={() => navigate('/explore/Filter')}>
바로가기
</Button>
</div>
</div>
{/*메인 카드: AI 탐색*/}
<div className="relative isolate px-5 pb-38 after:absolute after:top-20 after:left-1/2 after:-z-10 after:h-[230px] after:w-[170px] after:-translate-x-1/2 after:rounded-[9999px] after:bg-[radial-gradient(ellipse_at_center,_rgba(227,235,212,0.99)_0%,_rgba(227,235,212,0.9)_95%)] after:blur-2xl after:content-['']">
<div className="mx-auto w-full max-w-[200px] text-center">
<MainImage2 className="z-10 mx-auto justify-center" />
<div className="text-title2 text-green1">AI 맞춤 여행지 찾기</div>
<div className="text-caption4 text-green1 mt-4">1. 정보 입력하기</div>
<div className="text-body3 text-green-muted">출발지, 테마, 거리를 입력해주세요</div>
<div className="text-caption4 text-green1 mt-4">2. AI 분석 중!</div>
<div className="text-body3 text-green-muted">
입력한 정보를 바탕으로
<br />
AI가 딱 맞는 여행지를 찾고 있어요.
</div>
<div className="text-caption4 text-green1 mt-4">3. 추천 결과 확인</div>
<div className="text-body3 text-green-muted">
딱 맞는 여행지의 상세정보와 실제 후기
<br />
주차장 위치까지 한눈에 확인해보세요.
</div>
<Button variant="sm" className="mt-5" onClick={() => navigate('/explore')}>
바로가기
</Button>
</div>
</div>
<Footer />
{/*Todo: 추후 홈 메인 내용 확정 되면 구현....*/}
</div>
</div>
Expand Down