diff --git a/public/favicon.ico b/public/favicon.ico index 291c2b94..039d3772 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/slogan.png b/public/slogan.png deleted file mode 100644 index db4a070d..00000000 Binary files a/public/slogan.png and /dev/null differ diff --git a/public/testlogo.png b/public/testlogo.png deleted file mode 100644 index 91c29fa7..00000000 Binary files a/public/testlogo.png and /dev/null differ diff --git a/public/text_logo.png b/public/text_logo.png deleted file mode 100644 index 47529422..00000000 Binary files a/public/text_logo.png and /dev/null differ diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index 13146e7f..be853149 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -60,6 +60,20 @@ const bounceAnimation = { }, }; +const backgroundVariants = { + initial: { + backgroundColor: "#1a1a1a", + }, + animate: { + backgroundColor: "#2a2a2a", + transition: { + duration: 20, + repeat: Infinity, + repeatType: "reverse" as const, + }, + }, +}; + export default function LandingPage() { const isLargeScreen = useMediaQuery({ minWidth: 641 }); const [currentSlide, setCurrentSlide] = useState(0); @@ -71,12 +85,12 @@ export default function LandingPage() { useEffect(() => { setIsLoaded(true); lenisRef.current = new Lenis({ - duration: 0.8, // 1.2에서 0.8로 변경 - easing: (t: number) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), + duration: 1.2, + easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), smoothWheel: true, wheelMultiplier: 2, - touchMultiplier: 1.2, - infinite: false, + touchMultiplier: 2, + infinite: true, }); function raf(time: number) { @@ -87,7 +101,7 @@ export default function LandingPage() { requestAnimationFrame(raf); let lastScrollTime = 0; - const scrollThreshold = 500; // ms + const scrollThreshold = 150; lenisRef.current.on("scroll", ({ progress }: { progress: number }) => { const currentTime = Date.now(); @@ -95,28 +109,30 @@ export default function LandingPage() { if (!isScrollingRef.current) { const totalSlides = slides.length; - const newSlideIndex = Math.min(slides.length - 1, Math.max(0, Math.round(progress * (totalSlides - 1)))); + const progressPerSlide = 1 / (totalSlides - 1); + const currentProgress = progress / progressPerSlide; + let newSlideIndex = Math.round(currentProgress); + + if (progress >= 0.99) { + newSlideIndex = 0; + setTimeout(() => { + lenisRef.current?.scrollTo(0, { + duration: 1.2, + easing: (t) => t * (2 - t), + }); + }, 100); + } if (newSlideIndex !== currentSlide) { setCurrentSlide(newSlideIndex); isScrollingRef.current = true; - const targetScroll = - (newSlideIndex / (totalSlides - 1)) * - (isLargeScreen ? containerRef.current!.scrollHeight : containerRef.current!.scrollWidth); - - lenisRef.current?.scrollTo(targetScroll, { - immediate: false, - duration: 600, // 800에서 600으로 변경 - easing: (t: number) => t * (2 - t), - }); - - lastScrollTime = currentTime; setTimeout(() => { isScrollingRef.current = false; - }, 600); // 800에서 600으로 변경 + }, 200); } } + lastScrollTime = currentTime; }); document.documentElement.style.scrollbarWidth = "none"; @@ -143,18 +159,29 @@ export default function LandingPage() { {isLoaded && ( -
+
+
+
+ + {currentSlide === 0 ? ( ) : currentSlide === 4 || currentSlide === 5 ? ( {slides[currentSlide].blackAreaTitle} @@ -221,28 +245,27 @@ export default function LandingPage() { className="mb-0 max-w-[600px] whitespace-pre-wrap text-center text-sm text-gray-200 max-[640px]:mt-1 max-[640px]:px-4 md:text-xl" initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} - transition={{ delay: 0.5, duration: 0.6 }} // Update 2 + transition={{ delay: 0.5, duration: 0.6 }} > {slides[currentSlide].blackAreaContent} ) : ( -
- Brand Logo -
+ Brand Logo
)}
@@ -267,10 +290,7 @@ export default function LandingPage() { {currentSlide > 0 && ( - - {slides[currentSlide].title} - - -
+ - {slides[currentSlide].title -
-
- - {slides[currentSlide].content} - + + {slides[currentSlide].title} + + +
+ {slides[currentSlide].title +
+
+ + {slides[currentSlide].content} + +
+
)} -
+ {currentSlide > 0 && ( -
{slides.slice(1).map((_, index) => ( -
{ + if (!isScrollingRef.current) { + const targetScroll = + ((index + 1) / (slides.length - 1)) * (containerRef.current?.scrollHeight || 0); + lenisRef.current?.scrollTo(targetScroll, { + duration: 1.2, + easing: (t) => t * (2 - t), + }); + } + }} /> ))} -
+ )} )} diff --git a/src/app/(pages)/(workform)/addform/section/RecruitContentSection.tsx b/src/app/(pages)/(workform)/addform/section/RecruitContentSection.tsx index 48322007..1c6f360f 100644 --- a/src/app/(pages)/(workform)/addform/section/RecruitContentSection.tsx +++ b/src/app/(pages)/(workform)/addform/section/RecruitContentSection.tsx @@ -1,7 +1,6 @@ "use client"; import BaseInput from "@/app/components/input/text/BaseInput"; import BaseTextArea from "@/app/components/input/textarea/BaseTextArea"; -import ImageInput from "@/app/components/input/file/ImageInput/ImageInput"; import DatePickerInput from "@/app/components/input/dateTimeDaypicker/DatePickerInput"; import { cn } from "@/lib/tailwindUtil"; import { useFormContext } from "react-hook-form"; @@ -12,6 +11,7 @@ import useUploadImages from "@/hooks/queries/user/me/useImageUpload"; import { formatToLocaleDate } from "@/utils/formatters"; import DotLoadingSpinner from "@/app/components/loading-spinner/DotLoadingSpinner"; import { isDirty } from "zod"; +import ImageInput from "@/app/components/input/file/ImageInput/ImageInput"; // 워크폼 만들기 - 사장님 - 1-모집내용 diff --git a/src/app/components/input/file/ImageInput/ImageInput.tsx b/src/app/components/input/file/ImageInput/ImageInput.tsx new file mode 100644 index 00000000..aa45e1a2 --- /dev/null +++ b/src/app/components/input/file/ImageInput/ImageInput.tsx @@ -0,0 +1,131 @@ +"use client"; +import { HiUpload } from "react-icons/hi"; +import { forwardRef, useState, useEffect } from "react"; +import { toast } from "react-hot-toast"; +import { DragDropContext, Droppable, Draggable, DropResult } from "react-beautiful-dnd"; +import PreviewItem from "./PreviewItem"; +import { cn } from "@/lib/tailwindUtil"; +import { ImageInputType } from "@/types/addform"; +import React from "react"; + +interface ImageInputProps { + name: string; + onChange?: (files: File[] | string[]) => void; + onDelete?: (id: string) => void; + initialImageList: ImageInputType[]; +} + +const ImageInput = forwardRef((props, ref) => { + const [imageList, setImageList] = useState(props.initialImageList || []); + + useEffect(() => { + if (props.initialImageList?.length > 0) { + setImageList(props.initialImageList); + } + }, [props.initialImageList]); + + const handleFileChange = (selectedFiles: FileList | null) => { + if (selectedFiles) { + const filesArray = Array.from(selectedFiles); // FileList를 배열로 변환 + const validFiles = filesArray.filter((file) => file.type.startsWith("image/")); + + if (validFiles.length + imageList.length > 3) { + toast.error("이미지는 최대 3개까지 업로드할 수 있습니다."); + return; + } + + if (validFiles.length === 0) { + toast.error("이미지 파일만 업로드할 수 있습니다."); + return; + } + + // 선택된 파일을 상위 컴포넌트로 전달 + props.onChange?.(validFiles); + } + }; + + const handleDeleteImage = (targetUrl: string) => { + const newImageList = imageList.filter((image) => image.url !== targetUrl); + setImageList(newImageList); + props.onDelete?.(targetUrl); + }; + + const handleOpenFileSelector = () => { + if (typeof ref === "function") { + const fileInput = document.querySelector(`input[name="${props.name}"]`); + if (fileInput) { + (fileInput as HTMLInputElement).click(); + } + } else if (ref && "current" in ref) { + ref.current?.click(); + } + }; + + const colorStyle = { + bgColor: "bg-background-200", + borderColor: "border-[0.5px] border-transparent", + hoverColor: "hover:border-grayscale-200 hover:bg-background-300", + innerHoverColor: "hover:bg-background-300", + }; + + const handleDragEnd = (result: DropResult) => { + if (!result.destination) return; + + const items = Array.from(imageList); + const [reorderedItem] = items.splice(result.source.index, 1); + items.splice(result.destination.index, 0, reorderedItem); + + setImageList(items); + // 상위 컴포넌트에 변경된 이미지 URL 배열 전달 + props.onChange?.(items.map((item) => item.url)); + }; + + return ( + +
+
+ handleFileChange(e.target.files)} + className="hidden" + multiple + /> +
+ +
+
+ + {(provided) => ( +
+ {imageList.map((image, index) => ( + + {(provided) => ( +
+ +
+ )} +
+ ))} + {provided.placeholder} +
+ )} +
+
+
+ ); +}); + +ImageInput.displayName = "ImageInput"; + +export default ImageInput;