diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..73db97d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +.DS_Store +dist +.vite diff --git a/README.md b/README.md index 1547226..db7d882 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,36 @@ # Wikisite -드립위키 사이트의 깃허브입니다. + +React.js로 구현한 드립위키 실험 프로젝트입니다. 위키의 기본적인 레이아웃과 주요 페이지의 초기 구조를 제공합니다. + +## 개발 환경 실행 + +```bash +npm install +npm run dev +``` + +## 프로젝트 구조 + +``` +Wikisite +├── index.html +├── package.json +├── src +│ ├── App.jsx +│ ├── main.jsx +│ ├── components +│ ├── data +│ ├── pages +│ └── styles +└── vite.config.js +``` + +## 제공되는 주요 페이지 + +- **대문(Home)**: 소개 문구와 추천 문서 카드. +- **최근 변경**: 더미 데이터를 기반으로 최근 편집 리스트 표시. +- **랜덤 문서**: 랜덤으로 문서를 선택하여 요약을 보여줍니다. +- **소개**: 위키의 목적과 참여 방법 안내. +- **개별 문서**: 문서 제목과 요약을 보여주고 본문은 향후 작성을 위해 비워둡니다. + +향후 실제 데이터 연동, 검색 기능, 편집 기능 등을 추가해 위키 경험을 확장할 수 있습니다. diff --git a/index.html b/index.html new file mode 100644 index 0000000..2ad115c --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + 드립위키 + + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..68f23bf --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "wikisite", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "prop-types": "^15.8.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.22.3" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.2.0", + "vite": "^5.2.0" + } +} diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..f59a58c --- /dev/null +++ b/public/vite.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..339aeec --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,25 @@ +import { Routes, Route, Navigate } from 'react-router-dom'; +import Layout from './components/Layout.jsx'; +import Home from './pages/Home.jsx'; +import RecentChanges from './pages/RecentChanges.jsx'; +import RandomPage from './pages/RandomPage.jsx'; +import About from './pages/About.jsx'; +import Page from './pages/Page.jsx'; +import wikiPages from './data/wikiPages.js'; + +const App = () => { + return ( + + + } /> + } /> + } /> + } /> + } /> + } /> + + + ); +}; + +export default App; diff --git a/src/components/Header.jsx b/src/components/Header.jsx new file mode 100644 index 0000000..5ab2741 --- /dev/null +++ b/src/components/Header.jsx @@ -0,0 +1,32 @@ +import { NavLink } from 'react-router-dom'; +import styles from './Header.module.css'; + +const Header = () => { + return ( +
+
+ 📚 +
+

드립위키

+

모두가 참여하는 지식 저장소

