+
-
+
좋아하는 아티스트에게 직접 후원하고
팬심을 행동으로 보여주세요.
@@ -120,7 +169,7 @@ export default function LandingMiddleSection() {
-
+
이번 달
가장 빛난 별은
누구?
@@ -128,10 +177,10 @@ export default function LandingMiddleSection() {
-
+
-
+
팬들의 사랑을 가장 많이 받은
이달의 아티스트를 소개합니다!
@@ -144,7 +193,7 @@ export default function LandingMiddleSection() {
-
+
내 마음 속
@@ -158,10 +207,10 @@ export default function LandingMiddleSection() {
-
+
-
+
내가 좋아하는 아티스트 소식만 골라보고
새로운 콘텐츠와 스케줄도 한눈에!
@@ -173,7 +222,6 @@ export default function LandingMiddleSection() {
);
}
-
const StyledLandingMiddleSection = styled.section`
position: relative;
@@ -363,6 +411,7 @@ const StyledLandingMiddleSection = styled.section`
}
}
@media all and (max-width: 768px) {
+ height: auto !important;
.landingGrid {
gap: 20px;
flex-direction: column;
@@ -385,9 +434,13 @@ const StyledLandingMiddleSection = styled.section`
width: 40vw;
top: 70%;
img {
- max-height: calc(65vh - 100px);
+ max-height: 400px;
}
}
+ article {
+ position: static;
+ height: 850px;
+ }
}
@media all and (max-width: 425px) {
span.show-425 {
diff --git a/src/pages/Landing/index.jsx b/src/pages/Landing/index.jsx
index 52f6f1a..1bc602a 100644
--- a/src/pages/Landing/index.jsx
+++ b/src/pages/Landing/index.jsx
@@ -1,8 +1,4 @@
import styled from "@emotion/styled";
-import Lenis from "@studio-freight/lenis";
-import { gsap } from "gsap";
-import { ScrollTrigger } from "gsap/ScrollTrigger";
-import { useEffect, useRef } from "react";
import LandingBottomSection from "./components/LandingBottomSection";
import LandingFooter from "./components/LandingFooter";
import LandingHeader from "./components/LandingHeader";
@@ -10,30 +6,6 @@ import LandingMiddleSection from "./components/LandingMiddleSection";
import LandingTopSection from "./components/LandingTopSection";
const Landing = () => {
- const lenisRef = useRef(null);
-
- // Lenis 스크롤 초기화
- useEffect(() => {
- lenisRef.current = new Lenis({
- smoothWheel: true,
- duration: 1.2,
- });
-
- lenisRef.current.on("scroll", ScrollTrigger.update);
-
- const animate = (time) => {
- lenisRef.current?.raf(time * 1000);
- };
-
- gsap.ticker.add(animate);
- gsap.ticker.lagSmoothing(0);
-
- return () => {
- gsap.ticker.remove(animate);
- lenisRef.current?.destroy();
- };
- }, []);
-
return (
@@ -50,7 +22,7 @@ export default Landing;
const LandingContainer = styled.div`
overflow: hidden;
section {
- height: 100vh;
+ height: 100lvh;
position: relative;
}
`;
diff --git a/src/pages/List/Chart/Chart.styles.js b/src/pages/List/Chart/Chart.styles.js
index bddd562..c163c80 100644
--- a/src/pages/List/Chart/Chart.styles.js
+++ b/src/pages/List/Chart/Chart.styles.js
@@ -1,10 +1,11 @@
import styled from "@emotion/styled";
+import { shimmerStyle } from "../../../styles/skeletonAnimation"; // ⭐ 스켈레톤 애니메이션 import
export const ChartContainer = styled.div`
- max-width:1200px;
+ max-width: 1200px;
margin: 0 auto;
padding: 40px 0;
- width: clamp(325px, 90vw, 1200px);
+ width: 100%
`;
export const ChartHeaderWrap = styled.div`
@@ -42,16 +43,11 @@ const ChartIdolBase = styled.div`
height: 42px;
`;
-export const ChartIdolLeft = styled(ChartIdolBase)`
-
-
-`;
+export const ChartIdolLeft = styled(ChartIdolBase)``;
-export const ChartIdolRight = styled(ChartIdolBase)`
-
-`;
+export const ChartIdolRight = styled(ChartIdolBase)``;
-export const ChartList = styled.div`
+export const ChartList = styled.ul`
display: flex;
flex-wrap: wrap;
gap: 16px;
@@ -129,3 +125,53 @@ export const VoteChart = styled.div`
align-items: center;
gap: 4px;
`;
+export const Overlay = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ background-color: rgba(0, 0, 0, 0.6);
+ z-index: 1000;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ `;
+
+// ✨ 스켈레톤 컴포넌트 추가
+
+export const SkeletonListItem = styled.li`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding-bottom: 8px;
+ border-bottom: 1px solid #ffffff1a;
+`;
+
+export const SkeletonProfile = styled.div`
+ width: 70px;
+ height: 70px;
+ border-radius: 50%;
+ ${shimmerStyle};
+`;
+
+export const SkeletonRankAndName = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin-left: 16px;
+`;
+
+export const SkeletonName = styled.div`
+ width: 100px;
+ height: 16px;
+ border-radius: 8px;
+ ${shimmerStyle};
+`;
+
+export const SkeletonVotes = styled.div`
+ width: 40px;
+ height: 16px;
+ border-radius: 8px;
+ ${shimmerStyle};
+`;
diff --git a/src/pages/List/Chart/components/IdolProfileModal.jsx b/src/pages/List/Chart/components/IdolProfileModal.jsx
index 02a91c6..8c49edb 100644
--- a/src/pages/List/Chart/components/IdolProfileModal.jsx
+++ b/src/pages/List/Chart/components/IdolProfileModal.jsx
@@ -1,7 +1,5 @@
-/** @jsxImportSource @emotion/react */
-import React from "react";
-import Circle from "../../../../components/Circle";
-import Modal from "../../../../components/Modal";
+import Circle from "@/components/Circle";
+import Modal from "@/components/Modal";
import {
CircleContainer,
CloseButton,
@@ -22,9 +20,21 @@ import {
VoteCount,
VoteLabel,
VoteSection,
-} from "./IdolProfileModal.styles";
+} from "@/pages/List/Chart/components/IdolProfileModal.styles"; // ✨ styles 파일도 절대경로로 변경
+/** @jsxImportSource @emotion/react */
+import { useEffect, useState } from "react";
const IdolProfileModal = ({ idol, onClose }) => {
+ const [isMobile, setIsMobile] = useState(window.innerWidth <= 425);
+
+ useEffect(() => {
+ const handleResize = () => {
+ setIsMobile(window.innerWidth <= 425);
+ };
+ window.addEventListener("resize", handleResize);
+ return () => window.removeEventListener("resize", handleResize);
+ }, []);
+
if (!idol) return null;
return (
@@ -32,7 +42,19 @@ const IdolProfileModal = ({ idol, onClose }) => {
-
+ {isMobile ? (
+
+ ) : (
+
+ )}
diff --git a/src/pages/List/Chart/components/IdolProfileModal.styles.js b/src/pages/List/Chart/components/IdolProfileModal.styles.js
index 15f6612..454b495 100644
--- a/src/pages/List/Chart/components/IdolProfileModal.styles.js
+++ b/src/pages/List/Chart/components/IdolProfileModal.styles.js
@@ -14,7 +14,8 @@ export const Profile = styled.div`
transform: translate(-50%, -50%);
z-index: 1001;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
- width: clamp(325px, 90vw, 1200px);
+ width: 100%;
+ padding:0 8px;
`;
@@ -149,8 +150,7 @@ export const StyledVoteButton = styled.div`
`;
export const Logo = styled.div`
- width:150px;
- height:200px;
+ width:250px;
display: flex;
gap: 4px;
padding-left: 15px;
@@ -173,8 +173,17 @@ export const CloseButton = styled.button`
padding: 0;
z-index: 10;
- img {
+ img.close{
+ width: 24px;
+ height: 24px;
+ }
+ img.back{
width: 24px;
height: 24px;
+
+ }
+ @media (max-width: 425px) {
+ left: 12px;
+ top:40px;
}
`;
diff --git a/src/pages/List/Chart/components/IdolProfiles.js b/src/pages/List/Chart/components/IdolProfiles.js
index 8b19de4..bc20ad8 100644
--- a/src/pages/List/Chart/components/IdolProfiles.js
+++ b/src/pages/List/Chart/components/IdolProfiles.js
@@ -695,4 +695,16 @@ export const idolProfiles = {
instagram: " https://www.instagram.com/official.boynextdoor/",
fancam: "https://www.youtube.com/watch?v=PX05ht8so7s",
},
+ 정진규: {
+ englishName: "JEONG JINGYU",
+ birth: "1995",
+ hometown: "서울",
+ hobby: "라이브 코딩",
+ mbti: "INTP",
+ group: "FANDOM ",
+ agency: "Codeit",
+ groupLogo: "/images/FANDOM.png",
+ instagram: "https://www.instagram.com/codeit_kr/",
+ fancam: "https://www.youtube.com/watch?v=IyvijbB8jmc",
+ },
};
diff --git a/src/pages/List/Chart/index.jsx b/src/pages/List/Chart/index.jsx
index 8bbff44..9247861 100644
--- a/src/pages/List/Chart/index.jsx
+++ b/src/pages/List/Chart/index.jsx
@@ -1,13 +1,12 @@
import Button from "@/components/Button/Button";
import Circle from "@/components/Circle";
+import LoadingError from "@/components/Error";
import Modal from "@/components/Modal";
-import styled from "@emotion/styled";
+import { useChart } from "@/hooks/useChart";
+import ChartVoteModal from "@/pages/List/Chart/components/ChartVoteModal";
+import IdolProfileModal from "@/pages/List/Chart/components/IdolProfileModal";
+import { idolProfiles } from "@/pages/List/Chart/components/IdolProfiles";
import React, { useState } from "react";
-import chartImg from "/images/Chart.png";
-import ChartVoteModal from "./components/ChartVoteModal";
-import IdolProfileModal from "./components/IdolProfileModal";
-import { idolProfiles } from "./components/IdolProfiles";
-import useChart from "./components/hooks/useChart";
import {
ChartButtonWrap,
@@ -20,44 +19,54 @@ import {
ChartTitle,
ListItem,
MoreButton,
+ Overlay,
ProfileInfo,
RankAndName,
+ SkeletonListItem,
+ SkeletonName,
+ SkeletonProfile,
+ SkeletonRankAndName,
+ SkeletonVotes,
VoteChart,
Votes,
-} from "./Chart.styles";
-
-const Overlay = styled.div`
- position: fixed;
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
- background-color: rgba(0, 0, 0, 0.6);
- z-index: 1000;
- display: flex;
- justify-content: center;
- align-items: center;
-`;
+} from "@/pages/List/Chart/Chart.styles";
const Chart = () => {
+ const [activeTab, setActiveTab] = useState("female");
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [selectedIdol, setSelectedIdol] = useState(null);
+
const {
- idols,
- setIdols,
- activeTab,
loading,
- visibleList,
- handleMore,
- handleTabChange,
+ error,
femaleIdols,
maleIdols,
+ setIdols,
+ visibleCount,
+ setVisibleCount,
} = useChart();
- const [isModalOpen, setIsModalOpen] = useState(false);
- const [selectedIdol, setSelectedIdol] = useState(null);
-
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
+ const isFemale = activeTab === "female";
+ const visibleList = (isFemale ? femaleIdols : maleIdols).slice(
+ 0,
+ visibleCount,
+ );
+
+ const handleMore = () => {
+ if (window.matchMedia("(max-width: 425px)").matches) {
+ setVisibleCount((prev) => prev + 5);
+ } else if (
+ window.matchMedia("(min-width: 426px) and (max-width: 768px)").matches
+ ) {
+ setVisibleCount((prev) => prev + 5);
+ } else {
+ setVisibleCount((prev) => prev + 10);
+ }
+ };
+
const handleIdolClick = (idol) => {
const mockData = idolProfiles[idol.name];
if (mockData) {
@@ -91,6 +100,25 @@ const Chart = () => {
);
+ // ✨ for문으로 스켈레톤 10개 생성하는 함수
+ const renderSkeletonItems = () => {
+ const items = [];
+ for (let i = 0; i < 10; i++) {
+ items.push(
+
+
+
+
+
+
+
+
+ ,
+ );
+ }
+ return items;
+ };
+
return (
<>
@@ -99,7 +127,7 @@ const Chart = () => {
@@ -109,40 +137,66 @@ const Chart = () => {
handleTabChange("female")}
+ onClick={() => {
+ if (!isFemale) {
+ setActiveTab("female");
+ if (window.matchMedia("(max-width: 425px)").matches) {
+ setVisibleCount(5);
+ } else if (
+ window.matchMedia("(min-width: 426px) and (max-width: 768px)")
+ .matches
+ ) {
+ setVisibleCount(5);
+ } else {
+ setVisibleCount(10);
+ }
+ }
+ }}
style={{
cursor: "pointer",
- fontWeight: activeTab === "female" ? 700 : 400,
- backgroundColor:
- activeTab === "female" ? "#ffffff1a" : "transparent",
- borderBottom:
- activeTab === "female" ? "1px solid #ffffff" : "none",
+ fontWeight: isFemale ? 700 : 400,
+ backgroundColor: isFemale ? "#ffffff1a" : "transparent",
+ borderBottom: isFemale ? "1px solid #ffffff" : "none",
transition: "all 0.3s ease",
}}
>
이달의 여자 아이돌
+
handleTabChange("male")}
+ onClick={() => {
+ if (isFemale) {
+ setActiveTab("male");
+ if (window.matchMedia("(max-width: 425px)").matches) {
+ setVisibleCount(5);
+ } else if (
+ window.matchMedia("(min-width: 426px) and (max-width: 768px)")
+ .matches
+ ) {
+ setVisibleCount(5);
+ } else {
+ setVisibleCount(10);
+ }
+ }
+ }}
style={{
cursor: "pointer",
- fontWeight: activeTab === "male" ? 700 : 400,
- backgroundColor:
- activeTab === "male" ? "#ffffff1a" : "transparent",
- borderBottom: activeTab === "male" ? "1px solid #ffffff" : "none",
+ fontWeight: !isFemale ? 700 : 400,
+ backgroundColor: !isFemale ? "#ffffff1a" : "transparent",
+ borderBottom: !isFemale ? "1px solid #ffffff" : "none",
transition: "all 0.3s ease",
}}
>
@@ -150,10 +204,10 @@ const Chart = () => {
- {loading ? (
-
- 불러오는 중...
-
+ {error ? (
+
+ ) : loading ? (
+ {renderSkeletonItems()}
) : (
<>
@@ -163,9 +217,7 @@ const Chart = () => {
{visibleList.length <
- (activeTab === "female"
- ? femaleIdols.length
- : maleIdols.length) && (
+ (isFemale ? femaleIdols.length : maleIdols.length) && (