프랍 라이브러리
+스트리밍글 서비스에서 사용 가능한 프랍(소품) 목록입니다
+ +프랍 데이터를 불러오는 중...
+diff --git a/Partners/(주)세광종합기술단.png b/Partners/(주)세광종합기술단.png
index dcfb2e2..eefff33 100644
Binary files a/Partners/(주)세광종합기술단.png and b/Partners/(주)세광종합기술단.png differ
diff --git a/Partners/메가메타.jpg b/Partners/메가메타.jpg
index f49d5d3..3d5f3fd 100644
Binary files a/Partners/메가메타.jpg and b/Partners/메가메타.jpg differ
diff --git a/css/props.css b/css/props.css
new file mode 100644
index 0000000..bf38c7e
--- /dev/null
+++ b/css/props.css
@@ -0,0 +1,432 @@
+/* ========================================
+ 프랍 라이브러리 페이지 스타일
+ ======================================== */
+
+/* 페이지 헤더 */
+.props-page {
+ min-height: calc(100vh - var(--navbar-height));
+ padding: var(--spacing-2xl) 0;
+}
+
+.page-header {
+ text-align: center;
+ margin-bottom: var(--spacing-2xl);
+}
+
+.page-header h1 {
+ font-size: var(--font-4xl);
+ font-weight: 700;
+ color: var(--text-primary);
+ margin-bottom: var(--spacing-sm);
+}
+
+.page-description {
+ font-size: var(--font-lg);
+ color: var(--text-secondary);
+ margin-bottom: var(--spacing-md);
+}
+
+.last-updated {
+ font-size: var(--font-sm);
+ color: var(--text-light);
+}
+
+/* 필터 섹션 */
+.filter-section {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--spacing-md);
+ align-items: center;
+ margin-bottom: var(--spacing-lg);
+ padding: var(--spacing-lg);
+ background: var(--bg-white);
+ border-radius: var(--border-radius);
+ box-shadow: var(--box-shadow);
+}
+
+.search-box {
+ position: relative;
+ flex: 1;
+ min-width: 250px;
+}
+
+.search-input {
+ width: 100%;
+ padding: var(--spacing-md) var(--spacing-lg);
+ padding-left: 3rem;
+ border: 2px solid #eee;
+ border-radius: var(--border-radius-full);
+ font-size: var(--font-base);
+ transition: var(--transition);
+}
+
+.search-input:focus {
+ outline: none;
+ border-color: var(--primary-color);
+ box-shadow: 0 0 0 3px rgba(255, 136, 0, 0.1);
+}
+
+.search-icon {
+ position: absolute;
+ left: var(--spacing-md);
+ top: 50%;
+ transform: translateY(-50%);
+ font-size: 1.2rem;
+}
+
+.view-options {
+ display: flex;
+ gap: var(--spacing-xs);
+}
+
+.view-btn {
+ width: 40px;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 2px solid #eee;
+ border-radius: var(--border-radius-sm);
+ background: var(--bg-white);
+ cursor: pointer;
+ font-size: 1.2rem;
+ transition: var(--transition);
+}
+
+.view-btn:hover {
+ border-color: var(--primary-color);
+}
+
+.view-btn.active {
+ background: var(--primary-color);
+ color: white;
+ border-color: transparent;
+}
+
+/* 통계 바 */
+.stats-bar {
+ display: flex;
+ gap: var(--spacing-lg);
+ margin-bottom: var(--spacing-lg);
+ font-size: var(--font-sm);
+ color: var(--text-secondary);
+}
+
+.stat-item strong {
+ color: var(--primary-color);
+ font-weight: 700;
+}
+
+/* 프랍 그리드 */
+.props-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: var(--spacing-lg);
+}
+
+.props-grid.list-view {
+ grid-template-columns: 1fr;
+}
+
+/* 프랍 카드 */
+.prop-card {
+ background: var(--bg-white);
+ border-radius: var(--border-radius);
+ overflow: hidden;
+ box-shadow: var(--box-shadow);
+ transition: var(--transition);
+ cursor: pointer;
+}
+
+.prop-card:hover {
+ transform: translateY(-5px);
+ box-shadow: var(--box-shadow-lg);
+}
+
+.card-thumbnail {
+ position: relative;
+ aspect-ratio: 1 / 1;
+ overflow: hidden;
+ background: linear-gradient(135deg, #2a2a2a 0%, #3a3a3a 100%);
+}
+
+.card-thumbnail img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ transition: transform 0.5s ease;
+ padding: 10px;
+}
+
+.prop-card:hover .card-thumbnail img {
+ transform: scale(1.05);
+}
+
+.card-thumbnail .no-thumbnail {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: linear-gradient(135deg, #e0e0e0 0%, #f5f5f5 100%);
+ color: var(--text-light);
+ font-size: 3rem;
+}
+
+.card-badge {
+ position: absolute;
+ top: var(--spacing-sm);
+ right: var(--spacing-sm);
+ padding: var(--spacing-xs) var(--spacing-sm);
+ background: rgba(255, 136, 0, 0.9);
+ color: white;
+ font-size: var(--font-xs);
+ font-weight: 500;
+ border-radius: var(--border-radius-sm);
+}
+
+.card-content {
+ padding: var(--spacing-md);
+}
+
+.card-title {
+ font-size: var(--font-base);
+ font-weight: 600;
+ color: var(--text-primary);
+ margin-bottom: var(--spacing-sm);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.card-info {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--spacing-xs);
+}
+
+.card-info-item {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 2px 8px;
+ background: var(--bg-gray);
+ color: var(--text-secondary);
+ font-size: var(--font-xs);
+ border-radius: var(--border-radius-sm);
+}
+
+.card-info-item .icon {
+ font-size: 0.8rem;
+}
+
+/* 리스트 뷰 카드 */
+.props-grid.list-view .prop-card {
+ display: flex;
+ flex-direction: row;
+}
+
+.props-grid.list-view .card-thumbnail {
+ width: 100px;
+ min-width: 100px;
+ aspect-ratio: auto;
+ height: 100px;
+}
+
+.props-grid.list-view .card-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+
+.props-grid.list-view .card-title {
+ font-size: var(--font-lg);
+}
+
+/* 로딩 */
+.loading-placeholder {
+ grid-column: 1 / -1;
+ text-align: center;
+ padding: var(--spacing-3xl);
+ color: var(--text-secondary);
+}
+
+.loading-spinner {
+ width: 50px;
+ height: 50px;
+ border: 4px solid #f0f0f0;
+ border-top-color: var(--primary-color);
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin: 0 auto var(--spacing-md);
+}
+
+@keyframes spin {
+ to { transform: rotate(360deg); }
+}
+
+/* 데이터 없음 / 검색 결과 없음 */
+.no-data,
+.no-results {
+ text-align: center;
+ padding: var(--spacing-3xl);
+ color: var(--text-secondary);
+}
+
+.no-data-icon,
+.no-results-icon {
+ font-size: 4rem;
+ margin-bottom: var(--spacing-md);
+}
+
+.no-data h3,
+.no-results h3 {
+ font-size: var(--font-2xl);
+ color: var(--text-primary);
+ margin-bottom: var(--spacing-sm);
+}
+
+/* 모달 */
+.image-modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 2000;
+ display: none;
+ align-items: center;
+ justify-content: center;
+}
+
+.image-modal.active {
+ display: flex;
+}
+
+.modal-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.9);
+ cursor: pointer;
+}
+
+.modal-content {
+ position: relative;
+ max-width: 90%;
+ max-height: 90%;
+ z-index: 1;
+}
+
+.modal-close {
+ position: absolute;
+ top: -40px;
+ right: 0;
+ width: 40px;
+ height: 40px;
+ background: transparent;
+ border: none;
+ color: white;
+ font-size: 2rem;
+ cursor: pointer;
+ transition: var(--transition);
+}
+
+.modal-close:hover {
+ color: var(--primary-color);
+}
+
+.modal-image {
+ max-width: 100%;
+ max-height: 60vh;
+ border-radius: var(--border-radius);
+ box-shadow: var(--box-shadow-lg);
+ background: #2a2a2a;
+ padding: 20px;
+}
+
+.modal-info {
+ background: var(--bg-white);
+ padding: var(--spacing-lg);
+ border-radius: 0 0 var(--border-radius) var(--border-radius);
+ margin-top: -5px;
+}
+
+.modal-info h3 {
+ font-size: var(--font-xl);
+ color: var(--text-primary);
+ margin-bottom: var(--spacing-md);
+}
+
+.modal-details {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--spacing-md);
+}
+
+.modal-detail-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: var(--spacing-sm) var(--spacing-md);
+ background: var(--bg-gray);
+ border-radius: var(--border-radius-sm);
+ color: var(--text-secondary);
+ font-size: var(--font-sm);
+}
+
+.modal-detail-item .icon {
+ font-size: 1.2rem;
+}
+
+.modal-detail-item strong {
+ color: var(--primary-color);
+}
+
+/* 반응형 */
+@media (max-width: 768px) {
+ .page-header h1 {
+ font-size: var(--font-3xl);
+ }
+
+ .filter-section {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .search-box {
+ min-width: 100%;
+ }
+
+ .view-options {
+ justify-content: center;
+ }
+
+ .props-grid {
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+ }
+
+ .props-grid.list-view .prop-card {
+ flex-direction: column;
+ }
+
+ .props-grid.list-view .card-thumbnail {
+ width: 100%;
+ height: auto;
+ aspect-ratio: 1 / 1;
+ }
+
+ .stats-bar {
+ justify-content: center;
+ }
+}
+
+@media (max-width: 480px) {
+ .props-grid {
+ grid-template-columns: repeat(2, 1fr);
+ gap: var(--spacing-md);
+ }
+}
diff --git a/data/props.json b/data/props.json
new file mode 100644
index 0000000..979e9b3
--- /dev/null
+++ b/data/props.json
@@ -0,0 +1,4 @@
+{
+ "lastUpdated": "2025-01-08T12:00:00",
+ "props": []
+}
diff --git a/js/props.js b/js/props.js
new file mode 100644
index 0000000..62817f7
--- /dev/null
+++ b/js/props.js
@@ -0,0 +1,308 @@
+/**
+ * 프랍 라이브러리 JavaScript
+ * Unity에서 업로드된 JSON 데이터를 기반으로 프랍 목록을 표시
+ */
+
+(function() {
+ 'use strict';
+
+ // 데이터 파일 경로
+ const DATA_PATH = 'data/props.json';
+
+ // 상태
+ let propsData = [];
+ let filteredData = [];
+ let currentView = 'grid';
+ let searchQuery = '';
+
+ // DOM 요소
+ const elements = {
+ grid: document.getElementById('propsGrid'),
+ searchInput: document.getElementById('searchInput'),
+ totalCount: document.getElementById('totalCount'),
+ filteredCount: document.getElementById('filteredCount'),
+ lastUpdated: document.getElementById('lastUpdated'),
+ noData: document.getElementById('noData'),
+ noResults: document.getElementById('noResults'),
+ imageModal: document.getElementById('imageModal'),
+ modalImage: document.getElementById('modalImage'),
+ modalTitle: document.getElementById('modalTitle'),
+ modalDetails: document.getElementById('modalDetails')
+ };
+
+ /**
+ * 초기화
+ */
+ async function init() {
+ await loadData();
+ setupEventListeners();
+ }
+
+ /**
+ * 데이터 로드
+ */
+ async function loadData() {
+ try {
+ const response = await fetch(DATA_PATH + '?t=' + Date.now());
+
+ if (!response.ok) {
+ throw new Error('데이터를 찾을 수 없습니다');
+ }
+
+ const data = await response.json();
+ propsData = data.props || [];
+
+ // 마지막 업데이트 시간 표시
+ if (data.lastUpdated) {
+ const date = new Date(data.lastUpdated);
+ elements.lastUpdated.textContent = `마지막 업데이트: ${formatDate(date)}`;
+ }
+
+ // 통계 업데이트
+ elements.totalCount.textContent = propsData.length;
+
+ // 데이터 렌더링
+ filterAndRender();
+
+ } catch (error) {
+ console.error('데이터 로드 실패:', error);
+ showNoData();
+ }
+ }
+
+ /**
+ * 이벤트 리스너 설정
+ */
+ function setupEventListeners() {
+ // 검색
+ elements.searchInput.addEventListener('input', debounce((e) => {
+ searchQuery = e.target.value.toLowerCase();
+ filterAndRender();
+ }, 300));
+
+ // 뷰 전환
+ document.querySelectorAll('.view-btn').forEach(btn => {
+ btn.addEventListener('click', () => {
+ document.querySelectorAll('.view-btn').forEach(b => b.classList.remove('active'));
+ btn.classList.add('active');
+ currentView = btn.dataset.view;
+ updateViewMode();
+ });
+ });
+
+ // 모달 닫기
+ elements.imageModal.querySelector('.modal-overlay').addEventListener('click', closeModal);
+ elements.imageModal.querySelector('.modal-close').addEventListener('click', closeModal);
+
+ // ESC 키로 모달 닫기
+ document.addEventListener('keydown', (e) => {
+ if (e.key === 'Escape') closeModal();
+ });
+ }
+
+ /**
+ * 필터링 및 렌더링
+ */
+ function filterAndRender() {
+ // 필터링
+ filteredData = propsData.filter(prop => {
+ // 검색 필터
+ if (searchQuery) {
+ const searchTarget = prop.name.toLowerCase();
+ if (!searchTarget.includes(searchQuery)) {
+ return false;
+ }
+ }
+
+ return true;
+ });
+
+ // 통계 업데이트
+ elements.filteredCount.textContent = filteredData.length;
+
+ // 렌더링
+ render();
+ }
+
+ /**
+ * 카드 렌더링
+ */
+ function render() {
+ // 로딩/에러 상태 숨기기
+ const loadingPlaceholder = elements.grid.querySelector('.loading-placeholder');
+ if (loadingPlaceholder) {
+ loadingPlaceholder.remove();
+ }
+
+ elements.noData.style.display = 'none';
+ elements.noResults.style.display = 'none';
+
+ // 데이터 없음
+ if (propsData.length === 0) {
+ elements.grid.innerHTML = '';
+ showNoData();
+ return;
+ }
+
+ // 검색 결과 없음
+ if (filteredData.length === 0) {
+ elements.grid.innerHTML = '';
+ elements.noResults.style.display = 'block';
+ return;
+ }
+
+ // 카드 생성
+ elements.grid.innerHTML = filteredData.map(prop => createCard(prop)).join('');
+
+ // 카드 클릭 이벤트
+ elements.grid.querySelectorAll('.prop-card').forEach((card, index) => {
+ card.addEventListener('click', () => openModal(filteredData[index]));
+ });
+
+ // 뷰 모드 적용
+ updateViewMode();
+ }
+
+ /**
+ * 카드 HTML 생성
+ */
+ function createCard(prop) {
+ const thumbnailHtml = prop.thumbnailUrl
+ ? ``
+ : `
스트리밍글 서비스에서 사용 가능한 프랍(소품) 목록입니다
+ +프랍 데이터를 불러오는 중...
+