+
+
+ +
+ ); +}; + +export default Header; diff --git a/src/components/Header.module.css b/src/components/Header.module.css new file mode 100644 index 0000000..2aabb80 --- /dev/null +++ b/src/components/Header.module.css @@ -0,0 +1,65 @@ +.header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.75rem min(1200px, 92vw); + margin: 0 auto; +} + +.brand { + display: flex; + align-items: center; + gap: 1rem; +} + +.logo { + font-size: 2.5rem; +} + +.title { + font-size: 1.75rem; + margin: 0; + color: var(--color-text-primary); +} + +.subtitle { + margin: 0; + color: var(--color-text-secondary); + font-size: 0.95rem; +} + +.nav { + display: flex; + gap: 1rem; + font-weight: 600; +} + +.nav a { + color: var(--color-text-secondary); + text-decoration: none; + padding: 0.5rem 0.75rem; + border-radius: 999px; + transition: background 0.2s ease, color 0.2s ease; +} + +.nav a:hover { + background: var(--color-surface); + color: var(--color-text-primary); +} + +.active { + background: var(--color-primary); + color: white !important; +} + +@media (max-width: 720px) { + .header { + flex-direction: column; + align-items: flex-start; + gap: 1.25rem; + } + + .nav { + flex-wrap: wrap; + } +} diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx new file mode 100644 index 0000000..9058185 --- /dev/null +++ b/src/components/Layout.jsx @@ -0,0 +1,26 @@ +import PropTypes from 'prop-types'; +import { Outlet } from 'react-router-dom'; +import Header from './Header.jsx'; +import Sidebar from './Sidebar.jsx'; +import styles from './Layout.module.css'; + +const Layout = ({ children }) => { + return ( +
+
+
+ +
{children ?? }
+
+ +
+ ); +}; + +Layout.propTypes = { + children: PropTypes.node +}; + +export default Layout; diff --git a/src/components/Layout.module.css b/src/components/Layout.module.css new file mode 100644 index 0000000..78db3bb --- /dev/null +++ b/src/components/Layout.module.css @@ -0,0 +1,42 @@ +.appShell { + display: flex; + flex-direction: column; + min-height: 100vh; + background: var(--color-background); + color: var(--color-text-primary); +} + +.body { + flex: 1; + display: grid; + grid-template-columns: 280px 1fr; + gap: 2rem; + width: min(1200px, 92vw); + margin: 2rem auto; +} + +.content { + background: var(--color-surface); + border-radius: 16px; + padding: 2.5rem; + box-shadow: var(--shadow-elevation); +} + +.footer { + margin-top: auto; + padding: 1.5rem 0; + text-align: center; + font-size: 0.875rem; + color: var(--color-text-secondary); + border-top: 1px solid var(--color-border); +} + +@media (max-width: 960px) { + .body { + grid-template-columns: 1fr; + } + + .content { + order: 2; + } +} diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx new file mode 100644 index 0000000..f9206be --- /dev/null +++ b/src/components/Sidebar.jsx @@ -0,0 +1,48 @@ +import { NavLink } from 'react-router-dom'; +import wikiPages from '../data/wikiPages.js'; +import styles from './Sidebar.module.css'; + +const Sidebar = () => { + return ( + + ); +}; + +export default Sidebar; diff --git a/src/components/Sidebar.module.css b/src/components/Sidebar.module.css new file mode 100644 index 0000000..cfa09e0 --- /dev/null +++ b/src/components/Sidebar.module.css @@ -0,0 +1,69 @@ +.sidebar { + background: var(--color-surface); + border-radius: 16px; + padding: 1.75rem; + box-shadow: var(--shadow-elevation); + position: sticky; + top: 2rem; + align-self: start; + height: fit-content; +} + +.section + .section { + margin-top: 1.75rem; +} + +.section h2 { + font-size: 1.1rem; + margin-bottom: 0.75rem; + color: var(--color-text-primary); +} + +.searchInput { + width: 100%; + padding: 0.6rem 0.85rem; + border: 1px solid var(--color-border); + border-radius: 10px; + background: var(--color-background); + color: var(--color-text-secondary); +} + +.searchInput::placeholder { + color: var(--color-text-tertiary); +} + +.pageList, +.links { + margin: 0; + padding: 0; + list-style: none; + display: grid; + gap: 0.5rem; +} + +.pageList a, +.links a { + color: var(--color-text-secondary); + text-decoration: none; + padding: 0.45rem 0.6rem; + border-radius: 8px; + transition: background 0.2s ease, color 0.2s ease; +} + +.pageList a:hover, +.links a:hover { + background: var(--color-background); + color: var(--color-text-primary); +} + +.active { + background: var(--color-primary); + color: white !important; +} + +@media (max-width: 960px) { + .sidebar { + position: static; + order: 1; + } +} diff --git a/src/data/wikiPages.js b/src/data/wikiPages.js new file mode 100644 index 0000000..d471ecb --- /dev/null +++ b/src/data/wikiPages.js @@ -0,0 +1,34 @@ +const wikiPages = [ + { + slug: 'welcome', + title: '드립위키에 오신 것을 환영합니다', + summary: + '드립위키는 다양한 밈과 커뮤니티 이야기를 기록하고 공유하는 공간입니다. 자유롭게 문서를 만들고 수정해 주세요.' + }, + { + slug: 'editing-guidelines', + title: '편집 가이드라인', + summary: + '모두가 즐겁게 참여할 수 있도록 편집 시 지켜야 할 기본 규칙과 스타일 가이드를 정리했습니다.' + }, + { + slug: 'popular-memes', + title: '인기 있는 드립 모음', + summary: + '요즘 커뮤니티에서 화제가 되는 드립과 밈을 한눈에 정리했습니다. 이미지와 영상 링크도 함께 확인해 보세요.' + }, + { + slug: 'community-history', + title: '커뮤니티 역사', + summary: + '드립 문화가 형성된 배경부터 현재까지의 주요 사건들을 연대기 순으로 정리했습니다.' + }, + { + slug: 'faq', + title: '자주 묻는 질문', + summary: + '처음 방문한 사용자들이 궁금해하는 점들을 모아 답변했습니다. 계정, 문서 작성, 이미지 업로드 등 자주 묻는 질문을 확인하세요.' + } +]; + +export default wikiPages; diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 0000000..9a057a4 --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import App from './App.jsx'; +import './styles/global.css'; + +ReactDOM.createRoot(document.getElementById('root')).render( + + + + + +); diff --git a/src/pages/About.jsx b/src/pages/About.jsx new file mode 100644 index 0000000..998e14d --- /dev/null +++ b/src/pages/About.jsx @@ -0,0 +1,30 @@ +import styles from './About.module.css'; + +const About = () => { + return ( +
+

드립위키 소개

+

+ 드립위키는 커뮤니티 문화의 다양성을 존중하며, 그 순간의 웃음과 이야기를 기록하는 것이 목표입니다. 누구나 쉽게 + 참여하고 정보를 공유할 수 있는 개방형 플랫폼을 지향합니다. +

+
+

우리의 목표

+ +
+
+

참여하는 방법

+

+ 로그인 기능과 편집 도구는 곧 제공될 예정입니다. 그동안은 커뮤니티 게시판을 통해 새로운 아이디어와 피드백을 공유해 + 주세요. 오픈 베타를 준비하며 여러분의 의견을 적극 반영하겠습니다. +

+
+
+ ); +}; + +export default About; diff --git a/src/pages/About.module.css b/src/pages/About.module.css new file mode 100644 index 0000000..188cfb8 --- /dev/null +++ b/src/pages/About.module.css @@ -0,0 +1,33 @@ +.about { + display: grid; + gap: 1.75rem; +} + +.about h2 { + margin: 0; +} + +.about p { + margin: 0; + color: var(--color-text-secondary); + line-height: 1.7; +} + +section { + background: var(--color-background); + border-radius: 14px; + padding: 1.5rem; + border: 1px solid rgba(79, 70, 229, 0.08); +} + +section h3 { + margin-top: 0; + margin-bottom: 0.75rem; +} + +section ul { + margin: 0; + padding-left: 1.25rem; + color: var(--color-text-secondary); + line-height: 1.6; +} diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx new file mode 100644 index 0000000..fb9d8e3 --- /dev/null +++ b/src/pages/Home.jsx @@ -0,0 +1,40 @@ +import { Link } from 'react-router-dom'; +import wikiPages from '../data/wikiPages.js'; +import styles from './Home.module.css'; + +const Home = () => { + return ( +
+
+

드립과 이야기를 기록하는 열린 공간

+

+ 드립위키는 커뮤니티에서 탄생한 다양한 드립과 밈을 체계적으로 정리해 공유하는 위키입니다. + 누구나 자유롭게 문서를 만들고 편집하여, 우리만의 역사를 함께 만들어 갈 수 있어요. +

+
+ + 첫 문서 보기 + + + 최근 변경 살펴보기 + +
+
+ +
+

추천 문서

+
+ {wikiPages.map((page) => ( +
+

{page.title}

+

{page.summary}

+ 문서 읽기 → +
+ ))} +
+
+
+ ); +}; + +export default Home; diff --git a/src/pages/Home.module.css b/src/pages/Home.module.css new file mode 100644 index 0000000..cd1e59b --- /dev/null +++ b/src/pages/Home.module.css @@ -0,0 +1,86 @@ +.home { + display: grid; + gap: 2.5rem; +} + +.hero { + display: grid; + gap: 1.25rem; +} + +.hero h2 { + margin: 0; + font-size: 2.4rem; + color: var(--color-text-primary); +} + +.hero p { + margin: 0; + color: var(--color-text-secondary); + line-height: 1.7; +} + +.ctaGroup { + display: flex; + gap: 1rem; + flex-wrap: wrap; +} + +.primaryCta, +.secondaryCta { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.75rem 1.5rem; + border-radius: 999px; + text-decoration: none; + font-weight: 600; +} + +.primaryCta { + background: var(--color-primary); + color: white; + box-shadow: 0 12px 24px rgba(79, 70, 229, 0.2); +} + +.secondaryCta { + background: var(--color-background); + color: var(--color-text-primary); + border: 1px solid var(--color-border); +} + +.featured h3 { + margin-bottom: 1rem; + color: var(--color-text-primary); +} + +.cardGrid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 1.25rem; +} + +.card { + background: var(--color-background); + border-radius: 14px; + padding: 1.5rem; + border: 1px solid rgba(79, 70, 229, 0.08); + display: grid; + gap: 0.75rem; +} + +.card h4 { + margin: 0; +} + +.card p { + margin: 0; + color: var(--color-text-secondary); + line-height: 1.6; +} + +.card a { + justify-self: flex-start; + color: var(--color-primary); + font-weight: 600; +} diff --git a/src/pages/Page.jsx b/src/pages/Page.jsx new file mode 100644 index 0000000..ab3005d --- /dev/null +++ b/src/pages/Page.jsx @@ -0,0 +1,41 @@ +import { useParams } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import styles from './Page.module.css'; + +const Page = ({ pages }) => { + const { slug } = useParams(); + const page = pages.find((item) => item.slug === slug); + + if (!page) { + return ( +
+

문서를 찾을 수 없습니다

+

요청한 문서는 아직 작성되지 않았습니다. 새로운 문서를 만들어 보는 건 어떨까요?

+
+ ); + } + + return ( +
+
+

{page.title}

+

{page.summary}

+
+
+

문서 본문은 아직 준비 중입니다. 여러분이 첫 편집자가 되어 내용을 채워 주세요!

+
+
+ ); +}; + +Page.propTypes = { + pages: PropTypes.arrayOf( + PropTypes.shape({ + slug: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + summary: PropTypes.string.isRequired + }) + ).isRequired +}; + +export default Page; diff --git a/src/pages/Page.module.css b/src/pages/Page.module.css new file mode 100644 index 0000000..981e865 --- /dev/null +++ b/src/pages/Page.module.css @@ -0,0 +1,37 @@ +.page { + display: grid; + gap: 2rem; +} + +header h2 { + margin: 0; +} + +.summary { + margin: 0.75rem 0 0; + color: var(--color-text-secondary); + line-height: 1.6; +} + +.placeholder { + padding: 2rem; + border-radius: 16px; + border: 1px dashed rgba(79, 70, 229, 0.4); + background: rgba(79, 70, 229, 0.05); + color: var(--color-text-secondary); +} + +.notFound { + text-align: center; + display: grid; + gap: 1rem; +} + +.notFound h2 { + margin-bottom: 0; +} + +.notFound p { + margin: 0; + color: var(--color-text-secondary); +} diff --git a/src/pages/RandomPage.jsx b/src/pages/RandomPage.jsx new file mode 100644 index 0000000..4be0d12 --- /dev/null +++ b/src/pages/RandomPage.jsx @@ -0,0 +1,24 @@ +import { useMemo } from 'react'; +import { Link } from 'react-router-dom'; +import wikiPages from '../data/wikiPages.js'; +import styles from './RandomPage.module.css'; + +const RandomPage = () => { + const page = useMemo(() => wikiPages[Math.floor(Math.random() * wikiPages.length)], []); + + return ( +
+

랜덤 문서

+
+

{page.title}

+

{page.summary}

+ + 문서 전체 읽기 + +
+

새로고침(F5)을 눌러 다른 문서를 만나보세요!

+
+ ); +}; + +export default RandomPage; diff --git a/src/pages/RandomPage.module.css b/src/pages/RandomPage.module.css new file mode 100644 index 0000000..6d9ff2d --- /dev/null +++ b/src/pages/RandomPage.module.css @@ -0,0 +1,40 @@ +.randomPage { + display: grid; + gap: 1.5rem; +} + +.randomPage h2 { + margin: 0; +} + +article { + padding: 1.75rem; + border-radius: 14px; + border: 1px solid rgba(79, 70, 229, 0.1); + background: var(--color-background); + display: grid; + gap: 0.75rem; +} + +article h3 { + margin: 0; +} + +article p { + margin: 0; + color: var(--color-text-secondary); + line-height: 1.6; +} + +.link { + justify-self: flex-start; + color: var(--color-primary); + font-weight: 600; + text-decoration: none; +} + +.note { + margin: 0; + color: var(--color-text-tertiary); + font-size: 0.9rem; +} diff --git a/src/pages/RecentChanges.jsx b/src/pages/RecentChanges.jsx new file mode 100644 index 0000000..ecb651a --- /dev/null +++ b/src/pages/RecentChanges.jsx @@ -0,0 +1,49 @@ +import styles from './RecentChanges.module.css'; + +const dummyChanges = [ + { + page: '인기 있는 드립 모음', + user: 'wiki_master', + time: '5분 전', + summary: '최신 밈 추가 및 이미지 첨부' + }, + { + page: '커뮤니티 역사', + user: 'historians', + time: '1시간 전', + summary: '2000년대 초반 커뮤니티 사건 보강' + }, + { + page: '편집 가이드라인', + user: 'mod_team', + time: '어제', + summary: '욕설 사용 지침 업데이트' + } +]; + +const RecentChanges = () => { + return ( +
+
+

최근 변경

+

커뮤니티에서 방금 업데이트한 문서를 확인해 보세요.

+
+ +
+ ); +}; + +export default RecentChanges; diff --git a/src/pages/RecentChanges.module.css b/src/pages/RecentChanges.module.css new file mode 100644 index 0000000..88daeae --- /dev/null +++ b/src/pages/RecentChanges.module.css @@ -0,0 +1,61 @@ +.recentChanges { + display: grid; + gap: 1.75rem; +} + +header h2 { + margin: 0 0 0.5rem; + color: var(--color-text-primary); +} + +header p { + margin: 0; + color: var(--color-text-secondary); +} + +.changeList { + list-style: none; + margin: 0; + padding: 0; + display: grid; + gap: 1rem; +} + +.item { + display: flex; + justify-content: space-between; + gap: 1rem; + padding: 1.25rem 1.5rem; + border-radius: 12px; + border: 1px solid rgba(15, 23, 42, 0.06); + background: var(--color-background); +} + +.item h3 { + margin: 0; + color: var(--color-text-primary); +} + +.item p { + margin: 0.35rem 0 0; + color: var(--color-text-secondary); +} + +.meta { + display: grid; + justify-items: end; + gap: 0.35rem; + color: var(--color-text-tertiary); + font-size: 0.9rem; +} + +@media (max-width: 640px) { + .item { + flex-direction: column; + } + + .meta { + justify-items: start; + font-size: 0.85rem; + } +} diff --git a/src/styles/global.css b/src/styles/global.css new file mode 100644 index 0000000..379f544 --- /dev/null +++ b/src/styles/global.css @@ -0,0 +1,30 @@ +:root { + --color-background: #f5f7fb; + --color-surface: #ffffff; + --color-primary: #4f46e5; + --color-text-primary: #1f2937; + --color-text-secondary: #4b5563; + --color-text-tertiary: #9ca3af; + --color-border: rgba(79, 70, 229, 0.12); + --shadow-elevation: 0 18px 35px rgba(15, 23, 42, 0.08); + font-family: 'Pretendard', 'Noto Sans KR', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + background-color: var(--color-background); + color: var(--color-text-primary); +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + background: var(--color-background); +} + +a { + color: inherit; +} + +main { + min-height: 60vh; +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..d05fa59 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { + port: 5173, + open: true + } +});