diff --git a/backend/server.js b/backend/server.js new file mode 100644 index 0000000..18a5018 --- /dev/null +++ b/backend/server.js @@ -0,0 +1,29 @@ +import express from "express"; +import path from "path"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const app = express(); +const PORT = process.env.PORT || 80; + +// JSON 파싱 +app.use(express.json()); + +// 정적 파일 서빙 (프론트엔드 빌드 결과물) +app.use(express.static(path.join(__dirname, "dist"))); + +// API 라우트 (추후 구현) +app.get("/api/health", (req, res) => { + res.json({ status: "ok", timestamp: new Date().toISOString() }); +}); + +// SPA 폴백 - 모든 요청을 index.html로 +app.get("*", (req, res) => { + res.sendFile(path.join(__dirname, "dist", "index.html")); +}); + +app.listen(PORT, () => { + console.log(`🌸 fromis_9 서버가 포트 ${PORT}에서 실행 중입니다`); +}); diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..cac28aa --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,18 @@ + + + + + + fromis_9 - 프로미스나인 + + + + + +
+ + + diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 0000000..580b84f --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,39 @@ +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import { BrowserView, MobileView } from 'react-device-detect'; + +// PC 페이지 +import PCHome from './pages/pc/Home'; +import PCMembers from './pages/pc/Members'; +import PCDiscography from './pages/pc/Discography'; +import PCSchedule from './pages/pc/Schedule'; + +// PC 레이아웃 +import PCLayout from './components/pc/Layout'; + +function App() { + return ( + + + + + } /> + } /> + } /> + } /> + + + + + {/* 모바일 버전은 추후 구현 */} +
+
+

fromis_9

+

모바일 버전은 준비 중입니다.

