diff --git a/public/pngs/clan-background.png b/public/pngs/clan-background.png
new file mode 100644
index 00000000..7271f6dd
Binary files /dev/null and b/public/pngs/clan-background.png differ
diff --git a/public/pngs/slider-battlegrounds.png b/public/pngs/slider-battlegrounds.png
new file mode 100644
index 00000000..61708198
Binary files /dev/null and b/public/pngs/slider-battlegrounds.png differ
diff --git a/public/pngs/slider-lol.png b/public/pngs/slider-lol.png
new file mode 100644
index 00000000..6dae9170
Binary files /dev/null and b/public/pngs/slider-lol.png differ
diff --git a/public/pngs/slider-minecraft.png b/public/pngs/slider-minecraft.png
new file mode 100644
index 00000000..2ffba787
Binary files /dev/null and b/public/pngs/slider-minecraft.png differ
diff --git a/public/pngs/slider-overwatch.png b/public/pngs/slider-overwatch.png
new file mode 100644
index 00000000..29933773
Binary files /dev/null and b/public/pngs/slider-overwatch.png differ
diff --git a/public/pngs/slider-stroke-active.png b/public/pngs/slider-stroke-active.png
new file mode 100644
index 00000000..8274e2bb
Binary files /dev/null and b/public/pngs/slider-stroke-active.png differ
diff --git a/public/pngs/slider-stroke-default.png b/public/pngs/slider-stroke-default.png
new file mode 100644
index 00000000..001b2f60
Binary files /dev/null and b/public/pngs/slider-stroke-default.png differ
diff --git a/src/components/commons/Tag/index.tsx b/src/components/commons/Tag/index.tsx
index 040c2236..e754a5d0 100644
--- a/src/components/commons/Tag/index.tsx
+++ b/src/components/commons/Tag/index.tsx
@@ -2,14 +2,14 @@ import classNames from 'classnames/bind';
import { formatPostTypeToKR } from '@/utils';
-import { ReservedPostTypesEN } from '@/types';
+import { PostTypesEN } from '@/types';
import styles from './Tag.module.scss';
const cx = classNames.bind(styles);
type TagProps = {
- postType: ReservedPostTypesEN | string;
+ postType: PostTypesEN | string;
};
const Tag = ({ postType }: TagProps) => {
diff --git a/src/components/commons/buttons/SliderButton.module.scss b/src/components/commons/buttons/SliderButton.module.scss
new file mode 100644
index 00000000..5047a527
--- /dev/null
+++ b/src/components/commons/buttons/SliderButton.module.scss
@@ -0,0 +1,15 @@
+%arrow-btn-base {
+ @include flexbox;
+
+ width: 4rem;
+ height: 4rem;
+
+ background: $opacity-white-10;
+ backdrop-filter: $slider-button-blur;
+ border: 0.1rem solid $opacity-white-20;
+ border-radius: 0.4rem;
+}
+
+.clan-slider-btn {
+ @extend %arrow-btn-base;
+}
diff --git a/src/components/commons/buttons/SliderButton.tsx b/src/components/commons/buttons/SliderButton.tsx
new file mode 100644
index 00000000..6219205e
--- /dev/null
+++ b/src/components/commons/buttons/SliderButton.tsx
@@ -0,0 +1,28 @@
+import Image from 'next/image';
+
+import classNames from 'classnames/bind';
+
+import { SVGS } from '@/constants';
+
+import styles from './SliderButton.module.scss';
+
+const cx = classNames.bind(styles);
+
+type SliderButtonProps = {
+ type: 'left' | 'right';
+ onClick: () => void;
+};
+
+const SliderButton = ({ type, onClick }: SliderButtonProps) => {
+ const { active } = type === 'left' ? SVGS.arrow.left : SVGS.arrow.right;
+
+ return (
+
+
+
+ );
+};
+
+export default SliderButton;
diff --git a/src/components/landing/ClanCard.tsx/ClanCard.module.scss b/src/components/landing/ClanCard.tsx/ClanCard.module.scss
new file mode 100644
index 00000000..9d5645d1
--- /dev/null
+++ b/src/components/landing/ClanCard.tsx/ClanCard.module.scss
@@ -0,0 +1,66 @@
+.slider-banner-item {
+ &-content {
+ @include responsive(PC) {
+ &:hover {
+ background-image: url('/pngs/slider-stroke-active.png');
+ box-shadow: $clan-card-shadow;
+ }
+
+ &:hover .slider-banner-item-info-title {
+ color: $white;
+ }
+ }
+
+ @include responsive(T) {
+ background-image: url('/pngs/slider-stroke-active.png');
+ }
+
+ cursor: pointer;
+
+ position: relative;
+
+ display: block;
+
+ width: 100%;
+ height: 100%;
+
+ background: url('/pngs/slider-stroke-default.png') no-repeat center/cover;
+
+ transition: $base-transition;
+ }
+
+ &-tag {
+ position: absolute;
+ top: 1.2rem;
+ left: 1.2rem;
+ }
+
+ &-info {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+
+ overflow: hidden;
+
+ width: 100%;
+ padding: 2.4rem;
+
+ &-title {
+ @include responsive(T) {
+ color: $white;
+ }
+
+ @include text-style(24, $gray30, bold);
+
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ transition: $base-transition;
+ }
+
+ &-createdAt {
+ @include flexbox(start, center, 0.4rem);
+ @include text-style(14, $gray10);
+ }
+ }
+}
diff --git a/src/components/landing/ClanCard.tsx/index.tsx b/src/components/landing/ClanCard.tsx/index.tsx
new file mode 100644
index 00000000..eda2660f
--- /dev/null
+++ b/src/components/landing/ClanCard.tsx/index.tsx
@@ -0,0 +1,41 @@
+import Image from 'next/image';
+import Link from 'next/link';
+
+import classNames from 'classnames/bind';
+
+import { SVGS } from '@/constants';
+import { getFormatDate } from '@/utils';
+
+import Tag from '@/components/commons/Tag';
+
+import styles from './ClanCard.module.scss';
+
+const cx = classNames.bind(styles);
+
+const { url, alt } = SVGS.calendar.active;
+
+export type ClanCardProps = {
+ id: number;
+ gameName: string;
+ clanTitle: string;
+ createdAt: string;
+};
+
+const ClanCard = ({ id, gameName, clanTitle, createdAt }: ClanCardProps) => {
+ return (
+
+
+
+
+
+
{clanTitle}
+
+
+ {getFormatDate(createdAt)}
+
+
+
+ );
+};
+
+export default ClanCard;
diff --git a/src/components/landing/ClanRecruitment/ClanRecruitment.module.scss b/src/components/landing/ClanRecruitment/ClanRecruitment.module.scss
new file mode 100644
index 00000000..6a1c2de7
--- /dev/null
+++ b/src/components/landing/ClanRecruitment/ClanRecruitment.module.scss
@@ -0,0 +1,159 @@
+$slider-item-margin: 2.4rem;
+$slider-banner-tablet: 389.5rem;
+$slider-banner-mobile: 387.9rem;
+
+.clan {
+ @include responsive(T) {
+ height: 80rem;
+ }
+
+ width: 100%;
+ height: 100vh;
+ background: url('/pngs/clan-background.png') no-repeat center/cover;
+
+ .container {
+ @include responsive(T) {
+ max-width: 100%;
+ padding-top: 10.2rem;
+ }
+
+ @include responsive(M) {
+ max-width: 100%;
+ padding-top: 8.8rem;
+ }
+
+ max-width: 120rem;
+ height: 100%;
+ margin: 0 auto;
+ padding-top: 14.4rem;
+ }
+
+ &-header {
+ @include column-flexbox($gap: 1.6rem);
+
+ @include responsive(M) {
+ margin-bottom: 3.2rem;
+ }
+
+ margin-bottom: 4.8rem;
+
+ &-title {
+ @include text-style-quantico(40, $purple);
+
+ @include responsive(M) {
+ font-size: 3.2rem;
+ }
+ }
+
+ &-description {
+ @include text-style(24, $gray30);
+
+ @include responsive(M) {
+ font-size: 1.6rem;
+ line-height: 2.4rem;
+ }
+
+ max-width: 64rem;
+ text-align: center;
+
+ &-highlight {
+ color: $white;
+ }
+ }
+ }
+
+ &-slider {
+ position: relative;
+
+ &-btn-prev,
+ &-btn-next {
+ @include pos-center-y;
+
+ z-index: $slider-button-level;
+ }
+
+ &-btn-prev {
+ left: -2rem;
+ }
+
+ &-btn-next {
+ right: -2rem;
+ }
+
+ .slider-banner {
+ @include responsive(T) {
+ @include no-scrollbar;
+
+ overflow-x: scroll;
+ padding-left: 4rem;
+ }
+
+ @include responsive(M) {
+ padding-left: 1.5rem;
+ }
+
+ scroll-behavior: smooth;
+
+ overflow: hidden;
+
+ width: 100%;
+ height: 100%;
+ padding-top: 1.6rem;
+
+ &-list {
+ @include responsive(T) {
+ width: $slider-banner-tablet;
+ }
+
+ @include responsive(M) {
+ width: $slider-banner-mobile;
+ }
+
+ width: calc(400vw + ($slider-item-margin * 11));
+ }
+
+ &-item {
+ @include responsive(T) {
+ width: 30rem;
+ height: 36rem;
+ margin-right: 4rem;
+ }
+
+ @include responsive(T) {
+ margin-right: 1.5rem;
+ }
+
+ @include responsive(PC) {
+ &:hover {
+ transform: translateY(-1.6rem);
+ }
+ }
+
+ display: inline-block;
+ width: 38.4rem;
+ height: 46rem;
+ transition: $base-transition;
+
+ &.league-of-legends {
+ background: url('/pngs/slider-lol.png') no-repeat center/cover;
+ }
+
+ &.battlegrounds {
+ background: url('/pngs/slider-battlegrounds.png') no-repeat center/cover;
+ }
+
+ &.overwatch-2 {
+ background: url('/pngs/slider-overwatch.png') no-repeat center/cover;
+ }
+
+ &.minecraft {
+ background: url('/pngs/slider-minecraft.png') no-repeat center/cover;
+ }
+
+ &:not(:last-child) {
+ margin-right: 2.4rem;
+ }
+ }
+ }
+ }
+}
diff --git a/src/components/landing/ClanRecruitment/index.tsx b/src/components/landing/ClanRecruitment/index.tsx
new file mode 100644
index 00000000..b2cb0afb
--- /dev/null
+++ b/src/components/landing/ClanRecruitment/index.tsx
@@ -0,0 +1,105 @@
+import { useRef, useState } from 'react';
+
+import { useQuery } from '@tanstack/react-query';
+import classNames from 'classnames/bind';
+
+import { getActivities } from '@/apis/queryFunctions';
+import { GAME_NAME_KR_TO_PATH_NAME } from '@/constants';
+import { splitTitleByDelimiter } from '@/utils';
+
+import SliderButton from '@/components/commons/buttons/SliderButton';
+import ClanCard from '@/components/landing/ClanCard.tsx';
+
+import styles from './ClanRecruitment.module.scss';
+
+const cx = classNames.bind(styles);
+
+const SCROLL_SLIDER_WIDTH = 1224;
+const MAX_DISPLAYED_CLAN_CARDS = 3;
+const POST_CATEGORY_CLAN = 2;
+
+const ClanRecruitment = () => {
+ const { data: lol } = useQuery({ queryKey: ['activities', '스포츠'], queryFn: getActivities });
+ const { data: battlegrounds } = useQuery({ queryKey: ['activities', '투어'], queryFn: getActivities });
+ const { data: overwatch } = useQuery({ queryKey: ['activities', '관광'], queryFn: getActivities });
+ const { data: minecraft } = useQuery({ queryKey: ['activities', '웰빙'], queryFn: getActivities });
+
+ const gamePostList = [lol, battlegrounds, overwatch, minecraft];
+
+ const filteredGameCategory = gamePostList.map((gamePosts) => {
+ if (!gamePosts) return [];
+ return gamePosts.activities.filter((postType) => postType.price === POST_CATEGORY_CLAN);
+ });
+
+ const filterSliderClanList = [];
+
+ for (let i = 0; i < MAX_DISPLAYED_CLAN_CARDS; i++) {
+ for (const list of filteredGameCategory) {
+ if (list && list.length > i) {
+ filterSliderClanList.push(list[i]);
+ }
+ }
+ }
+
+ const sliderRef = useRef(null);
+
+ const [currentSliderIndex, setCurrentSliderIndex] = useState(0);
+
+ const handleNextClick = () => {
+ if (sliderRef.current && currentSliderIndex < MAX_DISPLAYED_CLAN_CARDS) {
+ sliderRef.current.scrollLeft += SCROLL_SLIDER_WIDTH;
+ setCurrentSliderIndex((prev) => prev + 1);
+ }
+ };
+
+ const handlePrevClick = () => {
+ if (sliderRef.current && currentSliderIndex > 0) {
+ sliderRef.current.scrollLeft -= SCROLL_SLIDER_WIDTH;
+ setCurrentSliderIndex((prev) => prev - 1);
+ }
+ };
+
+ return (
+
+ 클랜 모집
+
+
+
+
+
+
+
+
+
+
+ {filterSliderClanList?.map(({ id, title, createdAt }) => {
+ const { category, title: clanTitle } = splitTitleByDelimiter(title);
+ const gameName = GAME_NAME_KR_TO_PATH_NAME[category];
+
+ return (
+ -
+
+
+ );
+ })}
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ClanRecruitment;
diff --git a/src/pages/landing/index.tsx b/src/pages/landing/index.tsx
index f8145c9b..4312014c 100644
--- a/src/pages/landing/index.tsx
+++ b/src/pages/landing/index.tsx
@@ -1,5 +1,26 @@
+import { dehydrate } from '@tanstack/react-query';
+
+import { getActivities } from '@/apis/queryFunctions';
+import { queryClient } from '@/utils';
+
import Layout from '@/components/layout/Layout';
+import { Category } from '@/types';
+
+export const getServerSideProps = () => {
+ queryClient.prefetchQuery({ queryKey: ['activities', ''], queryFn: getActivities });
+
+ return {
+ props: {
+ dehydratedState: dehydrate(queryClient),
+ },
+ };
+};
+
+export type LandingPageProps = {
+ category: Category;
+};
+
const LandingPage = () => {
return LandingPage
;
};
diff --git a/src/styles/variables/_blur.scss b/src/styles/variables/_blur.scss
index 0421052f..e62650b2 100644
--- a/src/styles/variables/_blur.scss
+++ b/src/styles/variables/_blur.scss
@@ -6,4 +6,5 @@ $alarm-blur: blur(80px);
$drawer-blur: blur(60px);
$input-blur: blur(20px);
$confirm-schedule-blur: blur(32px);
+$slider-button-blur: blur(32px);
$landing-blur: blur(12px);
diff --git a/src/styles/variables/_box-shadow.scss b/src/styles/variables/_box-shadow.scss
index ba7ba837..61d25d92 100644
--- a/src/styles/variables/_box-shadow.scss
+++ b/src/styles/variables/_box-shadow.scss
@@ -3,3 +3,4 @@ $dropdown-shadow: 0 4px 12px 0 rgb(0 0 0 / 40%);
$popup-shadow: 0 12px 24px 0 rgb(0 0 0 / 32%);
$schedule-item-shadow: 0 6px 12px 0 rgb(0 0 0 / 30%);
$icon-shadow: 0 0 4px 0 rgb(0 0 0 / 16%);
+$clan-card-shadow: 0 8px 12px 0 rgb(0 0 0 / 60%);
diff --git a/src/styles/variables/_levels.scss b/src/styles/variables/_levels.scss
index f95998ae..358590c4 100644
--- a/src/styles/variables/_levels.scss
+++ b/src/styles/variables/_levels.scss
@@ -1,5 +1,6 @@
$glitch-2-level: -2;
$glitch-1-level: -1;
+$slider-button-level: 1;
$chatbot-level: 4;
$kebab-level: 5;
$dropdown-level: 10;