diff --git a/src/app/styles/theme.ts b/src/app/styles/theme.ts
index cfbf6cb..00d66be 100644
--- a/src/app/styles/theme.ts
+++ b/src/app/styles/theme.ts
@@ -163,6 +163,30 @@ const theme = createTheme({
fontSize: "1.4rem",
},
},
+ subtitle1: {
+ fontSize: "3.2rem",
+ fontWeight: 700,
+ lineHeight: 1.2,
+ letterSpacing: "-0.025em",
+ "@media (min-width:600px)": {
+ fontSize: "4rem",
+ },
+ "@media (min-width:960px)": {
+ fontSize: "6rem",
+ },
+ },
+ subtitle2: {
+ fontSize: "3rem",
+ fontWeight: 700,
+ lineHeight: 1.2,
+ letterSpacing: "-0.025em",
+ "@media (min-width:600px)": {
+ fontSize: "3.2rem",
+ },
+ "@media (min-width:960px)": {
+ fontSize: "3.6rem",
+ },
+ },
},
shape: {
@@ -360,15 +384,12 @@ const theme = createTheme({
MuiContainer: {
styleOverrides: {
root: {
- paddingLeft: "1.6rem",
- paddingRight: "1.6rem",
+ paddingInline: "1.6rem",
"@media (min-width:600px)": {
- paddingLeft: "2rem",
- paddingRight: "2rem",
+ paddingInline: "2rem",
},
"@media (min-width:960px)": {
- paddingLeft: "2.4rem",
- paddingRight: "2.4rem",
+ paddingInline: "2.4rem",
},
},
},
diff --git a/src/entities/projects/types/projects.ts b/src/entities/projects/types/projects.ts
index b2b10ac..d7e2584 100644
--- a/src/entities/projects/types/projects.ts
+++ b/src/entities/projects/types/projects.ts
@@ -1,7 +1,15 @@
+// 나중에 Project Owner 정보가 타입으로 들어가야할 것 같음 + expectedPeriod 타입 수정 or 포맷팅해서 DB 저장
+
+import type { UserRole } from "@shared/user/types/user";
+
+// 팀원 목록들도 타입으로 들어가야할 것 같음, 이미지도 타입으로 들어가야할 것 같음
+// 지원자들도 넣어야할 듯
export interface ProjectItemInsertReq {
userId: string; // 작성자 id
userName: string; // 작성사 이름
status: "모집중" | "모집완료";
+ userRole: UserRole;
+ avatar: string;
title: string; // 프로젝트 제목
oneLineInfo: string; // 프로젝트 한줄 소개
simpleInfo: string; // 프로젝트 간단 소개
@@ -13,6 +21,7 @@ export interface ProjectItemInsertReq {
requirements: string[]; // 지원 요구사항
preferentialTreatment: string[]; // 우대사항
positions: Positions[]; // 모집 포지션
+ applicants: string[]; // 지원자들
}
interface Positions {
@@ -20,7 +29,8 @@ interface Positions {
count: number;
experience: string; // 경력
}
-
+// 나중에 Project Owner 정보가 타입으로 들어가야할 것 같음 + expectedPeriod 타입 수정 or 포맷팅해서 DB 저장
+// 팀원 목록들도 타입으로 들어가야할 것 같음
export interface ProjectListRes extends ProjectItemInsertReq {
id: string; // firebase 문서 id
}
diff --git a/src/entities/projects/ui/projects-card/ProjectCard.tsx b/src/entities/projects/ui/projects-card/ProjectCard.tsx
new file mode 100644
index 0000000..dbb963b
--- /dev/null
+++ b/src/entities/projects/ui/projects-card/ProjectCard.tsx
@@ -0,0 +1,315 @@
+import AccessTimeIcon from "@mui/icons-material/AccessTime";
+import LocationPinIcon from "@mui/icons-material/LocationPin";
+import PeopleAltIcon from "@mui/icons-material/PeopleAlt";
+import {
+ Button,
+ Card,
+ CardContent,
+ Chip,
+ Divider,
+ Stack,
+ styled,
+ Typography,
+ Box,
+ useMediaQuery,
+ useTheme,
+} from "@mui/material";
+import type { JSX } from "react";
+import { Link } from "react-router-dom";
+
+import type { ProjectListRes } from "@entities/projects/types/projects";
+
+import DragScrollContainer from "@shared/ui/DragScrollContainer";
+import UserProfileAvatar from "@shared/user/ui/UserProfileAvatar";
+import UserProfileWithNamePosition from "@shared/user/ui/UserProfileWithNamePosition";
+
+const ProjectCard = (): JSX.Element => {
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.up("sm"));
+
+ const mock: ProjectListRes = {
+ id: "1",
+ userId: "1",
+ userName: "John Doe",
+ status: "모집중",
+ title: "Project Title",
+ userRole: "frontend",
+ avatar: "https://via.placeholder.com/150",
+ oneLineInfo: "Project One Line Info",
+ simpleInfo: "Project Simple Info",
+ techStack: [
+ "React",
+ "Node.js",
+ "MongoDB",
+ "TypeScript",
+ "JavaScript",
+ "Vue.js",
+ "Angular",
+ "Svelte",
+ "Next.js",
+ "Nuxt.js",
+ "Tailwind CSS",
+ "Bootstrap",
+ "Material UI",
+ "Chakra UI",
+ "Ant Design",
+ "Styled Components",
+ "Emotion",
+ "Tailwind CSS",
+ "Bootstrap",
+ "Material UI",
+ "Chakra UI",
+ "Ant Design",
+ "Styled Components",
+ "Emotion",
+ ],
+ teamSize: 4,
+ expectedPeriod: "1개월",
+ description: "Project Description",
+ workflow: "Project Workflow",
+ requirements: ["React", "Node.js", "MongoDB"],
+ preferentialTreatment: ["React", "Node.js", "MongoDB"],
+ positions: [
+ {
+ position: "Frontend",
+ count: 2,
+ experience: "1년 이상",
+ },
+ ],
+ applicants: ["asdfasdfsf2", "asdzxc1er", "bsdfgh12", "cbvscbatfg"],
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {mock.title}
+
+
+ {mock.oneLineInfo}
+
+
+ {mock.simpleInfo}
+
+
+
+ {isMobile ? (
+
+ ) : (
+
+ )}
+
+
+ {mock.techStack.map((stack, index) => (
+
+ ))}
+
+
+
+
+
+
+ {mock.teamSize}명
+
+
+
+
+
+ {mock.expectedPeriod}
+
+
+
+
+
+ 온라인
+
+
+
+
+
+
+
+
+ {mock.applicants.length}명 지원
+
+
+
+ 자세히 보기
+
+
+
+
+
+ );
+};
+
+export default ProjectCard;
+
+const StyledCard = styled(Card)(({ theme }) => ({
+ height: "100%",
+ maxWidth: "40rem",
+ maxHeight: "50rem",
+ width: "100%",
+ display: "flex",
+ flexDirection: "column",
+ transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
+ cursor: "pointer",
+ border: `1px solid ${theme.palette.divider}`,
+
+ "&:hover": {
+ transform: "translateY(-0.4rem)",
+ boxShadow:
+ "0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)",
+ borderColor: theme.palette.primary.light,
+ },
+
+ [theme.breakpoints.up("sm")]: {
+ maxWidth: "44rem",
+ maxHeight: "52rem",
+ "&:hover": {
+ transform: "translateY(-0.6rem)",
+ },
+ },
+
+ [theme.breakpoints.up("md")]: {
+ maxWidth: "48rem",
+ maxHeight: "54rem",
+ },
+}));
+
+const StyledCardContent = styled(CardContent)(({ theme }) => ({
+ height: "100%",
+ display: "flex",
+ flexDirection: "column",
+ gap: theme.spacing(1),
+
+ [theme.breakpoints.up("sm")]: {
+ gap: theme.spacing(2),
+ },
+}));
+
+const ProjectHeader = styled(Box)(() => ({
+ display: "flex",
+ justifyContent: "flex-start",
+ alignItems: "center",
+}));
+
+const StatusChip = styled(Chip)(({ theme }) => ({
+ fontWeight: 600,
+ letterSpacing: "0.025em",
+ backgroundColor: theme.palette.primary.main,
+ color: theme.palette.primary.contrastText,
+
+ "&:hover": {
+ backgroundColor: theme.palette.primary.dark,
+ },
+}));
+
+const ContentSection = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ gap: theme.spacing(0.8),
+}));
+
+const ProjectTitle = styled(Typography)(({ theme }) => ({
+ lineHeight: 1.3,
+ letterSpacing: "-0.015em",
+ color: theme.palette.text.primary,
+}));
+
+const OneLineInfo = styled(Typography)(() => ({
+ lineHeight: 1.4,
+ fontWeight: 600,
+}));
+
+const SimpleInfo = styled(Typography)(() => ({
+ lineHeight: 1.5,
+ overflow: "hidden",
+ display: "-webkit-box",
+ WebkitLineClamp: 2,
+ WebkitBoxOrient: "vertical",
+}));
+
+const TechChip = styled(Chip)(({ theme }) => ({
+ backgroundColor: theme.palette.background.default,
+ border: `1px solid ${theme.palette.divider}`,
+ fontWeight: 500,
+ fontSize: "1.1rem",
+ flexShrink: 0,
+ whiteSpace: "nowrap",
+
+ [theme.breakpoints.up("sm")]: {
+ fontSize: "1.2rem",
+ },
+}));
+
+const ProjectDetails = styled(Stack)(({ theme }) => ({
+ flexDirection: "row",
+ flexWrap: "wrap",
+ gap: theme.spacing(1.6),
+ marginTop: theme.spacing(0.8),
+
+ [theme.breakpoints.up("sm")]: {
+ gap: theme.spacing(2),
+ },
+}));
+
+const DetailItem = styled(Stack)(({ theme }) => ({
+ flexDirection: "row",
+ alignItems: "center",
+ gap: theme.spacing(0.6),
+}));
+
+const StyledDivider = styled(Divider)(({ theme }) => ({
+ margin: `${theme.spacing(0.8)} 0`,
+ backgroundColor: theme.palette.divider,
+}));
+
+const FooterSection = styled(Stack)(({ theme }) => ({
+ flexDirection: "row",
+ justifyContent: "space-between",
+ alignItems: "center",
+ marginTop: "auto",
+ gap: theme.spacing(1.2),
+}));
+
+const StyledLink = styled(Link)(() => ({
+ textDecoration: "none",
+ flexShrink: 0,
+}));
+
+const ActionButton = styled(Button)(({ theme }) => ({
+ fontWeight: 600,
+ letterSpacing: "0.025em",
+ borderRadius: theme.spacing(0.8),
+ boxShadow: "none",
+ transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)",
+
+ "&:hover": {
+ transform: "translateY(-0.1rem)",
+ boxShadow: "0 4px 8px -2px rgba(37, 99, 235, 0.3)",
+ },
+
+ "&:active": {
+ transform: "translateY(0)",
+ },
+}));
+
+const TextHighlight = styled("span")(({ theme }) => ({
+ color: theme.palette.primary.main,
+ fontWeight: 600,
+}));
diff --git a/src/entities/projects/ui/projects-stats/ProjectsStats.tsx b/src/entities/projects/ui/projects-stats/ProjectsStats.tsx
new file mode 100644
index 0000000..7bbdca3
--- /dev/null
+++ b/src/entities/projects/ui/projects-stats/ProjectsStats.tsx
@@ -0,0 +1,91 @@
+import EmojiEventsIcon from "@mui/icons-material/EmojiEvents";
+import PeopleAltIcon from "@mui/icons-material/PeopleAlt";
+import RocketLaunchIcon from "@mui/icons-material/RocketLaunch";
+import { Card, CardContent, Stack, styled, Typography } from "@mui/material";
+import type { JSX } from "react";
+
+const ProjectsStats = (): JSX.Element => {
+ const mock = [
+ {
+ id: "a",
+ title: "진행중인 프로젝트",
+ value: 110,
+ icon: ,
+ color: "#2563eb",
+ },
+ {
+ id: "b",
+ title: "활성 사용자",
+ value: 120,
+ icon: ,
+ color: "#16a34a",
+ },
+ {
+ id: "c",
+ title: "완성된 프로젝트",
+ value: 130,
+ icon: ,
+ color: "#eab308",
+ },
+ ];
+
+ return (
+ <>
+ {mock.map((stat) => {
+ return (
+
+
+
+
+ {stat.icon}
+
+
+ {`${stat.value}+`}
+
+
+ {stat.title}
+
+
+
+
+ );
+ })}
+ >
+ );
+};
+
+export default ProjectsStats;
+
+interface ProjectStatsIconProps {
+ color: string;
+}
+
+const ProjectStatsCard = styled(Card)(() => ({
+ flex: 1,
+}));
+
+const ProjectStatsIcon = styled("span")(({ color }) => ({
+ color: color,
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ borderRadius: "12px",
+ padding: "1.6rem",
+ backgroundColor: `${color}10`,
+}));
+
+const ProjectStatsStack = styled(Stack)(() => ({
+ display: "flex",
+ alignItems: "center",
+ flexDirection: "column",
+ gap: "0.8rem",
+}));
+
+const ProjectStatsCount = styled(Typography)(() => ({
+ fontSize: "2.4rem",
+ fontWeight: "bold",
+}));
+
+const ProjectStatsTitle = styled(Typography)(() => ({
+ fontSize: "1.6rem",
+}));
diff --git a/src/features/projects/hook/useProjectInsert.ts b/src/features/projects/hook/useProjectInsert.ts
index 07521d7..ad6bb90 100644
--- a/src/features/projects/hook/useProjectInsert.ts
+++ b/src/features/projects/hook/useProjectInsert.ts
@@ -22,6 +22,9 @@ export default useProjectInsert;
const TestData: ProjectItemInsertReq = {
userId: "user1234",
userName: "홍길동",
+ userRole: "frontend",
+ avatar: "https://via.placeholder.com/150",
+ applicants: [],
status: "모집중",
title: "AI 기반 음악 추천 서비스 개발",
oneLineInfo: "AI로 사용자 취향을 분석하는 음악 추천 프로젝트입니다.",
diff --git a/src/pages/home/ui/HomePage.tsx b/src/pages/home/ui/HomePage.tsx
index 66f3e7e..05f07aa 100644
--- a/src/pages/home/ui/HomePage.tsx
+++ b/src/pages/home/ui/HomePage.tsx
@@ -1,6 +1,11 @@
+import { Box, Container, styled } from "@mui/material";
import type { JSX } from "react";
+import Hero from "@widgets/hero/ui/Hero";
+
import useProjectList from "@entities/projects/queries/useProjectList";
+import ProjectCard from "@entities/projects/ui/projects-card/ProjectCard";
+import ProjectsStats from "@entities/projects/ui/projects-stats/ProjectsStats";
const HomePage = (): JSX.Element => {
const { data } = useProjectList();
@@ -9,11 +14,65 @@ const HomePage = (): JSX.Element => {
console.log("API_KEY: ", import.meta.env.VITE_API_KEY);
return (
-
-
홈 페이지
-
환영합니다! 이곳은 홈 페이지입니다.
-
+
+
+
+
+
+
+
+
+
+
+
);
};
export default HomePage;
+
+const MainContainer = styled(Container)(({ theme }) => ({
+ flexGrow: 1,
+ minHeight: "100vh",
+ backgroundColor: theme.palette.background.default,
+}));
+
+const HeroContainer = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "center",
+ justifyContent: "center",
+ backgroundColor: theme.palette.background.default,
+ padding: "2rem 0rem",
+ [theme.breakpoints.up("sm")]: {
+ padding: "4rem 2rem",
+ },
+ [theme.breakpoints.up("md")]: {
+ padding: "6rem 2.4rem",
+ },
+}));
+
+const ProjectStatsContainer = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ gap: "3.2rem",
+ justifyContent: "center",
+ padding: "2rem 0rem",
+ [theme.breakpoints.up("sm")]: {
+ flexDirection: "row",
+ padding: "4rem 2rem",
+ },
+ [theme.breakpoints.up("md")]: {
+ padding: "6rem 2.4rem",
+ },
+}));
+
+const ProjectCardContainer = styled(Box)(({ theme }) => ({
+ padding: "2rem 0rem",
+ [theme.breakpoints.up("sm")]: {
+ flexDirection: "row",
+ padding: "4rem 2rem",
+ },
+ [theme.breakpoints.up("md")]: {
+ padding: "6rem 2.4rem",
+ },
+}));
diff --git a/src/shared/hooks/useDraggable.tsx b/src/shared/hooks/useDraggable.tsx
new file mode 100644
index 0000000..fd91e3a
--- /dev/null
+++ b/src/shared/hooks/useDraggable.tsx
@@ -0,0 +1,67 @@
+import { useRef, useCallback } from "react";
+
+interface ReturnTypes {
+ scrollRef: React.RefObject;
+ handleMouseDown: (e: React.MouseEvent) => void;
+}
+
+const useDraggable = (): ReturnTypes => {
+ const scrollRef = useRef(null);
+
+ const handleMouseDown = useCallback((e: React.MouseEvent): void => {
+ const slider = scrollRef.current;
+ if (!slider) return;
+
+ e.preventDefault();
+
+ let isDown = true;
+ let startX = e.pageX;
+ let scrollLeft = slider.scrollLeft;
+ let hasMoved = false;
+
+ slider.style.cursor = "grabbing";
+ slider.style.userSelect = "none";
+
+ const handleMouseMove = (e: MouseEvent): void => {
+ if (!isDown) return;
+ e.preventDefault();
+
+ hasMoved = true;
+ const x = e.pageX;
+ const walk = (x - startX) * 1.5;
+ slider.scrollLeft = scrollLeft - walk;
+ };
+
+ const handleMouseUp = (): void => {
+ isDown = false;
+
+ slider.style.cursor = "grab";
+ slider.style.userSelect = "";
+
+ document.removeEventListener("mousemove", handleMouseMove);
+ document.removeEventListener("mouseup", handleMouseUp);
+
+ if (hasMoved) {
+ const preventClick = (e: Event): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ };
+
+ slider.addEventListener("click", preventClick, { once: true });
+ setTimeout(() => {
+ slider.removeEventListener("click", preventClick);
+ }, 0);
+ }
+ };
+
+ document.addEventListener("mousemove", handleMouseMove, { passive: false });
+ document.addEventListener("mouseup", handleMouseUp);
+ }, []);
+
+ return {
+ scrollRef,
+ handleMouseDown,
+ };
+};
+
+export default useDraggable;
diff --git a/src/shared/react-query/queryClient.ts b/src/shared/react-query/queryClient.ts
index db2b63d..85b0eae 100644
--- a/src/shared/react-query/queryClient.ts
+++ b/src/shared/react-query/queryClient.ts
@@ -1,5 +1,12 @@
import { QueryClient } from "@tanstack/react-query";
-const queryClient = new QueryClient();
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ refetchOnWindowFocus: false,
+ retry: 1,
+ },
+ },
+});
export default queryClient;
diff --git a/src/shared/ui/DragScrollContainer.tsx b/src/shared/ui/DragScrollContainer.tsx
new file mode 100644
index 0000000..d894864
--- /dev/null
+++ b/src/shared/ui/DragScrollContainer.tsx
@@ -0,0 +1,65 @@
+import { Box, Stack, styled } from "@mui/material";
+import type { JSX, ReactNode } from "react";
+
+import useDraggable from "@shared/hooks/useDraggable";
+
+const DragScrollContainer = ({
+ children,
+}: {
+ children: ReactNode;
+}): JSX.Element => {
+ const { scrollRef, handleMouseDown } = useDraggable();
+
+ return (
+
+
+ {children}
+
+
+ );
+};
+
+export default DragScrollContainer;
+
+const Container = styled(Box)(({ theme }) => ({
+ overflow: "hidden",
+ position: "relative",
+ marginTop: theme.spacing(0.4),
+
+ "&::after": {
+ content: '""',
+ position: "absolute",
+ top: 0,
+ right: 0,
+ width: "2rem",
+ height: "100%",
+ background: `linear-gradient(to right, transparent, ${theme.palette.background.paper})`,
+ pointerEvents: "none",
+ zIndex: 1,
+ },
+}));
+
+const DragScrollSection = styled(Stack)(({ theme }) => ({
+ flexDirection: "row",
+ gap: theme.spacing(0.8),
+ overflowX: "auto",
+ scrollbarWidth: "none",
+ msOverflowStyle: "none",
+ paddingBottom: theme.spacing(0.4),
+ paddingRight: theme.spacing(2),
+ cursor: "grab",
+
+ "&::-webkit-scrollbar": {
+ display: "none",
+ },
+
+ "&:active": {
+ cursor: "grabbing",
+ },
+
+ scrollBehavior: "smooth",
+ WebkitOverflowScrolling: "touch",
+
+ minHeight: "3.2rem",
+ alignItems: "center",
+}));
diff --git a/src/shared/user/types/user.ts b/src/shared/user/types/user.ts
new file mode 100644
index 0000000..25501bc
--- /dev/null
+++ b/src/shared/user/types/user.ts
@@ -0,0 +1,11 @@
+export interface User {
+ id: string;
+ name: string;
+ email: string;
+ avatar: string;
+ skills: string[];
+ userRole: UserRole;
+ experience: string;
+}
+
+export type UserRole = "frontend" | "backend" | "fullstack" | "designer" | "pm";
diff --git a/src/shared/user/ui/UserProfileAvatar.tsx b/src/shared/user/ui/UserProfileAvatar.tsx
new file mode 100644
index 0000000..c94ad37
--- /dev/null
+++ b/src/shared/user/ui/UserProfileAvatar.tsx
@@ -0,0 +1,37 @@
+import { Avatar, Box, styled } from "@mui/material";
+import type { CSSProperties, JSX } from "react";
+
+import type { User } from "@shared/user/types/user";
+import UserProfileWithNamePosition from "@shared/user/ui/UserProfileWithNamePosition";
+
+interface UserProfileAvatarProps
+ extends Pick {
+ flexDirection?: CSSProperties["flexDirection"];
+}
+
+const UserProfileAvatar = ({
+ name,
+ userRole,
+ avatar,
+ flexDirection = "row",
+}: UserProfileAvatarProps): JSX.Element => {
+ return (
+
+
+
+
+ );
+};
+
+export default UserProfileAvatar;
+
+const UserProfileAvatarContainer = styled(Box)(() => ({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ gap: "0.8rem",
+}));
diff --git a/src/shared/user/ui/UserProfileWithNamePosition.tsx b/src/shared/user/ui/UserProfileWithNamePosition.tsx
new file mode 100644
index 0000000..6c76c7e
--- /dev/null
+++ b/src/shared/user/ui/UserProfileWithNamePosition.tsx
@@ -0,0 +1,24 @@
+import { Stack, Typography } from "@mui/material";
+import type { CSSProperties, JSX } from "react";
+
+import type { User } from "@shared/user/types/user";
+
+interface UserProfileWithNamePositionProps
+ extends Pick {
+ flexDirection?: CSSProperties["flexDirection"];
+}
+
+const UserProfileWithNamePosition = ({
+ name,
+ userRole,
+ flexDirection = "column",
+}: UserProfileWithNamePositionProps): JSX.Element => {
+ return (
+
+ {name}
+ {userRole}
+
+ );
+};
+
+export default UserProfileWithNamePosition;
diff --git a/src/widgets/hero/ui/Hero.tsx b/src/widgets/hero/ui/Hero.tsx
new file mode 100644
index 0000000..fd88a1a
--- /dev/null
+++ b/src/widgets/hero/ui/Hero.tsx
@@ -0,0 +1,82 @@
+import AddIcon from "@mui/icons-material/Add";
+import SearchIcon from "@mui/icons-material/Search";
+import {
+ Box,
+ Button,
+ styled,
+ Typography,
+ type ButtonProps,
+} from "@mui/material";
+import type { JSX } from "react";
+import { Link } from "react-router-dom";
+
+const Hero = (): JSX.Element => {
+ return (
+ <>
+
+ 함께 만들어가는{" "}
+ 사이드 프로젝트 🚀
+
+
+ 아이디어는 있지만 팀이 없나요?
+ 프로젝트 잼에서 함께할 동료를 찾아보세요!
+
+
+ 혼자서는 힘들어도 함께라면 뭐든 할 수 있어요 ✨
+
+
+
+
+
+ 프로젝트 찾기
+
+
+
+
+
+ 프로젝트 등록
+
+
+
+ >
+ );
+};
+
+export default Hero;
+
+const HeroTitle = styled(Typography)(() => ({
+ textAlign: "center",
+ marginBottom: "2.4rem",
+}));
+
+const HeroTitleHighlight = styled(Typography)(({ theme }) => ({
+ ...theme.typography.subtitle1,
+ color: theme.palette.primary.main,
+}));
+
+const HeroDescription = styled(Typography)(() => ({
+ textAlign: "center",
+ marginBottom: "1.6rem",
+}));
+
+const HeroMessage = styled(Typography)(() => ({
+ textAlign: "center",
+ marginBottom: "3.2rem",
+}));
+
+const HeroButtonContainer = styled(Box)(() => ({
+ display: "flex",
+ justifyContent: "center",
+ gap: "1.6rem",
+}));
+
+const HeroButton = styled(Button)(() => ({
+ display: "flex",
+ alignItems: "center",
+ gap: "0.8rem",
+}));
+
+const HeroButtonLink = styled(Link)(() => ({
+ textDecoration: "none",
+ color: "inherit",
+}));