diff --git a/backend/routes/albums.js b/backend/routes/albums.js index b5ea92c..20235fe 100644 --- a/backend/routes/albums.js +++ b/backend/routes/albums.js @@ -47,6 +47,13 @@ router.get("/:id", async (req, res) => { ); album.tracks = tracks; + // 티저 이미지 조회 + const [teasers] = await pool.query( + "SELECT image_url FROM album_teasers WHERE album_id = ? ORDER BY sort_order", + [album.id] + ); + album.teasers = teasers.map((t) => t.image_url); + res.json(album); } catch (error) { console.error("앨범 조회 오류:", error); diff --git a/frontend/src/pages/pc/AlbumDetail.jsx b/frontend/src/pages/pc/AlbumDetail.jsx index 2ed31e3..54e5d57 100644 --- a/frontend/src/pages/pc/AlbumDetail.jsx +++ b/frontend/src/pages/pc/AlbumDetail.jsx @@ -1,13 +1,15 @@ import { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { motion } from 'framer-motion'; -import { ArrowLeft, Calendar, Music2, Play, Clock } from 'lucide-react'; +import { ArrowLeft, Calendar, Music2, Play, Clock, X, ChevronLeft, ChevronRight } from 'lucide-react'; function AlbumDetail() { const { id } = useParams(); const navigate = useNavigate(); const [album, setAlbum] = useState(null); const [loading, setLoading] = useState(true); + const [lightbox, setLightbox] = useState({ open: false, images: [], index: 0 }); + const [slideDirection, setSlideDirection] = useState(0); useEffect(() => { fetch(`/api/albums/${id}`) @@ -70,6 +72,7 @@ function AlbumDetail() { } return ( + <> {/* 앨범 정보 헤더 */} -
- {/* 앨범 커버 */} +
+ {/* 앨범 커버 - 크기 증가 */} - - {album.album_type} - -

{album.title}

- -
-
- - {formatDate(album.release_date)} -
-
- - {album.tracks?.length || 0}곡 -
-
- - {getTotalDuration()} +
+ + {album.album_type} + +

{album.title}

+ +
+
+ + {formatDate(album.release_date)} +
+
+ + {album.tracks?.length || 0}곡 +
+
+ + {getTotalDuration()} +
+ +

+ 타이틀곡: {album.tracks?.find(t => t.is_title_track === 1)?.title || album.tracks?.[0]?.title} +

-

- 타이틀곡: {album.tracks?.find(t => t.is_title_track === 1)?.title || album.tracks?.[0]?.title} -

+ {/* 앨범 티저 이미지 */} + {album.teasers && album.teasers.length > 0 && ( +
+

Official Teaser

+
+ {album.teasers.map((teaser, index) => ( +
setLightbox({ open: true, images: album.teasers, index })} + className="w-24 h-24 bg-gray-200 rounded-lg overflow-hidden cursor-pointer transition-all duration-200 hover:scale-110 hover:shadow-xl hover:z-10" + > + {`Teaser +
+ ))} +
+
+ )}
@@ -200,8 +227,117 @@ function AlbumDetail() {
+ + {/* 컨셉 포토 섹션 */} + +
+

컨셉 포토

+ +
+ {/* 4장 정사각형 그리드 - 실제 이미지는 object-cover로 크롭 */} +
+ {[1, 2, 3, 4].map((num) => ( +
+ {/* 실제 이미지가 들어갈 자리 - object-cover로 중앙 크롭 */} + {/* */} +
+ {/* 더미 플레이스홀더 */} +
+ {num} +
+
+ ))} +
+
+ + {/* 라이트박스 모달 */} + {lightbox.open && ( +
setLightbox({ ...lightbox, open: false })} + > + {/* 닫기 버튼 */} + + + {/* 이전 버튼 */} + {lightbox.images.length > 1 && ( + + )} + + {/* 이미지 */} + e.stopPropagation()} + initial={{ opacity: 0, x: slideDirection * 100 }} + animate={{ opacity: 1, x: 0 }} + transition={{ duration: 0.25, ease: 'easeOut' }} + /> + + {/* 다음 버튼 */} + {lightbox.images.length > 1 && ( + + )} + + {/* 인디케이터 */} +
+ {lightbox.images.map((_, i) => ( +
+
+ )} + ); }