Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
93f7444
[style]๋งˆ์ดํŽ˜์ด์ง€ ์Šฌ๋ผ์ด๋“œ ํผ๋ธ”๋ฆฌ์‹ฑ
mtm-git1018 Oct 12, 2025
1f5bfb9
Merge remote-tracking branch 'origin/dev' into style/mainPage-2#122
mtm-git1018 Oct 12, 2025
7ae1bee
[style] ๋ฉ”์ธํŽ˜์ด์ง€ ์Šฌ๋ผ์ด๋“œ
mtm-git1018 Oct 12, 2025
5475d83
[docs]provider๋ฌธ์„œ ์ •๋ฆฌ
mtm-git1018 Oct 12, 2025
8a4a77e
Merge remote-tracking branch 'origin/dev' into style/mainPage-2#122
mtm-git1018 Oct 12, 2025
1e89a30
[style] ๋ฉ”์ธํŽ˜์ด์ง€ ์Šคํƒ€์ผ ๊ฐ„๊ฒฉ์กฐ์ •
mtm-git1018 Oct 12, 2025
6558c51
Merge remote-tracking branch 'origin/dev' into style/mainPage-2#122
mtm-git1018 Oct 12, 2025
eed64d1
[feat]์•Œ๋ฆผ ์„ค์ •์ฒ˜๋ฆฌ
mtm-git1018 Oct 13, 2025
2a00ee7
[stype]๋ฉ”์ธํŽ˜์ด์ง€ ๋ฐ˜์‘ํ˜•
mtm-git1018 Oct 13, 2025
14840b2
[feat] ๋‚˜๋งŒ์˜ ๋ฐ” ์‚ญ์ œ ๋™๊ธฐํ™”
mtm-git1018 Oct 13, 2025
3d72ff9
[feat] ๋งˆ์ดํŽ˜์ด์ง€ ์ „์ฒด์‚ญ์ œ
mtm-git1018 Oct 13, 2025
1fb5afb
Merge remote-tracking branch 'origin/dev' into style/mainPage-2#122
mtm-git1018 Oct 13, 2025
08f42a9
Merge remote-tracking branch 'origin/dev' into style/mainPage-2#122
mtm-git1018 Oct 13, 2025
a789f65
[style] ๋ฉ”์ธํŽ˜์ด์ง€ ๋ฐ˜์‘ํ˜•
mtm-git1018 Oct 13, 2025
e5fe975
[feat] ๋งˆ์ดํŽ˜์ด์ง€ ํ‘ธ์‹œ ์˜ค๋ฅ˜ ์ˆ˜์ •
mtm-git1018 Oct 13, 2025
e3f6b64
[docs]๋ฏธ ์‚ฌ์šฉ ํŒŒ์ผ ์‚ญ์ œ
mtm-git1018 Oct 13, 2025
e693077
[style] ๋ฉ”์ธ3d์ด๋ฏธ์ง€ ์Šคํฌ๋กค ์‹คํ—˜div์ œ๊ฑฐ
mtm-git1018 Oct 13, 2025
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
3 changes: 2 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import KaKaoScript from './api/kakao/KaKaoScript';
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
import Provider from '@/shared/api/Provider';

import ClientInitHook from '@/domains/login/components/ClientInitHook';
import Provider from '@/shared/provider/Provider';

export const metadata: Metadata = {
title: { default: 'SSOUL', template: 'SSOUL | %s' },
Expand Down
9 changes: 8 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import Landing from '@/domains/shared/components/3d/Landing';

import MainSlide from '@/domains/main/components/mainSlide/components/MainSlide';

export default function Home() {
return <Landing />;
return (
<div className="page-layout max-w-full">
<Landing />
<MainSlide />
</div>
);
}
3 changes: 2 additions & 1 deletion src/app/recipe/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getApi } from '@/app/api/config/appConfig';
import DetailMain from '@/domains/recipe/details/DetailMain';
import DetailMain from '@/domains/recipe/components/details/DetailMain';

import StarBg from '@/domains/shared/components/star-bg/StarBg';
import { Metadata } from 'next';