+
+
+
+
+ ); +} + +export default App; diff --git a/frontend/src/components/pc/Footer.jsx b/frontend/src/components/pc/Footer.jsx new file mode 100644 index 0000000..63c6dd7 --- /dev/null +++ b/frontend/src/components/pc/Footer.jsx @@ -0,0 +1,72 @@ +import { Instagram, Youtube, Twitter } from 'lucide-react'; +import { socialLinks } from '../../data/dummy'; + +function Footer() { + return ( + + ); +} + +export default Footer; diff --git a/frontend/src/components/pc/Header.jsx b/frontend/src/components/pc/Header.jsx new file mode 100644 index 0000000..8857fd8 --- /dev/null +++ b/frontend/src/components/pc/Header.jsx @@ -0,0 +1,72 @@ +import { NavLink } from 'react-router-dom'; +import { Instagram, Youtube, Twitter } from 'lucide-react'; +import { socialLinks } from '../../data/dummy'; + +function Header() { + const navItems = [ + { path: '/', label: '홈' }, + { path: '/members', label: '멤버' }, + { path: '/discography', label: '디스코그래피' }, + { path: '/schedule', label: '스케줄' }, + ]; + + return ( +
+
+
+ {/* 로고 */} + + fromis_9 + + + {/* 네비게이션 */} + + + {/* SNS 링크 */} +
+ + + + + + + + + +
+
+
+
+ ); +} + +export default Header; diff --git a/frontend/src/components/pc/Layout.jsx b/frontend/src/components/pc/Layout.jsx new file mode 100644 index 0000000..3ad6eda --- /dev/null +++ b/frontend/src/components/pc/Layout.jsx @@ -0,0 +1,14 @@ +import Header from './Header'; +import Footer from './Footer'; + +function Layout({ children }) { + return ( +
+
+
{children}
+
+ ); +} + +export default Layout; diff --git a/frontend/src/data/dummy.js b/frontend/src/data/dummy.js new file mode 100644 index 0000000..79b6aae --- /dev/null +++ b/frontend/src/data/dummy.js @@ -0,0 +1,125 @@ +// 더미 멤버 데이터 +export const members = [ + { + id: 1, + name: "송하영", + birthDate: "1997.03.25", + position: "리더, 메인보컬, 메인댄서", + imageUrl: + "https://i.namu.wiki/i/M9XsM0yLkPOEwQJBAnVn2rMqo4kXRfv-AkvWNvgAX1m2IUWX1IECPIZ1sqH6tDMAmpqYQefaP1ydJz7hUZ6Zxw.webp", + instagram: "https://www.instagram.com/hayoung_0325/", + }, + { + id: 2, + name: "박지원", + birthDate: "1998.10.20", + position: "메인보컬", + imageUrl: + "https://i.namu.wiki/i/M9XsM0yLkPOEwQJBAnVn2rMqo4kXRfv-AkvWNvgAX1lE7WfQKMuTmQCCJcXhPLv65cQCTMHWFhzhjJZIEbAKbQ.webp", + instagram: "https://www.instagram.com/jiwon_1020/", + }, + { + id: 3, + name: "이채영", + birthDate: "2000.06.14", + position: "래퍼", + imageUrl: + "https://i.namu.wiki/i/M9XsM0yLkPOEwQJBAnVn2rMqo4kXRfv-AkvWNvgAX1lqvGHXpz1iuLHTnVmAEdbgahPaQEZA7VOxLLdB-pK_hQ.webp", + instagram: "https://www.instagram.com/chaeyoung_0614/", + }, + { + id: 4, + name: "이나경", + birthDate: "2000.05.01", + position: "리드보컬", + imageUrl: + "https://i.namu.wiki/i/M9XsM0yLkPOEwQJBAnVn2rMqo4kXRfv-AkvWNvgAX1kGZHhKV9nNDi9mSwBCKKl5QGcHNT8_EZxLKSv6QmtVmg.webp", + instagram: "https://www.instagram.com/nakyung_0501/", + }, + { + id: 5, + name: "백지헌", + birthDate: "2003.04.17", + position: "막내", + imageUrl: + "https://i.namu.wiki/i/M9XsM0yLkPOEwQJBAnVn2rMqo4kXRfv-AkvWNvgAX1n2pqk4sWJWC_FGvQpEmLxSJC1tuhIwq8cWnqt-HMv_gw.webp", + instagram: "https://www.instagram.com/jiheon_0417/", + }, +]; + +// 더미 앨범 데이터 +export const albums = [ + { + id: 1, + title: "Unlock My World", + albumType: "정규", + releaseDate: "2023.06.05", + titleTrack: "Unlock My World", + coverUrl: + "https://i.namu.wiki/i/KDu-5K16r75g3eCGiYiOqRBNJLKfmimubzXR1bSY2C39dxGbAG1nZqKYn_uqxjShKmN0HRjqJjM1DQfbHwXhsQ.webp", + }, + { + id: 2, + title: "from our Memento Box", + albumType: "미니", + releaseDate: "2022.06.27", + titleTrack: "Stay This Way", + coverUrl: + "https://i.namu.wiki/i/RJ-v7VHvnbV2-M7S1YBn35LLQ1PV4rrLM-QE5qE0-C_p5xYx6vb0fB0O5oXPZsMo0kOWPLyE_45Cpt1kkQa5xg.webp", + }, + { + id: 3, + title: "Midnight Guest", + albumType: "미니", + releaseDate: "2022.01.17", + titleTrack: "DM", + coverUrl: + "https://i.namu.wiki/i/yPAOeX6xBp_Bxs0wNLX-4IWLvqoTJoiZPAcDFdFHiUXTM3YLKtfSbEOqr3ofAZGdPNnBxNBKXQ0bZKBJjWG1hg.webp", + }, + { + id: 4, + title: "Talk & Talk", + albumType: "싱글", + releaseDate: "2024.09.05", + titleTrack: "Supersonic", + coverUrl: + "https://i.namu.wiki/i/jYC5xE6SyC-eDlQWMHrx2OIKHKkJGkgj-_zCeMIgw8CbvU-d6c5kGE4Zy3cwkiZ7kpRG5Hmo5WfCN0uqUWpyiw.webp", + }, +]; + +// 더미 스케줄 데이터 +export const schedules = [ + { + id: 1, + date: "2025-01-02", + time: "19:00", + title: "유튜브 [스프 : 스튜디오 프로미스나인 30화]", + platform: "YouTube", + members: ["전원"], + }, + { + id: 2, + date: "2025-01-05", + time: "18:00", + title: "SBS 인기가요 출연", + platform: "SBS", + members: ["전원"], + }, + { + id: 3, + date: "2025-01-10", + time: "20:00", + title: '팬미팅 "FLOVER DAY"', + platform: "올림픽공원", + members: ["전원"], + }, +]; + +// 공식 SNS 링크 +export const socialLinks = { + youtube: "https://www.youtube.com/c/officialfromis9", + instagram: "https://www.instagram.com/officialfromis9/", + twitter: "https://twitter.com/realfromis_9", + tiktok: "https://www.tiktok.com/@officialfromis_9", + fancafe: "https://cafe.daum.net/officialfromis9", +}; diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..5fa87d9 --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,28 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* 기본 스타일 */ +body { + font-family: "Noto Sans KR", sans-serif; + background-color: #fafafa; + color: #1a1a1a; +} + +/* 스크롤바 스타일 */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; +} + +::-webkit-scrollbar-thumb { + background: #548360; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: #456e50; +} diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx new file mode 100644 index 0000000..c57caa6 --- /dev/null +++ b/frontend/src/main.jsx @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import './index.css'; + +ReactDOM.createRoot(document.getElementById('root')).render( + + + +); diff --git a/frontend/src/pages/pc/Discography.jsx b/frontend/src/pages/pc/Discography.jsx new file mode 100644 index 0000000..0ef4276 --- /dev/null +++ b/frontend/src/pages/pc/Discography.jsx @@ -0,0 +1,104 @@ +import { motion } from 'framer-motion'; +import { Calendar, Music } from 'lucide-react'; +import { albums } from '../../data/dummy'; + +function Discography() { + return ( +
+
+ {/* 헤더 */} +
+ + 디스코그래피 + + + 프로미스나인의 음악을 만나보세요 + +
+ + {/* 앨범 그리드 */} +
+ {albums.map((album, index) => ( + + {/* 앨범 커버 */} +
+ {album.title} + + {/* 앨범 타입 배지 */} + + {album.albumType} + + + {/* 호버 오버레이 */} +
+
+ +

앨범 상세보기

+
+
+
+ + {/* 앨범 정보 */} +
+

{album.title}

+

+ {album.titleTrack} +

+
+ + {album.releaseDate} +
+
+
+ ))} +
+ + {/* 통계 */} + +
+

