Skip to content
Merged
Binary file added public/1Stars.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/2Stars.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/CocktailDrop_4x.webp
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/app/(no-layout)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function NoLayout({ children }: { children: React.ReactNode }) {
return <main className="flex flex-1">{children}</main>;
}
export default NoLayout;
9 changes: 9 additions & 0 deletions src/app/(no-layout)/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import FinalLanding from '@/domains/main/components/FinalLanding';

export default function Home() {
return (
<div className="page-layout max-w-full">
<FinalLanding />
</div>
);
}
File renamed without changes.
15 changes: 15 additions & 0 deletions src/app/(with-layout)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import ClientInitHook from '@/domains/login/components/ClientInitHook';
import FooterWrapper from '@/shared/components/footer/FooterWrapper';
import Header from '@/shared/components/header/Header';

function LayoutWithHeaderFooter({ children }: { children: React.ReactNode }) {
return (
<>
<Header />
<ClientInitHook />
<main className="flex flex-1 pt-[2.75rem] md:pt-[3.75rem]">{children}</main>
<FooterWrapper />
</>
);
}
export default LayoutWithHeaderFooter;
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
6 changes: 3 additions & 3 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ export default function RootLayout({
<html lang="ko-KR">
<body className="relative flex flex-col min-h-full-screen">
<Provider>
<Header />
<ClientInitHook />
<main className="flex flex-1 pt-[2.75rem] md:pt-[3.75rem]">{children}</main>
<FooterWrapper />

{children}

<div id="modal-root"></div>

<Toaster
position="top-center"
toastOptions={{
Expand Down
12 changes: 0 additions & 12 deletions src/app/page.tsx

This file was deleted.

104 changes: 104 additions & 0 deletions src/domains/main/cocktailDrop/CocktailDrop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
'use client';

import Image from 'next/image';
import Cocktailcup from '../../../../public/CocktailDrop_4x.webp';
import { useLayoutEffect, useRef } from 'react';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/all';

gsap.registerPlugin(ScrollTrigger);

function CocktailDrop() {
const containerRef = useRef<HTMLDivElement>(null);
const logoRef = useRef<HTMLDivElement>(null);
const line1Ref = useRef<HTMLDivElement>(null);
const line2Ref = useRef<HTMLDivElement>(null);

useLayoutEffect(() => {
const ctx = gsap.context(() => {
// 양쪽 대각선 줄 들어오기 (line1, line2)
gsap.fromTo(
[line1Ref.current, line2Ref.current],
{
x: (i) => (i === 0 ? '-100%' : '100%'),
opacity: 0,
},
{
x: '0%',
opacity: 1,
ease: 'power4.out',
duration: 1.2,
stagger: 0.2,
scrollTrigger: {
trigger: containerRef.current,
// markers: true, // ✅ 디버
start: 'top 95%',
toggleActions: 'restart none none none',
once: false,
},
}
);

// 로고 위에서 아래로 자연스럽게 등장
gsap.fromTo(
logoRef.current,
{ y: -300, opacity: 0 },
{
y: -40,
opacity: 1,
duration: 3,
ease: 'power3.out',
scrollTrigger: {
trigger: containerRef.current,
// markers: true, // ✅ 디버
start: 'top 90%',
toggleActions: 'restart none none none',
once: false,
},
}
);
ScrollTrigger.refresh();
}, containerRef);

return () => ctx.revert();
}, [containerRef]);

return (
<div
ref={containerRef}
className="relative w-full min-h-[110vh] flex flex-col justify-center items-center mt-10"
id="scroll-fixed"
>
{/* 대각선 줄 1 */}
<div
ref={line1Ref}
className="absolute top-[80px] left-[-50%] w-[200%] h-[40px] bg-secondary/80 rotate-[8deg] z-10"
/>
{/* 대각선 줄 2 */}
<div
ref={line2Ref}
className="absolute top-[140px] left-[-50%] w-[200%] h-[40px] bg-secondary rotate-[8deg] z-10"
/>

{/* 로고 */}
<div ref={logoRef} className="absolute z-20">
<Image
src="/logo.svg"
alt="로고 이미지"
width={600}
height={600}
className="rotate-[-9deg]"
/>
</div>

<div className="w-full h-90"></div>

{/* 컵 이미지 */}
<div className="z-10">
<Image src={Cocktailcup} alt="칵테일 컵" width={800} height={800} priority />
</div>
</div>
);
}

export default CocktailDrop;
27 changes: 27 additions & 0 deletions src/domains/main/components/3d/HomeBackground.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useEffect, useRef } from 'react';

function HomeBackground() {
const bgRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
const x = e.clientX / window.innerWidth;
const percentage = 6 + x * 70;
if (bgRef.current) {
bgRef.current.style.background = `linear-gradient(128deg, rgba(26, 26, 26, 0.7) ${percentage}%, rgba(42, 42, 42, 0.3) ${percentage + 10}%, rgba(60, 70, 78, 0) 100%)`;
}
};

window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);

return (
<div
ref={bgRef}
className="absolute inset-0 z-1 top-0 left-0 transition-all duration-100 will-change-auto"
/>
);
}

export default HomeBackground;
14 changes: 14 additions & 0 deletions src/domains/main/components/3d/HomeLogo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Image from 'next/image';

function HomeLogo({ isDesktop }: { isDesktop: boolean }) {
return (
<div
className="z-5 absolute md:top-8 md:left-10 md:translate-none top-13 left-1/2 -translate-x-1/2"
style={{ width: !isDesktop ? 400 : 700, height: !isDesktop ? 70 : 240 }}
>
<Image src={'/logo.svg'} alt="로고 이미지" fill priority className="object-contain" />
</div>
);
}

export default HomeLogo;
98 changes: 98 additions & 0 deletions src/domains/main/components/3d/HomeModel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
'use client';

import { Environment, OrbitControls, useGLTF } from '@react-three/drei';
import { Canvas, useFrame, useThree } from '@react-three/fiber';
import { Bloom, EffectComposer } from '@react-three/postprocessing';
import { useEffect, useRef } from 'react';
import * as THREE from 'three';

interface Props {
onLoaded: () => void;
}

function Model({ onLoaded }: Props) {
const { scene } = useGLTF('/3d/model/scene.gltf');

useEffect(() => {
if (scene) {
onLoaded(); // 모델이 로드되면 부모에게 알림
}
}, [scene]);

if (!scene) return null; // 로딩 전 대기 처리

scene.traverse((child) => {
if ((child as THREE.Mesh).isMesh) {
const mesh = child as THREE.Mesh;
const material = mesh.material as THREE.MeshPhysicalMaterial;

material.envMapIntensity = 3;
material.metalness = 1;
material.roughness = 0.3;
material.emissiveIntensity = 2;
material.clearcoat = 1;
material.clearcoatRoughness = 0.2;
material.needsUpdate = true;
material.opacity = 0.35;
material.bumpScale = 0.3;
material.thickness = 0.1;
}
});

return <primitive object={scene} scale={18} position={[0, -1.2, 0]} />;
}

function CameraAnimation() {
const { camera } = useThree();
const targetPosition = new THREE.Vector3(5, 10, 10); // 최종 위치
const startPosition = new THREE.Vector3(0, 15, 6); // 시작 위치
const progress = useRef(0);

useFrame((state, delta) => {
if (progress.current < 1) {
progress.current += delta / 5; // 3초 동안
const t = Math.min(progress.current, 1);
camera.position.lerpVectors(startPosition, targetPosition, t);
}
});

return null;
}

function HomeModel({ onLoaded }: Props) {
return (
<Canvas
className="z-10 w-full pointer-none"
camera={{ position: [10, 40, 9], fov: 26 }}
dpr={[1, 1.5]}
>
<ambientLight intensity={1} />
<pointLight position={[10, 30, 40]} intensity={1} />
<spotLight position={[0, 10, 10]} angle={0.2} penumbra={1} intensity={15} castShadow />
<directionalLight intensity={8} color={0xffffff} position={[10, 40, 100]} />
<Environment files={`/hdri/footprint_court.hdr`} background={false} />
<Model onLoaded={onLoaded} />
{/* <CameraAnimation /> */}
<OrbitControls
enablePan={false}
enableZoom={false}
enableRotate={true}
autoRotate={true}
autoRotateSpeed={-0.4}
target={[0, 0, 0]}
/>

<EffectComposer multisampling={0}>
<Bloom
intensity={0.65}
luminanceThreshold={0.9}
luminanceSmoothing={0.2}
luminanceColor={new THREE.Color(1, 1, 1)}
mipmapBlur
/>
</EffectComposer>
</Canvas>
);
}

export default HomeModel;
17 changes: 17 additions & 0 deletions src/domains/main/components/3d/HomeText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
function HomeText({ isDesktop }: { isDesktop: boolean }) {
return (
<>
{!isDesktop ? (
<p className="absolute top-32 text-sm left-1/2 -translate-x-1/2 whitespace-nowrap">
어떤 칵테일이 끌리시나요? SSoul이 쉽게 골라드릴게요.
</p>
) : (
<p className="absolute bottom-45 right-12 font-serif text-xl text-right font-normal z-20">
어떤 칵테일이 끌리시나요? SSoul이 쉽게 골라드릴게요.
</p>
)}
</>
);
}

export default HomeText;
42 changes: 42 additions & 0 deletions src/domains/main/components/3d/Landing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use client';

import HomeModel from './HomeModel';
import HomeLogo from './HomeLogo';
import HomeText from './HomeText';
import Scroll from './Scroll';
import { useEffect, useState } from 'react';
import ModelImage from './ModelImage';

interface Props {
setIsLoading: (value: boolean) => void;
isDesktop: boolean;
}

function Landing({ setIsLoading, isDesktop }: Props) {
const [modelLoaded, setModelLoaded] = useState(false);
useEffect(() => {
if (modelLoaded) setIsLoading(false);
}, [modelLoaded, setIsLoading]);
return (
<>
<div className="page-layout max-w-full">
<div className="relative w-full h-screen">
{isDesktop ? (
<HomeModel onLoaded={() => setModelLoaded(true)} />
) : (
<ModelImage onLoaded={() => setModelLoaded(true)} />
)}
{modelLoaded && (
<>
<HomeLogo isDesktop={isDesktop} />
<HomeText isDesktop={isDesktop} />
<Scroll isDesktop={isDesktop} />
</>
)}
</div>
</div>
</>
);
}

export default Landing;
14 changes: 14 additions & 0 deletions src/domains/main/components/3d/ModelImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useEffect } from 'react';

interface Props {
onLoaded: () => void;
}

function ModelImage({ onLoaded }: Props) {
useEffect(() => {
onLoaded();
});
return <div className="w-full"></div>;
}

export default ModelImage;
Loading