Expand Down
187 changes: 187 additions & 0 deletions src/domains/main/components/mainSlide/components/MainSlide.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
'use client';
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import MainSlideAbv from './MainSlideAbv';

import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import MobileSlide from './mobile/MobileSlide';
import MainSlideIntro from './MainSlideIntro';
import MainSlideTest from './MainSlideTest';
import MainSlideCommunity from './MainSlideCommunity';

gsap.registerPlugin(ScrollTrigger);

function MainSlide() {
const root = useRef<HTMLDivElement>(null);
const [isMobile, setIsMobile] = useState(false);
const [mounted, setMounted] = useState(false);
const cleanupFnRef = useRef<(() => void) | null>(null);
const resizeTimeoutRef = useRef<NodeJS.Timeout | null>(null);

// ์ดˆ๊ธฐ ๋งˆ์šดํŠธ
useEffect(() => {
setIsMobile(window.innerWidth < 1024);
setMounted(true);

const handleResize = () => {
// ๋””๋ฐ”์šด์Šค: resize ์ด๋ฒคํŠธ๋ฅผ 200ms ์ง€์—ฐ
if (resizeTimeoutRef.current) {
clearTimeout(resizeTimeoutRef.current);
}

resizeTimeoutRef.current = setTimeout(() => {
const newIsMobile = window.innerWidth < 1024;

// ๋ชจ๋ฐ”์ผ โ†” ๋ฐ์Šคํฌํƒ‘ ์ „ํ™˜ ์‹œ์—๋งŒ cleanup ์‹คํ–‰
if (newIsMobile !== isMobile) {
// GSAP์„ ๋จผ์ € ์™„์ „ํžˆ ์ •๋ฆฌ
if (cleanupFnRef.current) {
cleanupFnRef.current();
cleanupFnRef.current = null;
}

// ์ƒํƒœ ์—…๋ฐ์ดํŠธ
setIsMobile(newIsMobile);
}
}, 200);
};

window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
if (resizeTimeoutRef.current) {
clearTimeout(resizeTimeoutRef.current);
}
if (cleanupFnRef.current) {
cleanupFnRef.current();
}
};
}, [isMobile]);

// GSAP ์ดˆ๊ธฐํ™” - ๋ฐ์Šคํฌํƒ‘์—์„œ๋งŒ
useLayoutEffect(() => {
if (!mounted) return;
if (isMobile) return;
if (!root.current) return;

const el = root.current;
const stage = el.querySelector('.stage') as HTMLElement;
if (!stage) return;

// ์•ฝ๊ฐ„์˜ ์ง€์—ฐ์„ ์ค˜์„œ DOM์ด ์•ˆ์ •ํ™”๋˜๋„๋ก
const timer = setTimeout(() => {
if (!root.current) return;

const ctx = gsap.context(() => {
const panels = Array.from(el.querySelectorAll<HTMLElement>('.panel'));
const tl = gsap.timeline({ paused: true, defaults: { ease: 'none' } });

panels.forEach((panel, i) => {
const c = panel.querySelector<HTMLElement>('.slide-content');
if (!c) return;
const stageW = () => stage.clientWidth;
const contentW = () => c.getBoundingClientRect().width;

gsap.set(c, { x: stageW() });

tl.to(
c,
{
x: () => stageW() - contentW(),
duration: 1,
immediateRender: false,
onStart: () => c.classList.remove('invisible'),
},
i
);
});

ScrollTrigger.create({
trigger: el,
start: 'top top',
end: `+=${panels.length * 100}%`,
pin: true,
scrub: true,
animation: tl,
invalidateOnRefresh: true,
});

ScrollTrigger.refresh();
}, root);

// cleanup ํ•จ์ˆ˜๋ฅผ ref์— ์ €์žฅ
cleanupFnRef.current = () => {
// ScrollTrigger๋ฅผ ๋จผ์ € ์™„์ „ํžˆ ์ œ๊ฑฐ
const allTriggers = ScrollTrigger.getAll();
allTriggers.forEach((st) => {
if (st.trigger === el || el.contains(st.trigger as Node)) {
st.kill(true);
}
});

// GSAP context revert
try {
ctx.revert();
} catch (e) {
// ๋ฌด์‹œ
}

// ํ˜น์‹œ ๋‚จ์•„์žˆ๋Š” pin-spacer ์ˆ˜๋™ ์ œ๊ฑฐ
const pinSpacers = document.querySelectorAll('.pin-spacer');
pinSpacers.forEach((spacer) => {
if (spacer.contains(el) || el.contains(spacer)) {
try {
const child = spacer.querySelector('section');
if (child && spacer.parentElement) {
spacer.parentElement.appendChild(child);
}
spacer.remove();
} catch (e) {
// ๋ฌด์‹œ
}
}
});
};
}, 50);

return () => {
clearTimeout(timer);
if (cleanupFnRef.current) {
cleanupFnRef.current();
cleanupFnRef.current = null;
}
};
}, [isMobile, mounted]);