1

+

정규 앨범

+
+
+

2

+

미니 앨범

+
+
+

1

+

싱글 앨범

+
+
+

4+

+

총 앨범

+
+
+
+
+ ); +} + +export default Discography; diff --git a/frontend/src/pages/pc/Home.jsx b/frontend/src/pages/pc/Home.jsx new file mode 100644 index 0000000..229046f --- /dev/null +++ b/frontend/src/pages/pc/Home.jsx @@ -0,0 +1,149 @@ +import { motion } from 'framer-motion'; +import { Link } from 'react-router-dom'; +import { Calendar, Users, Disc3, ArrowRight } from 'lucide-react'; +import { members, schedules, albums } from '../../data/dummy'; + +function Home() { + return ( +
+ {/* 히어로 섹션 */} +
+
+
+ +

fromis_9

+

프로미스나인

+

+ 인사드리겠습니다. 둘, 셋!
+ 이제는 약속해 소중히 간직해,
+ 당신의 아이돌로 성장하겠습니다! +

+ + 멤버 보기 + + +
+
+ + {/* 장식 */} +
+
+
+
+
+ + {/* 퀵 링크 섹션 */} +
+
+
+ + +

멤버

+

5명의 멤버를 만나보세요

+ + + +

디스코그래피

+

앨범과 음악을 확인하세요

+ + + +

스케줄

+

다가오는 일정을 확인하세요

+ +
+
+
+ + {/* 멤버 미리보기 */} +
+
+
+

멤버

+ + 전체보기 + +
+
+ {members.map((member, index) => ( + +
+ {member.name} +
+
+

{member.name}

+

{member.position.split(',')[0]}

+
+
+ ))} +
+
+
+ + {/* 스케줄 미리보기 */} +
+
+
+

다가오는 스케줄

+ + 전체보기 + +
+
+ {schedules.slice(0, 3).map((schedule) => ( +
+
+

+ {schedule.date.split('-')[2]} +

+

+ {schedule.date.split('-')[1]}월 +

+
+
+

{schedule.title}

+

{schedule.platform} · {schedule.time}

+
+
+ {schedule.members.join(', ')} +
+
+ ))} +
+
+
+
+ ); +} + +export default Home; diff --git a/frontend/src/pages/pc/Members.jsx b/frontend/src/pages/pc/Members.jsx new file mode 100644 index 0000000..f5862d0 --- /dev/null +++ b/frontend/src/pages/pc/Members.jsx @@ -0,0 +1,108 @@ +import { motion } from 'framer-motion'; +import { Instagram, Calendar } from 'lucide-react'; +import { members } from '../../data/dummy'; + +function Members() { + return ( +
+
+ {/* 헤더 */} +
+ + 멤버 + + + 프로미스나인의 5명의 멤버를 소개합니다 + +
+ + {/* 멤버 그리드 */} +
+ {members.map((member, index) => ( + +
+ {/* 이미지 */} +
+ {member.name} +
+ + {/* 정보 */} +
+

{member.name}

+

{member.position}

+ +
+ + {member.birthDate} +
+ + {/* 인스타그램 링크 */} + + + Instagram + +
+ + {/* 호버 효과 - 컬러 바 */} +
+
+ + ))} +
+ + {/* 그룹 정보 */} + +
+
+

2018

+

데뷔 연도

+
+
+

5

+

멤버 수

+
+
+

7+

+

앨범 수

+
+
+

flover

+

팬덤명

+
+
+
+
+
+ ); +} + +export default Members; diff --git a/frontend/src/pages/pc/Schedule.jsx b/frontend/src/pages/pc/Schedule.jsx new file mode 100644 index 0000000..77e22df --- /dev/null +++ b/frontend/src/pages/pc/Schedule.jsx @@ -0,0 +1,126 @@ +import { motion } from 'framer-motion'; +import { Calendar, Clock, MapPin, Users } from 'lucide-react'; +import { schedules } from '../../data/dummy'; + +function Schedule() { + // 스케줄을 날짜별로 그룹핑 + const groupedSchedules = schedules.reduce((acc, schedule) => { + const date = schedule.date; + if (!acc[date]) { + acc[date] = []; + } + acc[date].push(schedule); + return acc; + }, {}); + + const formatDate = (dateStr) => { + const date = new Date(dateStr); + const days = ['일', '월', '화', '수', '목', '금', '토']; + return { + month: date.getMonth() + 1, + day: date.getDate(), + weekday: days[date.getDay()], + }; + }; + + return ( +
+
+ {/* 헤더 */} +
+ + 스케줄 + + + 프로미스나인의 다가오는 일정을 확인하세요 + +
+ + {/* 스케줄 타임라인 */} +
+ {Object.entries(groupedSchedules).map(([date, daySchedules], groupIndex) => { + const formatted = formatDate(date); + return ( + + {/* 타임라인 라인 */} +
+ + {/* 날짜 원 */} +
+
+ {formatted.month}월 + {formatted.day} +
+ {formatted.weekday} +
+ + {/* 스케줄 카드들 */} +
+ {daySchedules.map((schedule, index) => ( +
+

{schedule.title}

+ +
+
+ + {schedule.time} +
+
+ + {schedule.platform} +
+
+ + {schedule.members.join(', ')} +
+
+
+ ))} +
+ + ); + })} +
+ + {/* 빈 스케줄 메시지 (스케줄이 없을 때) */} + {Object.keys(groupedSchedules).length === 0 && ( +
+ +

예정된 스케줄이 없습니다.

+
+ )} + + {/* 안내 */} + +

스케줄은 DC Inside 갤러리에서 자동으로 수집됩니다.

+

일정은 변경될 수 있으니 공식 채널을 확인해 주세요.

+
+
+
+ ); +} + +export default Schedule; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000..4a7b79b --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,23 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], + theme: { + extend: { + colors: { + // 프로미스나인 팬덤 컬러 + primary: { + DEFAULT: "#548360", + dark: "#456E50", + light: "#6A9A75", + }, + // 보조 컬러 + secondary: "#F5F5F5", + accent: "#FFD700", + }, + fontFamily: { + sans: ['"Noto Sans KR"', "sans-serif"], + }, + }, + }, + plugins: [], +}; diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..77a11e3 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,10 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + server: { + host: true, + port: 5173, + }, +});