// SSR ๋ฐฉ์ง€
if (!mounted) {
return null;
}

return (
<>
{isMobile ? (
<MobileSlide key="mobile" />
) : (
<section key="desktop" ref={root} className="h-screen">
<div className="stage relative w-full h-full overflow-hidden">
<div className="panel absolute inset-0">
<MainSlideIntro />
</div>
<div className="panel absolute inset-0">
<MainSlideTest />
</div>
<div className="panel absolute inset-0">
<MainSlideCommunity />
</div>
<div className="panel absolute inset-0">
<MainSlideAbv />
</div>
</div>
</section>
)}
</>
);
}

export default MainSlide;
72 changes: 72 additions & 0 deletions src/domains/main/components/mainSlide/components/MainSlideAbv.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import Ssury1 from '@/shared/assets/ssury/ssury_level1.webp';
import Ssury2 from '@/shared/assets/ssury/ssury_level2.webp';
import Ssury3 from '@/shared/assets/ssury/ssury_level3.webp';
import Ssury4 from '@/shared/assets/ssury/ssury_level4.webp';
import Ssury5 from '@/shared/assets/ssury/ssury_level5.webp';
import Ssury6 from '@/shared/assets/ssury/ssury_level6.webp';
import MainSsuryDrunk from './MainSsuryDrunk';

function MainSlideAbv() {
const SSURY_DRUNK = [
{
id: 1,
src: Ssury1,
abv: 5,
},
{
id: 2,
src: Ssury2,
abv: 11,
},
{
id: 3,
src: Ssury3,
abv: 26,
},
{
id: 4,
src: Ssury4,
abv: 46,
},
{
id: 5,
src: Ssury5,
abv: 66,
},
{
id: 6,
src: Ssury6,
abv: 86,
},
];

return (
<div className="slide-content w-1/2 h-full bg-[#84739e] rounded-tl-[30px] rounded-bl-[30px] p-15 flex flex-col justify-center">
<div className="flex flex-col gap-15">
<span className="font-black text-[32px]">3</span>
<div className="flex flex-col gap-5">
<h2 className="text-5xl font-black text-shadow-[0_4px_6px_rgb(255_255_255_/0.25)]">
๋‚ด ์•Œ์ฝœ๋„์ˆ˜ UP
</h2>
<p className="text-xl xl:text-2xl font-normal leading-[1.5]">
5๋„ ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๋Š” ๋‚ด ์•Œ์ฝœ๋„์ˆ˜ <br />๊ธ€ ์ž‘์„ฑ,๋Œ“๊ธ€,์ข‹์•„์š” / ํ‚ต์œผ๋กœ ์•Œ์ฝœ๋„์ˆ˜ UP! <br />
์•Œ์ฝœ๋„์ˆ˜์— ๋”ฐ๋ผ ๋ณ€ํ•˜๋Š” ์‘ค๋ฆฌ(SSURY)๋ฅผ ๋ณด๋Š” ์žฌ๋ฏธ๋„ ์žˆ์–ด์š”.
</p>
</div>
<div className=" h-[190px] flex flex-col gap-2">
<ul className="flex gap-[5%] xl:gap-[10%]">
{SSURY_DRUNK.map(({ id, src, abv }) => (
<li key={id}>
<MainSsuryDrunk src={src} abv={abv} />
</li>
))}
</ul>
<div className="w-full h-3 border border-gray rounded-full relative">
<span className="absolute top-0 left-0 h-full rounded-full bg-gradient-to-r from-[#FFCA8D] to-[#FA2424] w-1/2"></span>
</div>
</div>
</div>
</div>
);
}
export default MainSlideAbv;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
function MainSlideCommunity() {
return (
<div className="slide-content w-3/5 h-full p-15 bg-[#77688d] rounded-tl-[30px] rounded-bl-[30px] flex flex-col justify-center">
<div className="flex flex-col justify-center">
<div className="flex flex-col gap-15">
<span className="text-[32px] font-black">2</span>
<div className="flex flex-col gap-5">
<h2 className="text-5xl text-secondary font-black text-shadow-[0_4px_6px_rgb(255_255_255_/0.25)]">
์ˆ ์ˆ  ์ฆ๊ธฐ๋Š”, ์ปค๋ฎค๋‹ˆํ‹ฐ
</h2>
<p className="text-xl xl:text-2xl text-secondary font-normal leading-[1.5]">
์นตํ…Œ์ผ์— ๋Œ€ํ•ด ๋ฌผ์–ด๋ณผ ๊ณณ์ด ์—†์–ด ๋ชฉ์ด ๋งˆ๋ฅธ ๋‹น์‹ ! <br />
์ดˆ๋ณด์ž๋ถ€ํ„ฐ ์• ํ˜ธ๊ฐ€๊นŒ์ง€, Ssoul์—์„œ๋Š” ๋ˆ„๊ตฌ๋‚˜ ์นตํ…Œ์ผ ์ด์•ผ๊ธฐ๋ฅผ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ์–ด์š”.
<br />
ํšŒ์›๋“ค๊ณผ ์†Œํ†ตํ•˜๋ฉด ๋‚ด ์นตํ…Œ์ผ ์†œ์”จ๋ฅผ ๋ฝ๋‚ด๋ณด์„ธ์š”.
</p>
</div>
</div>
<span className="h-[250px]"></span>
</div>
</div>
);
}
export default MainSlideCommunity;
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Keep from '@/shared/assets/icons/keep_36.svg';
import Image from 'next/image';

interface Props {
id: number;
src: string;
cocktailName: string;
}

function MainSlideDummyCard({ src, cocktailName }: Props) {
return (
<div className="relative flex flex-col w-full min-w-[150px] rounded-2xl overflow-hidden bg-white shadow-[0_0_12px_rgba(255,255,255,0.4)]">
<div className="relative w-full h-[100px]">
<Image src={src} fill className="object-cover" alt="" sizes="100px" priority />
</div>

<div className="p-3 flex flex-col gap-1 text-center">
<strong className="text-black text-lg">{cocktailName}</strong>
<span className="text-gray-500 text-sm">+ ์ƒ์„ธ๋ณด๊ธฐ</span>
</div>
<Keep className="absolute top-2 right-2" fill="transparent" />
</div>
);
}
export default MainSlideDummyCard;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import background from '@/shared/assets/images/main_slide.webp';
import Image from 'next/image';

function MainSlideIntro() {
return (
<div className="relative w-full p-12 h-full">
<Image src={background} alt="" fill className="-z-1" />
<div className="flex flex-col gap-8">
<h2 className="text-3xl lg:text-5xl font-bold leading-[1.5] text-secondary text-shadow-[0_4px_6px_rgb(255_255_255_/0.25)]">
์นตํ…Œ์ผ <br /> ๋ˆ„๊ตฌ๋‚˜ ์‰ฝ๊ฒŒ ์ฆ๊ธธ ์ˆ˜ ์žˆ์–ด์š”
</h2>
<p className="text-2xl font-normal">
SSOUL์˜ ์žฌ๋ฐŒ๊ณ  ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ๋“ค๋กœ ๋” ์นœ๊ทผํ•˜๊ฒŒ ์ ‘ํ•ด๋ณด์„ธ์š”
</p>
</div>
</div>
);
}
export default MainSlideIntro;
Loading