Refactor to 5-step workflow (new → mvp → ui → policy → visualize)
- Remove old commands: spec, customer, sales, merge, design - Add new commands: ui, policy, visualize - Update mvp to include landing page generation - Add templates for mockup, policy, and UI documents - Simplify output path (remove [project] subfolder) - Rewrite README focused on usage 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
193
.specify/templates/mockup-base.html
Normal file
193
.specify/templates/mockup-base.html
Normal file
@@ -0,0 +1,193 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>[화면명] - [서비스명]</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================ -->
|
||||
<header class="header" id="[screen]-header">
|
||||
<div class="header-left">
|
||||
<!-- 로고 또는 뒤로가기 -->
|
||||
<span class="logo">[서비스명]</span>
|
||||
<!-- <button class="btn-back" onclick="history.back()">←</button> -->
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<!-- 타이틀 (선택적) -->
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<!-- [MN-001] 상태 표시 -->
|
||||
<div class="status-badge">
|
||||
<span>💬</span>
|
||||
<span id="token-count">127</span>
|
||||
</div>
|
||||
<button class="btn-icon" onclick="location.href='settings.html'">⚙️</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- Main Content -->
|
||||
<!-- ============================================ -->
|
||||
<main class="content" id="[screen]-content">
|
||||
|
||||
<!-- 섹션 예시 -->
|
||||
<section class="section">
|
||||
<h2 class="section-title">[섹션 제목]</h2>
|
||||
|
||||
<!-- 카드 예시 -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">[카드 제목]</span>
|
||||
<span class="card-badge">[뱃지]</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>[카드 내용]</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-secondary">[버튼1]</button>
|
||||
<button class="btn btn-primary">[버튼2]</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<!-- 리스트 예시 -->
|
||||
<section class="section">
|
||||
<div class="list">
|
||||
<div class="list-item" onclick="location.href='[상세].html'">
|
||||
<div class="list-item-avatar">
|
||||
<img src="https://via.placeholder.com/48" alt="">
|
||||
</div>
|
||||
<div class="list-item-content">
|
||||
<div class="list-item-title">[아이템 제목]</div>
|
||||
<div class="list-item-subtitle">[부제목/설명]</div>
|
||||
</div>
|
||||
<div class="list-item-meta">
|
||||
<span class="time">2분 전</span>
|
||||
<span class="badge">3</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 메인 액션 버튼 (센터 탭용) -->
|
||||
<!--
|
||||
<div class="main-action">
|
||||
<button class="btn btn-large btn-primary pulse" onclick="showModal('modal-action')">
|
||||
▶ [메인 액션]
|
||||
</button>
|
||||
</div>
|
||||
-->
|
||||
|
||||
</main>
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- Tab Bar -->
|
||||
<!-- ============================================ -->
|
||||
<nav class="tab-bar">
|
||||
<a href="[tab1].html" class="tab-item">
|
||||
<span class="tab-icon">[아이콘1]</span>
|
||||
<span class="tab-label">[탭1]</span>
|
||||
<span class="tab-badge" style="display:none;">0</span>
|
||||
</a>
|
||||
<a href="[tab2].html" class="tab-item">
|
||||
<span class="tab-icon">[아이콘2]</span>
|
||||
<span class="tab-label">[탭2]</span>
|
||||
<span class="tab-badge">3</span>
|
||||
</a>
|
||||
<a href="[tab3].html" class="tab-item center active">
|
||||
<span class="tab-icon">[센터아이콘]</span>
|
||||
<span class="tab-label">[탭3]</span>
|
||||
</a>
|
||||
<a href="[tab4].html" class="tab-item">
|
||||
<span class="tab-icon">[아이콘4]</span>
|
||||
<span class="tab-label">[탭4]</span>
|
||||
</a>
|
||||
<a href="[tab5].html" class="tab-item">
|
||||
<span class="tab-icon">[아이콘5]</span>
|
||||
<span class="tab-label">[탭5]</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- Overlay (for modals/sheets) -->
|
||||
<!-- ============================================ -->
|
||||
<div class="overlay" id="overlay" onclick="closeAll()"></div>
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- Modal Example -->
|
||||
<!-- ============================================ -->
|
||||
<div class="modal" id="modal-example">
|
||||
<div class="modal-icon">🎉</div>
|
||||
<h3 class="modal-title">[모달 제목]</h3>
|
||||
<p class="modal-message">[모달 메시지]</p>
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-secondary" onclick="closeAll()">취소</button>
|
||||
<button class="btn btn-primary" onclick="handleConfirm()">확인</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- Bottom Sheet Example -->
|
||||
<!-- ============================================ -->
|
||||
<div class="bottom-sheet" id="bs-example">
|
||||
<div class="bs-handle"></div>
|
||||
<div class="bs-header">
|
||||
<h3>[바텀시트 제목]</h3>
|
||||
<button class="btn-close" onclick="closeAll()">✕</button>
|
||||
</div>
|
||||
<div class="bs-content">
|
||||
<div class="bs-option" onclick="selectOption(1)">
|
||||
<span class="bs-option-icon">📝</span>
|
||||
<span class="bs-option-label">[옵션 1]</span>
|
||||
</div>
|
||||
<div class="bs-option" onclick="selectOption(2)">
|
||||
<span class="bs-option-icon">🗑️</span>
|
||||
<span class="bs-option-label">[옵션 2]</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- Toast -->
|
||||
<!-- ============================================ -->
|
||||
<div class="toast" id="toast">[토스트 메시지]</div>
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- Scripts -->
|
||||
<!-- ============================================ -->
|
||||
<script src="scripts.js"></script>
|
||||
<script>
|
||||
// ============================================
|
||||
// Page-specific Scripts
|
||||
// ============================================
|
||||
|
||||
function handleConfirm() {
|
||||
// 확인 버튼 처리
|
||||
closeAll();
|
||||
showToast('완료되었습니다!');
|
||||
}
|
||||
|
||||
function selectOption(option) {
|
||||
// 옵션 선택 처리
|
||||
closeAll();
|
||||
showToast(`옵션 ${option} 선택됨`);
|
||||
}
|
||||
|
||||
// 페이지 로드 시 실행
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 초기화 코드
|
||||
console.log('[screen] loaded');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
298
.specify/templates/mockup-scripts.js
Normal file
298
.specify/templates/mockup-scripts.js
Normal file
@@ -0,0 +1,298 @@
|
||||
/* ============================================
|
||||
[서비스명] - Mockup Scripts
|
||||
============================================ */
|
||||
|
||||
// ============================================
|
||||
// Modal Functions
|
||||
// ============================================
|
||||
function showModal(id) {
|
||||
document.getElementById('overlay').classList.add('active');
|
||||
document.getElementById(id).classList.add('active');
|
||||
}
|
||||
|
||||
function showBottomSheet(id) {
|
||||
document.getElementById('overlay').classList.add('active');
|
||||
document.getElementById(id).classList.add('active');
|
||||
}
|
||||
|
||||
function closeAll() {
|
||||
document.getElementById('overlay').classList.remove('active');
|
||||
document.querySelectorAll('.modal, .bottom-sheet').forEach(el => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
}
|
||||
|
||||
// ESC 키로 닫기
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeAll();
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Toast Function
|
||||
// ============================================
|
||||
function showToast(message, type = 'default', duration = 2000) {
|
||||
const toast = document.getElementById('toast');
|
||||
if (toast) {
|
||||
toast.textContent = message;
|
||||
toast.className = 'toast';
|
||||
if (type !== 'default') {
|
||||
toast.classList.add(type);
|
||||
}
|
||||
toast.classList.add('active');
|
||||
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('active');
|
||||
}, duration);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Tab Badge Update
|
||||
// ============================================
|
||||
function updateBadge(tabSelector, count) {
|
||||
const badge = document.querySelector(`${tabSelector} .tab-badge`);
|
||||
if (badge) {
|
||||
badge.textContent = count;
|
||||
badge.style.display = count > 0 ? 'block' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Status Badge Update (Header)
|
||||
// ============================================
|
||||
function updateStatusBadge(id, value) {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.textContent = value;
|
||||
|
||||
// 색상 변경 (잔량에 따라)
|
||||
const badge = element.closest('.status-badge');
|
||||
if (badge) {
|
||||
badge.classList.remove('low', 'critical', 'empty');
|
||||
if (value <= 0) {
|
||||
badge.classList.add('empty');
|
||||
} else if (value < 10) {
|
||||
badge.classList.add('critical');
|
||||
} else if (value < 50) {
|
||||
badge.classList.add('low');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Loading State
|
||||
// ============================================
|
||||
function showLoading(containerId) {
|
||||
const container = document.getElementById(containerId);
|
||||
if (container) {
|
||||
container.innerHTML = `
|
||||
<div class="loading-state">
|
||||
<div class="spinner"></div>
|
||||
<p>로딩 중...</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function hideLoading(containerId, content) {
|
||||
const container = document.getElementById(containerId);
|
||||
if (container && content) {
|
||||
container.innerHTML = content;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Empty State
|
||||
// ============================================
|
||||
function showEmptyState(containerId, icon, title, message) {
|
||||
const container = document.getElementById(containerId);
|
||||
if (container) {
|
||||
container.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">${icon}</div>
|
||||
<h3 class="empty-title">${title}</h3>
|
||||
<p class="empty-message">${message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Confirm Dialog
|
||||
// ============================================
|
||||
function showConfirm(title, message, onConfirm, onCancel) {
|
||||
// 동적 모달 생성
|
||||
const modalId = 'modal-confirm-' + Date.now();
|
||||
const modalHtml = `
|
||||
<div class="modal" id="${modalId}">
|
||||
<div class="modal-icon">❓</div>
|
||||
<h3 class="modal-title">${title}</h3>
|
||||
<p class="modal-message">${message}</p>
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-secondary" onclick="handleConfirmCancel('${modalId}')">취소</button>
|
||||
<button class="btn btn-primary" onclick="handleConfirmOk('${modalId}')">확인</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
||||
|
||||
// 콜백 저장
|
||||
window._confirmCallbacks = window._confirmCallbacks || {};
|
||||
window._confirmCallbacks[modalId] = { onConfirm, onCancel };
|
||||
|
||||
// 모달 표시
|
||||
setTimeout(() => showModal(modalId), 10);
|
||||
}
|
||||
|
||||
function handleConfirmOk(modalId) {
|
||||
const callbacks = window._confirmCallbacks[modalId];
|
||||
closeAll();
|
||||
document.getElementById(modalId).remove();
|
||||
if (callbacks && callbacks.onConfirm) {
|
||||
callbacks.onConfirm();
|
||||
}
|
||||
delete window._confirmCallbacks[modalId];
|
||||
}
|
||||
|
||||
function handleConfirmCancel(modalId) {
|
||||
const callbacks = window._confirmCallbacks[modalId];
|
||||
closeAll();
|
||||
document.getElementById(modalId).remove();
|
||||
if (callbacks && callbacks.onCancel) {
|
||||
callbacks.onCancel();
|
||||
}
|
||||
delete window._confirmCallbacks[modalId];
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Form Validation Helpers
|
||||
// ============================================
|
||||
function validateRequired(value, fieldName) {
|
||||
if (!value || value.trim() === '') {
|
||||
return `${fieldName}을(를) 입력해주세요.`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateLength(value, fieldName, min, max) {
|
||||
const len = value ? value.length : 0;
|
||||
if (min && len < min) {
|
||||
return `${fieldName}은(는) ${min}자 이상이어야 합니다.`;
|
||||
}
|
||||
if (max && len > max) {
|
||||
return `${fieldName}은(는) ${max}자 이하여야 합니다.`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Local Storage Helpers (Mock Data)
|
||||
// ============================================
|
||||
function saveData(key, data) {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(data));
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Storage save error:', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function loadData(key, defaultValue = null) {
|
||||
try {
|
||||
const data = localStorage.getItem(key);
|
||||
return data ? JSON.parse(data) : defaultValue;
|
||||
} catch (e) {
|
||||
console.error('Storage load error:', e);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Animation Helpers
|
||||
// ============================================
|
||||
function fadeIn(element, duration = 300) {
|
||||
element.style.opacity = 0;
|
||||
element.style.display = 'block';
|
||||
|
||||
let start = null;
|
||||
function step(timestamp) {
|
||||
if (!start) start = timestamp;
|
||||
const progress = Math.min((timestamp - start) / duration, 1);
|
||||
element.style.opacity = progress;
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
}
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
|
||||
function fadeOut(element, duration = 300) {
|
||||
let start = null;
|
||||
function step(timestamp) {
|
||||
if (!start) start = timestamp;
|
||||
const progress = Math.min((timestamp - start) / duration, 1);
|
||||
element.style.opacity = 1 - progress;
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(step);
|
||||
} else {
|
||||
element.style.display = 'none';
|
||||
}
|
||||
}
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Utility Functions
|
||||
// ============================================
|
||||
function formatNumber(num) {
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
}
|
||||
|
||||
function formatTime(date) {
|
||||
const now = new Date();
|
||||
const diff = Math.floor((now - date) / 1000);
|
||||
|
||||
if (diff < 60) return '방금 전';
|
||||
if (diff < 3600) return `${Math.floor(diff / 60)}분 전`;
|
||||
if (diff < 86400) return `${Math.floor(diff / 3600)}시간 전`;
|
||||
if (diff < 604800) return `${Math.floor(diff / 86400)}일 전`;
|
||||
|
||||
return date.toLocaleDateString('ko-KR');
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Debug Mode
|
||||
// ============================================
|
||||
const DEBUG = true;
|
||||
|
||||
function log(...args) {
|
||||
if (DEBUG) {
|
||||
console.log('[Mockup]', ...args);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Initialize
|
||||
// ============================================
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
log('Mockup scripts loaded');
|
||||
|
||||
// 현재 탭 활성화 표시
|
||||
const currentPage = window.location.pathname.split('/').pop().replace('.html', '');
|
||||
document.querySelectorAll('.tab-item').forEach(tab => {
|
||||
const href = tab.getAttribute('href');
|
||||
if (href && href.includes(currentPage)) {
|
||||
tab.classList.add('active');
|
||||
} else if (tab.classList.contains('active') && !href.includes(currentPage)) {
|
||||
// center 탭이 아닌 경우에만 active 제거
|
||||
if (!tab.classList.contains('center')) {
|
||||
tab.classList.remove('active');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
704
.specify/templates/mockup-styles.css
Normal file
704
.specify/templates/mockup-styles.css
Normal file
@@ -0,0 +1,704 @@
|
||||
/* ============================================
|
||||
[서비스명] - Mockup Styles
|
||||
============================================ */
|
||||
|
||||
/* Reset & Base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
/* Colors - 다크 테마 기본 */
|
||||
--bg-primary: #0D0D1A;
|
||||
--bg-secondary: #1A1A2E;
|
||||
--bg-card: #252538;
|
||||
--bg-input: #2D2D42;
|
||||
--text-primary: #FFFFFF;
|
||||
--text-secondary: #9999AA;
|
||||
--text-muted: #666677;
|
||||
--accent-primary: #6C5CE7;
|
||||
--accent-secondary: #45B7D1;
|
||||
--accent-pink: #FF6B9D;
|
||||
--accent-gold: #FFD93D;
|
||||
--danger: #FF6B6B;
|
||||
--success: #4ECB71;
|
||||
--warning: #FFB84D;
|
||||
--border: #2D2D3A;
|
||||
|
||||
/* Sizing */
|
||||
--header-height: 56px;
|
||||
--tab-height: 64px;
|
||||
--safe-area-top: env(safe-area-inset-top, 0px);
|
||||
--safe-area-bottom: env(safe-area-inset-bottom, 0px);
|
||||
--radius-sm: 8px;
|
||||
--radius-md: 12px;
|
||||
--radius-lg: 16px;
|
||||
--radius-xl: 20px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
App Container
|
||||
============================================ */
|
||||
.app {
|
||||
max-width: 430px;
|
||||
margin: 0 auto;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Header
|
||||
============================================ */
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
max-width: 430px;
|
||||
height: var(--header-height);
|
||||
background: var(--bg-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
padding-top: var(--safe-area-top);
|
||||
z-index: 100;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.header-left,
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.header-center {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-back {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-primary);
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
background: var(--bg-card);
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Tab Bar
|
||||
============================================ */
|
||||
.tab-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
max-width: 430px;
|
||||
height: var(--tab-height);
|
||||
background: var(--bg-primary);
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
padding-bottom: var(--safe-area-bottom);
|
||||
border-top: 1px solid var(--border);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px 16px;
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.tab-item.center {
|
||||
transform: scale(1.15);
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.tab-item.center .tab-icon {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
font-size: 20px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.tab-label {
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tab-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 8px;
|
||||
background: var(--danger);
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
min-width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Content Area
|
||||
============================================ */
|
||||
.content {
|
||||
padding-top: calc(var(--header-height) + var(--safe-area-top));
|
||||
padding-bottom: calc(var(--tab-height) + var(--safe-area-bottom));
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.content-no-header {
|
||||
padding-top: var(--safe-area-top);
|
||||
}
|
||||
|
||||
.content-no-tab {
|
||||
padding-bottom: var(--safe-area-bottom);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Section
|
||||
============================================ */
|
||||
.section {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Cards
|
||||
============================================ */
|
||||
.card {
|
||||
background: var(--bg-card);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card-badge {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
List Items
|
||||
============================================ */
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background: var(--bg-card);
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.list-item:hover {
|
||||
background: var(--bg-input);
|
||||
}
|
||||
|
||||
.list-item-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.list-item-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.list-item-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.list-item-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.list-item-subtitle {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.list-item-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.list-item-meta .time {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.list-item-meta .badge {
|
||||
background: var(--danger);
|
||||
color: white;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
min-width: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Buttons
|
||||
============================================ */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12px 24px;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: none;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #5a4bd4;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--bg-input);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: var(--danger);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-large {
|
||||
padding: 16px 32px;
|
||||
font-size: 16px;
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Pulse Animation */
|
||||
.pulse {
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(108, 92, 231, 0.4);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 20px rgba(108, 92, 231, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(108, 92, 231, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Main Action (for center tab)
|
||||
============================================ */
|
||||
.main-action {
|
||||
position: fixed;
|
||||
bottom: calc(var(--tab-height) + 20px + var(--safe-area-bottom));
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: calc(100% - 64px);
|
||||
max-width: 366px;
|
||||
}
|
||||
|
||||
.main-action .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Overlay
|
||||
============================================ */
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
z-index: 1000;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.overlay.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Modal
|
||||
============================================ */
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(0.9);
|
||||
background: var(--bg-secondary);
|
||||
border-radius: var(--radius-xl);
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
z-index: 1001;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s;
|
||||
max-width: 320px;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.modal.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
|
||||
.modal-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.modal-message {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 24px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.modal-actions .btn {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Bottom Sheet
|
||||
============================================ */
|
||||
.bottom-sheet {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(100%);
|
||||
width: 100%;
|
||||
max-width: 430px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
|
||||
z-index: 1001;
|
||||
transition: transform 0.3s;
|
||||
padding-bottom: var(--safe-area-bottom);
|
||||
}
|
||||
|
||||
.bottom-sheet.active {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
|
||||
.bs-handle {
|
||||
width: 40px;
|
||||
height: 4px;
|
||||
background: var(--text-muted);
|
||||
border-radius: 2px;
|
||||
margin: 12px auto;
|
||||
}
|
||||
|
||||
.bs-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 20px 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.bs-header h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.bs-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.bs-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.bs-option:hover {
|
||||
background: var(--bg-card);
|
||||
}
|
||||
|
||||
.bs-option-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.bs-option-label {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Toast
|
||||
============================================ */
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: calc(var(--tab-height) + 20px + var(--safe-area-bottom));
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(100px);
|
||||
background: var(--bg-card);
|
||||
color: var(--text-primary);
|
||||
padding: 12px 24px;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
opacity: 0;
|
||||
transition: all 0.3s;
|
||||
z-index: 1002;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.toast.active {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
|
||||
.toast.success {
|
||||
background: var(--success);
|
||||
}
|
||||
|
||||
.toast.error {
|
||||
background: var(--danger);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Forms
|
||||
============================================ */
|
||||
.input {
|
||||
width: 100%;
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 12px 16px;
|
||||
font-size: 15px;
|
||||
color: var(--text-primary);
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.input::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Empty State
|
||||
============================================ */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 48px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: 16px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.empty-message {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Utilities
|
||||
============================================ */
|
||||
.text-center { text-align: center; }
|
||||
.text-left { text-align: left; }
|
||||
.text-right { text-align: right; }
|
||||
.text-primary { color: var(--text-primary); }
|
||||
.text-secondary { color: var(--text-secondary); }
|
||||
.text-muted { color: var(--text-muted); }
|
||||
.text-accent { color: var(--accent-primary); }
|
||||
.text-danger { color: var(--danger); }
|
||||
.text-success { color: var(--success); }
|
||||
.mt-8 { margin-top: 8px; }
|
||||
.mt-16 { margin-top: 16px; }
|
||||
.mb-8 { margin-bottom: 8px; }
|
||||
.mb-16 { margin-bottom: 16px; }
|
||||
.p-16 { padding: 16px; }
|
||||
.hidden { display: none; }
|
||||
.flex { display: flex; }
|
||||
.flex-1 { flex: 1; }
|
||||
.gap-8 { gap: 8px; }
|
||||
.gap-16 { gap: 16px; }
|
||||
198
.specify/templates/policy-content-rating.md
Normal file
198
.specify/templates/policy-content-rating.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# 콘텐츠 등급 정책 (Content Rating)
|
||||
|
||||
> 코드: `CR` | 연령 제한, 콘텐츠 등급, 경고 표시 정책
|
||||
|
||||
---
|
||||
|
||||
## CR-001: [정책명 - 예: 연령 등급 시스템]
|
||||
|
||||
### 정책 내용
|
||||
|
||||
| 등급 | 대상 연령 | 아이콘 | 설명 |
|
||||
|-----|---------|-------|------|
|
||||
| 전체 | 모든 연령 | 🟢 | 모든 이용자 이용 가능 |
|
||||
| 12+ | 12세 이상 | 🟡 | [설명] |
|
||||
| 15+ | 15세 이상 | 🟠 | [설명] |
|
||||
| 19+ | 19세 이상 | 🔴 | 성인만 이용 가능 |
|
||||
|
||||
### 적용 화면
|
||||
- `[화면1].html` - 콘텐츠 목록
|
||||
- `[화면2].html` - 콘텐츠 상세
|
||||
- `[화면3].html` - 검색 결과
|
||||
|
||||
### UI 영향
|
||||
|
||||
| 화면 | 위치 | 표시 방식 | mockup |
|
||||
|-----|------|----------|--------|
|
||||
| [화면1] | [위치] | [표시 방식] | [파일명].html |
|
||||
|
||||
### UI 표시
|
||||
|
||||
```
|
||||
[콘텐츠 카드 - 등급 배지]
|
||||
┌─────────────────────────────────────┐
|
||||
│ [썸네일] │
|
||||
│ 🔴 19+ │
|
||||
│ [제목] │
|
||||
│ [설명] │
|
||||
└─────────────────────────────────────┘
|
||||
|
||||
[등급 필터]
|
||||
┌─────────────────────────────────────┐
|
||||
│ 등급 필터 │
|
||||
│ ☑️ 전체 ☑️ 12+ ☑️ 15+ ☐ 19+ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CR-002: [정책명 - 예: 연령 인증]
|
||||
|
||||
### 정책 내용
|
||||
- [인증 방법 1]
|
||||
- [인증 방법 2]
|
||||
- [인증 유효 기간]
|
||||
|
||||
### 적용 화면
|
||||
- `[화면1].html` - 인증 화면
|
||||
- `[화면2].html` - 성인 콘텐츠 진입
|
||||
|
||||
### 인증 플로우
|
||||
|
||||
```
|
||||
[미인증 상태]
|
||||
│
|
||||
└─→ 19+ 콘텐츠 접근 시도
|
||||
│
|
||||
└─→ 인증 필요 팝업
|
||||
│
|
||||
├─→ [인증하기] → 인증 화면
|
||||
│ │
|
||||
│ └─→ 인증 완료 → 콘텐츠 접근
|
||||
│
|
||||
└─→ [취소] → 이전 화면
|
||||
```
|
||||
|
||||
### UI 표시
|
||||
|
||||
```
|
||||
[연령 인증 필요 팝업]
|
||||
┌─────────────────────────────────────┐
|
||||
│ 🔞 성인 인증이 필요합니다 │
|
||||
│ │
|
||||
│ 이 콘텐츠는 19세 이상만 │
|
||||
│ 이용할 수 있습니다. │
|
||||
│ │
|
||||
│ [취소] [인증하기] │
|
||||
└─────────────────────────────────────┘
|
||||
|
||||
[인증 화면]
|
||||
┌─────────────────────────────────────┐
|
||||
│ 🔐 성인 인증 │
|
||||
├─────────────────────────────────────┤
|
||||
│ │
|
||||
│ 인증 방법을 선택해주세요 │
|
||||
│ │
|
||||
│ ┌─────────────────────────────┐ │
|
||||
│ │ 📱 휴대폰 본인인증 │ │
|
||||
│ └─────────────────────────────┘ │
|
||||
│ ┌─────────────────────────────┐ │
|
||||
│ │ 💳 신용카드 인증 │ │
|
||||
│ └─────────────────────────────┘ │
|
||||
│ ┌─────────────────────────────┐ │
|
||||
│ │ 🏦 아이핀 인증 │ │
|
||||
│ └─────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CR-003: [정책명 - 예: 콘텐츠 경고]
|
||||
|
||||
### 정책 내용
|
||||
|
||||
| 경고 유형 | 아이콘 | 설명 |
|
||||
|---------|-------|------|
|
||||
| 폭력성 | ⚔️ | 폭력적인 장면 포함 |
|
||||
| 선정성 | 💋 | 선정적인 내용 포함 |
|
||||
| 공포 | 👻 | 공포/호러 요소 포함 |
|
||||
| 언어 | 🗣️ | 거친 언어 포함 |
|
||||
| 약물 | 💊 | 약물/음주 관련 내용 |
|
||||
|
||||
### 적용 화면
|
||||
- `[화면1].html` - 콘텐츠 상세
|
||||
- `[화면2].html` - 콘텐츠 시작 전
|
||||
|
||||
### UI 표시
|
||||
|
||||
```
|
||||
[콘텐츠 경고 배지]
|
||||
┌─────────────────────────────────────┐
|
||||
│ ⚠️ 콘텐츠 경고 │
|
||||
│ ⚔️ 폭력성 👻 공포 │
|
||||
└─────────────────────────────────────┘
|
||||
|
||||
[시작 전 경고]
|
||||
┌─────────────────────────────────────┐
|
||||
│ ⚠️ 콘텐츠 안내 │
|
||||
│ │
|
||||
│ 이 콘텐츠에는 다음 요소가 │
|
||||
│ 포함되어 있습니다: │
|
||||
│ │
|
||||
│ ⚔️ 폭력적인 장면 │
|
||||
│ 👻 공포/호러 요소 │
|
||||
│ │
|
||||
│ 계속하시겠습니까? │
|
||||
│ │
|
||||
│ [돌아가기] [계속하기] │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CR-004: [정책명 - 예: 세이프 모드]
|
||||
|
||||
### 정책 내용
|
||||
- [세이프 모드 설명]
|
||||
- [필터링 대상]
|
||||
- [설정 방법]
|
||||
|
||||
### 적용 화면
|
||||
- `settings.html` - 세이프 모드 설정
|
||||
- 전체 콘텐츠 목록
|
||||
|
||||
### UI 표시
|
||||
|
||||
```
|
||||
[설정 화면]
|
||||
┌─────────────────────────────────────┐
|
||||
│ 🛡️ 세이프 모드 │
|
||||
│ │
|
||||
│ 민감한 콘텐츠를 필터링합니다 │
|
||||
│ │
|
||||
│ [토글 ON/OFF] │
|
||||
│ │
|
||||
│ • 19+ 콘텐츠 숨김 │
|
||||
│ • 성인 키워드 필터링 │
|
||||
│ • 민감한 이미지 블러 처리 │
|
||||
└─────────────────────────────────────┘
|
||||
|
||||
[세이프 모드 활성화 시 콘텐츠 표시]
|
||||
┌─────────────────────────────────────┐
|
||||
│ [블러 처리된 썸네일] │
|
||||
│ 🛡️ 세이프 모드에서 숨겨진 콘텐츠 │
|
||||
│ [설정에서 해제] │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 관련 mockup
|
||||
|
||||
| 정책 | mockup 파일 |
|
||||
|-----|------------|
|
||||
| CR-001 | [목록].html, [상세].html |
|
||||
| CR-002 | auth.html, [콘텐츠].html |
|
||||
| CR-003 | [콘텐츠].html |
|
||||
| CR-004 | settings.html |
|
||||
159
.specify/templates/policy-content.md
Normal file
159
.specify/templates/policy-content.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# 콘텐츠 정책 (Content)
|
||||
|
||||
> 코드: `CH` | 콘텐츠 생성, 표시, 관리 규칙
|
||||
|
||||
---
|
||||
|
||||
## CH-001: [정책명 - 예: 콘텐츠 생성 규칙]
|
||||
|
||||
### 정책 내용
|
||||
- [생성 규칙 1]
|
||||
- [생성 규칙 2]
|
||||
- [금지 항목]
|
||||
|
||||
### 적용 화면
|
||||
- `[화면1].html` - 생성 화면
|
||||
- `[화면2].html` - 수정 화면
|
||||
|
||||
### UI 영향
|
||||
|
||||
| 화면 | 위치 | 표시 방식 | mockup |
|
||||
|-----|------|----------|--------|
|
||||
| [화면1] | [위치] | [표시 방식] | [파일명].html |
|
||||
|
||||
### 입력 제한
|
||||
|
||||
```
|
||||
[텍스트 입력]
|
||||
┌─────────────────────────────────────┐
|
||||
│ [입력 필드] │
|
||||
│ │
|
||||
│ 0/[최대글자수] 자 │
|
||||
│ 💡 [입력 가이드] │
|
||||
└─────────────────────────────────────┘
|
||||
|
||||
[유효성 검사 실패]
|
||||
⚠️ [오류 메시지]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CH-002: [정책명 - 예: 콘텐츠 표시 규칙]
|
||||
|
||||
### 정책 내용
|
||||
- [표시 규칙 1]
|
||||
- [정렬 기준]
|
||||
- [필터링 기준]
|
||||
|
||||
### 적용 화면
|
||||
- `[화면1].html` - 목록
|
||||
- `[화면2].html` - 피드
|
||||
|
||||
### 표시 로직
|
||||
|
||||
```javascript
|
||||
function displayContent(items) {
|
||||
return items
|
||||
.filter(item => item.isVisible)
|
||||
.sort((a, b) => b.createdAt - a.createdAt)
|
||||
.slice(0, ITEMS_PER_PAGE);
|
||||
}
|
||||
```
|
||||
|
||||
### UI 레이아웃
|
||||
|
||||
```
|
||||
[목록 뷰]
|
||||
┌─────────────────────────────────────┐
|
||||
│ [필터/정렬 옵션] │
|
||||
├─────────────────────────────────────┤
|
||||
│ [콘텐츠 카드 1] │
|
||||
│ [콘텐츠 카드 2] │
|
||||
│ [콘텐츠 카드 3] │
|
||||
│ ... │
|
||||
├─────────────────────────────────────┤
|
||||
│ [더 보기 / 무한스크롤] │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CH-003: [정책명 - 예: 콘텐츠 삭제/숨김]
|
||||
|
||||
### 정책 내용
|
||||
- [삭제 조건]
|
||||
- [숨김 처리]
|
||||
- [복구 가능 여부]
|
||||
|
||||
### 적용 화면
|
||||
- `[화면1].html` - 상세 화면
|
||||
- `[화면2].html` - 관리 화면
|
||||
|
||||
### UI 표시
|
||||
|
||||
```
|
||||
[삭제 확인]
|
||||
┌─────────────────────────────────────┐
|
||||
│ ⚠️ 삭제하시겠습니까? │
|
||||
│ │
|
||||
│ [삭제 영향 설명] │
|
||||
│ │
|
||||
│ [취소] [삭제] │
|
||||
└─────────────────────────────────────┘
|
||||
|
||||
[삭제된 콘텐츠 표시]
|
||||
┌─────────────────────────────────────┐
|
||||
│ 🗑️ 삭제된 콘텐츠입니다 │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CH-004: [정책명 - 예: 신고/차단]
|
||||
|
||||
### 정책 내용
|
||||
- [신고 카테고리]
|
||||
- [처리 프로세스]
|
||||
- [차단 효과]
|
||||
|
||||
### 신고 카테고리
|
||||
|
||||
| 카테고리 | 설명 | 처리 |
|
||||
|---------|-----|------|
|
||||
| 스팸 | 광고성 콘텐츠 | [처리 방식] |
|
||||
| 욕설/비하 | 부적절한 언어 | [처리 방식] |
|
||||
| 불법 콘텐츠 | 법률 위반 | [처리 방식] |
|
||||
| 기타 | 기타 사유 | [처리 방식] |
|
||||
|
||||
### UI 표시
|
||||
|
||||
```
|
||||
[신고 바텀시트]
|
||||
┌─────────────────────────────────────┐
|
||||
│ 🚨 신고 사유 선택 │
|
||||
├─────────────────────────────────────┤
|
||||
│ ○ 스팸/광고 │
|
||||
│ ○ 욕설/비하 │
|
||||
│ ○ 불법 콘텐츠 │
|
||||
│ ○ 기타 │
|
||||
├─────────────────────────────────────┤
|
||||
│ [추가 설명 입력 (선택)] │
|
||||
├─────────────────────────────────────┤
|
||||
│ [신고하기] │
|
||||
└─────────────────────────────────────┘
|
||||
|
||||
[신고 완료]
|
||||
✓ 신고가 접수되었습니다.
|
||||
검토 후 조치하겠습니다.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 관련 mockup
|
||||
|
||||
| 정책 | mockup 파일 |
|
||||
|-----|------------|
|
||||
| CH-001 | [생성화면].html |
|
||||
| CH-002 | [목록화면].html |
|
||||
| CH-003 | [상세화면].html |
|
||||
| CH-004 | [상세화면].html, [신고].html |
|
||||
175
.specify/templates/policy-gameplay.md
Normal file
175
.specify/templates/policy-gameplay.md
Normal file
@@ -0,0 +1,175 @@
|
||||
# 서비스 규칙 (Gameplay)
|
||||
|
||||
> 코드: `GM` | 핵심 서비스 규칙 및 제한사항
|
||||
|
||||
---
|
||||
|
||||
## GM-001: [정책명 - 예: 시간 제한]
|
||||
|
||||
### 정책 내용
|
||||
- [핵심 정책 설명 1]
|
||||
- [핵심 정책 설명 2]
|
||||
|
||||
### 적용 화면
|
||||
- `[화면1].html` - [용도]
|
||||
- `[화면2].html` - [용도]
|
||||
|
||||
### UI 영향
|
||||
|
||||
| 화면 | 위치 | 표시 방식 | mockup |
|
||||
|-----|------|----------|--------|
|
||||
| [화면1] | [위치] | [표시 방식] | [파일명].html |
|
||||
| [화면2] | [위치] | [표시 방식] | [파일명].html |
|
||||
|
||||
### 상태별 UI
|
||||
|
||||
```
|
||||
[활성 상태]
|
||||
┌─────────────────────────────────────┐
|
||||
│ ✓ 이용 가능 │
|
||||
│ [버튼 활성화] │
|
||||
└─────────────────────────────────────┘
|
||||
|
||||
[비활성 상태]
|
||||
┌─────────────────────────────────────┐
|
||||
│ ⏰ 이용 불가 │
|
||||
│ [제한 사유 표시] │
|
||||
│ [버튼 비활성화] │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GM-002: [정책명 - 예: 일일 제한]
|
||||
|
||||
### 정책 내용
|
||||
- [일일 제한 설명 1]
|
||||
- [일일 제한 설명 2]
|
||||
- [리셋 시간]
|
||||
|
||||
### 적용 화면
|
||||
- `[화면1].html` - [용도]
|
||||
|
||||
### UI 표시
|
||||
|
||||
```
|
||||
[일반 상태]
|
||||
오늘 남은 횟수: 3/5회
|
||||
|
||||
[제한 도달]
|
||||
⚠️ 오늘 이용 횟수를 모두 사용했습니다.
|
||||
내일 00:00에 초기화됩니다.
|
||||
|
||||
[프리미엄 유저]
|
||||
✓ 무제한 이용 가능 (프리미엄)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GM-003: [정책명 - 예: 레벨 제한]
|
||||
|
||||
### 정책 내용
|
||||
|
||||
| 레벨 | 해금 기능 | 조건 |
|
||||
|-----|---------|------|
|
||||
| 1 | [기본 기능] | 가입 시 |
|
||||
| 5 | [기능1] | [조건] |
|
||||
| 10 | [기능2] | [조건] |
|
||||
| 20 | [기능3] | [조건] |
|
||||
|
||||
### 적용 화면
|
||||
- `[화면1].html` - 레벨 표시
|
||||
- `[화면2].html` - 해금 안내
|
||||
|
||||
### UI 표시
|
||||
|
||||
```
|
||||
[잠금 상태]
|
||||
🔒 Lv.10에서 해금됩니다
|
||||
현재 Lv.7 (3레벨 더 필요)
|
||||
|
||||
[해금 완료]
|
||||
✓ 기능 사용 가능
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GM-004: [정책명 - 예: 대기열/쿨다운]
|
||||
|
||||
### 정책 내용
|
||||
- [대기/쿨다운 설명]
|
||||
- [적용 시간]
|
||||
- [예외 조건]
|
||||
|
||||
### 적용 화면
|
||||
- `[화면1].html` - 액션 버튼
|
||||
|
||||
### UI 표시
|
||||
|
||||
```
|
||||
[쿨다운 중]
|
||||
┌─────────────────────────────────────┐
|
||||
│ ⏳ 잠시 기다려주세요 │
|
||||
│ 02:30 남음 │
|
||||
│ │
|
||||
│ [비활성 버튼] │
|
||||
└─────────────────────────────────────┘
|
||||
|
||||
[대기열]
|
||||
현재 대기: 12명
|
||||
예상 대기 시간: 약 3분
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GM-005: [정책명 - 예: 매칭 규칙]
|
||||
|
||||
### 정책 내용
|
||||
- [매칭 조건 1]
|
||||
- [매칭 조건 2]
|
||||
- [매칭 실패 처리]
|
||||
|
||||
### 로직
|
||||
|
||||
```javascript
|
||||
function matchUser(user) {
|
||||
const candidates = findCandidates({
|
||||
level: user.level,
|
||||
preference: user.preference,
|
||||
// ...
|
||||
});
|
||||
|
||||
if (candidates.length === 0) {
|
||||
return showNoMatchPopup();
|
||||
}
|
||||
|
||||
return selectBestMatch(candidates);
|
||||
}
|
||||
```
|
||||
|
||||
### UI 표시
|
||||
|
||||
```
|
||||
[매칭 중]
|
||||
🔍 찾는 중...
|
||||
[로딩 애니메이션]
|
||||
|
||||
[매칭 성공]
|
||||
✓ 매칭되었습니다!
|
||||
[상대방 정보]
|
||||
|
||||
[매칭 실패]
|
||||
😢 조건에 맞는 상대를 찾지 못했습니다.
|
||||
[다시 시도] [조건 변경]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 관련 mockup
|
||||
|
||||
| 정책 | mockup 파일 |
|
||||
|-----|------------|
|
||||
| GM-001 | [화면].html |
|
||||
| GM-002 | [화면].html |
|
||||
| GM-003 | [화면].html |
|
||||
| GM-004, GM-005 | [화면].html |
|
||||
135
.specify/templates/policy-index.md
Normal file
135
.specify/templates/policy-index.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# 정책 인덱스
|
||||
|
||||
> [서비스명] - 정책 문서 목록 및 화면 매핑
|
||||
|
||||
---
|
||||
|
||||
## 정책 문서 목록
|
||||
|
||||
| 코드 | 문서 | 설명 |
|
||||
|-----|------|------|
|
||||
| MN | [01-monetization.md](01-monetization.md) | 과금/결제 정책 |
|
||||
| GM | [02-gameplay.md](02-gameplay.md) | 서비스 규칙 |
|
||||
| CH | [03-content.md](03-content.md) | 콘텐츠 정책 |
|
||||
| PR | [04-progression.md](04-progression.md) | 진행/성장 정책 |
|
||||
| CR | [05-content-rating.md](05-content-rating.md) | 콘텐츠 등급 정책 |
|
||||
|
||||
---
|
||||
|
||||
## 탭 구조 참조
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ [아이콘] [아이콘] [아이콘] [아이콘] [아이콘] │
|
||||
│ 탭1 탭2 탭3 탭4 탭5 │
|
||||
│ (1) (2) (3) (4) (5) │
|
||||
│ ^^^^ │
|
||||
│ (센터, 강조 디자인) │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 화면별 적용 정책
|
||||
|
||||
| 화면 | 경로 | 탭 위치 | 적용 정책 |
|
||||
|-----|------|--------|----------|
|
||||
| **[화면1]** | `/[경로1]` | 1번 탭 | [정책 목록] |
|
||||
| **[화면1 상세]** | `/[경로1]/:id` | - | [정책 목록] |
|
||||
| **[화면2]** | `/[경로2]` | 2번 탭 | [정책 목록] |
|
||||
| **[화면2 상세]** | `/[경로2]/:id` | - | [정책 목록] |
|
||||
| **[화면3 (홈)]** | `/` | 3번 탭 | [정책 목록] |
|
||||
| **[화면4]** | `/[경로4]` | 4번 탭 | [정책 목록] |
|
||||
| **[화면5]** | `/[경로5]` | 5번 탭 | [정책 목록] |
|
||||
| **프로필** | `/profile` | - | [정책 목록] |
|
||||
| **설정** | `/settings` | - | [정책 목록] |
|
||||
|
||||
---
|
||||
|
||||
## 정책별 적용 화면 (역매핑)
|
||||
|
||||
### 과금 정책 (MN)
|
||||
|
||||
| 정책 ID | 정책명 | 적용 화면 |
|
||||
|---------|-------|----------|
|
||||
| MN-001 | [정책명] | [화면 목록] |
|
||||
| MN-002 | [정책명] | [화면 목록] |
|
||||
| MN-003 | [정책명] | [화면 목록] |
|
||||
| MN-004 | [정책명] | [화면 목록] |
|
||||
| MN-005 | [정책명] | [화면 목록] |
|
||||
|
||||
### 서비스 규칙 (GM)
|
||||
|
||||
| 정책 ID | 정책명 | 적용 화면 |
|
||||
|---------|-------|----------|
|
||||
| GM-001 | [정책명] | [화면 목록] |
|
||||
| GM-002 | [정책명] | [화면 목록] |
|
||||
| GM-003 | [정책명] | [화면 목록] |
|
||||
| GM-004 | [정책명] | [화면 목록] |
|
||||
|
||||
### 콘텐츠 정책 (CH)
|
||||
|
||||
| 정책 ID | 정책명 | 적용 화면 |
|
||||
|---------|-------|----------|
|
||||
| CH-001 | [정책명] | [화면 목록] |
|
||||
| CH-002 | [정책명] | [화면 목록] |
|
||||
| CH-003 | [정책명] | [화면 목록] |
|
||||
|
||||
### 진행 정책 (PR)
|
||||
|
||||
| 정책 ID | 정책명 | 적용 화면 |
|
||||
|---------|-------|----------|
|
||||
| PR-001 | [정책명] | [화면 목록] |
|
||||
| PR-002 | [정책명] | [화면 목록] |
|
||||
| PR-003 | [정책명] | [화면 목록] |
|
||||
|
||||
### 콘텐츠 등급 정책 (CR)
|
||||
|
||||
| 정책 ID | 정책명 | 적용 화면 |
|
||||
|---------|-------|----------|
|
||||
| CR-001 | [정책명] | [화면 목록] |
|
||||
| CR-002 | [정책명] | [화면 목록] |
|
||||
|
||||
---
|
||||
|
||||
## 정책 변경 이력
|
||||
|
||||
| 날짜 | 정책 ID | 변경 내용 | 영향 화면 |
|
||||
|-----|---------|----------|----------|
|
||||
| YYYY-MM-DD | - | 초기 정책 프레임워크 생성 | 전체 |
|
||||
|
||||
---
|
||||
|
||||
## 사용법
|
||||
|
||||
### UI 문서에서 정책 참조
|
||||
|
||||
```markdown
|
||||
## 화면명
|
||||
|
||||
### 적용 정책
|
||||
- [MN-001] 잔액 표시 → 헤더 우측에 배지 형태
|
||||
- [GM-002] 시간 제한 → 특정 조건에서만 활성화
|
||||
```
|
||||
|
||||
### 정책 문서에서 UI 영향 명시
|
||||
|
||||
```markdown
|
||||
## MN-001: 잔액 표시
|
||||
|
||||
### UI 영향
|
||||
| 화면 | 위치 | 표시 방식 |
|
||||
|-----|------|----------|
|
||||
| 홈 | 헤더 우측 | 💬 127 (배지) |
|
||||
| 채팅 | 입력창 상단 | 남은 횟수: 127회 |
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
| 문서 | 설명 |
|
||||
|-----|------|
|
||||
| [router.md](../router.md) | 라우터 및 UI 목록 |
|
||||
| [main.md](../main.md) | 메인 구조 |
|
||||
224
.specify/templates/policy-monetization.md
Normal file
224
.specify/templates/policy-monetization.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# 과금 정책 (Monetization)
|
||||
|
||||
> 코드: `MN` | 결제, 구독, 포인트 관련 정책
|
||||
|
||||
---
|
||||
|
||||
## MN-001: [정책명 - 예: 잔액 표시]
|
||||
|
||||
### 정책 내용
|
||||
- [핵심 정책 설명 1]
|
||||
- [핵심 정책 설명 2]
|
||||
|
||||
### UI 영향
|
||||
|
||||
| 화면 | 위치 | 표시 방식 | mockup |
|
||||
|-----|------|----------|--------|
|
||||
| [화면1] | [위치] | [표시 방식] | [파일명].html |
|
||||
| [화면2] | [위치] | [표시 방식] | [파일명].html |
|
||||
| [화면3] | [위치] | [표시 방식] | [파일명].html |
|
||||
|
||||
### 표시 규칙
|
||||
|
||||
```
|
||||
[상세 규칙 설명]
|
||||
|
||||
예시:
|
||||
보너스 45회 + 기본 127회 인 경우:
|
||||
|
||||
[헤더 배지]
|
||||
💬 172 ← 합산 표시
|
||||
|
||||
[상세 뷰 - 상점/설정]
|
||||
🎁 보너스: 45회 (D-12 만료)
|
||||
💬 기본권: 127회 (영구)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MN-002: [정책명 - 예: 소모 규칙]
|
||||
|
||||
### 정책 내용
|
||||
- [소모 조건 1]
|
||||
- [소모 조건 2]
|
||||
- [소모되지 않는 경우]
|
||||
|
||||
### 적용 화면
|
||||
- `[화면1].html` - [용도]
|
||||
- `[화면2].html` - [용도]
|
||||
|
||||
### UI 표시
|
||||
|
||||
```
|
||||
[사용 전]
|
||||
액션을 수행하면 1회가 소모됩니다.
|
||||
남은 횟수: 🎁 45 + 💬 127 = 172회
|
||||
|
||||
[사용 후]
|
||||
남은 횟수: 171회 (-1)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MN-003: [정책명 - 예: 우선 소모 순서]
|
||||
|
||||
### 정책 내용
|
||||
- [우선순위 1]
|
||||
- [우선순위 2]
|
||||
- [우선순위 3]
|
||||
|
||||
### 로직
|
||||
|
||||
```javascript
|
||||
function consume() {
|
||||
if (bonus > 0) {
|
||||
bonus -= 1;
|
||||
} else if (credits > 0) {
|
||||
credits -= 1;
|
||||
} else {
|
||||
showRechargePopup();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### UI 표시
|
||||
|
||||
```
|
||||
[안내 - 상점/사용 화면 진입 시]
|
||||
💡 보너스가 먼저 사용되고, 소진 후 기본권이 사용됩니다.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MN-004: [정책명 - 예: 충전 패키지]
|
||||
|
||||
### 정책 내용
|
||||
|
||||
| 패키지 | 수량 | 가격 | 단가 | 할인율 |
|
||||
|-------|------|------|-----|-------|
|
||||
| 스타터 | [N]회 | [가격]P | [단가]P | - |
|
||||
| 베이직 | [N]회 | [가격]P | [단가]P | [N]% |
|
||||
| 스탠다드 | [N]회 | [가격]P | [단가]P | [N]% |
|
||||
| 프리미엄 | [N]회 | [가격]P | [단가]P | [N]% |
|
||||
|
||||
### 적용 화면
|
||||
- `shop.html` - 상점
|
||||
|
||||
### UI 구성
|
||||
|
||||
```
|
||||
[충전 섹션]
|
||||
|
||||
┌─────────────────────────────────────┐
|
||||
│ 🏷️ 스타터 │
|
||||
│ [N]회 · [가격]P │
|
||||
│ │
|
||||
├─────────────────────────────────────┤
|
||||
│ ⭐ 베이직 BEST │
|
||||
│ [N]회 · [가격]P ([N]% 할인) │
|
||||
│ │
|
||||
├─────────────────────────────────────┤
|
||||
│ 💎 스탠다드 │
|
||||
│ [N]회 · [가격]P ([N]% 할인) │
|
||||
│ │
|
||||
├─────────────────────────────────────┤
|
||||
│ 👑 프리미엄 │
|
||||
│ [N]회 · [가격]P ([N]% 할인) │
|
||||
│ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MN-005: [정책명 - 예: 프리미엄 구독]
|
||||
|
||||
### 정책 내용
|
||||
|
||||
| 항목 | 무료 | 프리미엄 ([가격]원/월) |
|
||||
|-----|------|---------------------|
|
||||
| [혜택1] | [무료 내용] | **[프리미엄 내용]** |
|
||||
| [혜택2] | [무료 내용] | **[프리미엄 내용]** |
|
||||
| [혜택3] | [무료 내용] | **[프리미엄 내용]** |
|
||||
| [혜택4] | [무료 내용] | **[프리미엄 내용]** |
|
||||
|
||||
### 적용 화면
|
||||
- `shop.html` - 구독 탭
|
||||
- `settings.html` - 구독 관리
|
||||
|
||||
### UI 표시
|
||||
|
||||
```
|
||||
[구독 배너 - 비구독자]
|
||||
┌─────────────────────────────────────┐
|
||||
│ 👑 프리미엄 구독 │
|
||||
│ │
|
||||
│ ✓ [혜택1 설명] │
|
||||
│ ✓ [혜택2 설명] │
|
||||
│ ✓ [혜택3 설명] │
|
||||
│ │
|
||||
│ 월 [가격]원 [구독하기] │
|
||||
└─────────────────────────────────────┘
|
||||
|
||||
[구독자 배지]
|
||||
👑 Premium Member
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MN-006: [정책명 - 예: 포인트 충전]
|
||||
|
||||
### 정책 내용
|
||||
|
||||
| 금액 | 포인트 | 보너스 |
|
||||
|-----|-------|-------|
|
||||
| [금액1]원 | [포인트]P | - |
|
||||
| [금액2]원 | [포인트]P | +[N]% |
|
||||
| [금액3]원 | [포인트]P | +[N]% |
|
||||
| [금액4]원 | [포인트]P | +[N]% |
|
||||
| [금액5]원 | [포인트]P | +[N]% |
|
||||
|
||||
### 적용 화면
|
||||
- `shop.html` - 포인트 탭
|
||||
|
||||
---
|
||||
|
||||
## MN-007: [정책명 - 예: 구독 관리]
|
||||
|
||||
### 정책 내용
|
||||
- 구독 해지 시 [해지 정책]
|
||||
- 구독 혜택은 [혜택 유효 기간]
|
||||
- 보유 아이템은 [보유 정책]
|
||||
|
||||
### 적용 화면
|
||||
- `settings.html` - 계정 > 구독 관리
|
||||
|
||||
### UI 구성
|
||||
|
||||
```
|
||||
[구독 상태]
|
||||
👑 프리미엄 구독 중
|
||||
다음 결제일: YYYY-MM-DD
|
||||
월 [가격]원
|
||||
|
||||
[해지하기] 버튼
|
||||
|
||||
[해지 확인 팝업]
|
||||
⚠️ 구독을 해지하시겠습니까?
|
||||
|
||||
• [해지 후 유효 기간 안내]
|
||||
• [보너스 처리 안내]
|
||||
• [보유 아이템 안내]
|
||||
|
||||
[해지하기] [취소]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 관련 mockup
|
||||
|
||||
| 정책 | mockup 파일 |
|
||||
|-----|------------|
|
||||
| MN-001 | home.html, chat.html, shop.html |
|
||||
| MN-002, MN-003 | chat.html, [action].html |
|
||||
| MN-004, MN-005, MN-006 | shop.html |
|
||||
| MN-007 | settings.html |
|
||||
175
.specify/templates/policy-progression.md
Normal file
175
.specify/templates/policy-progression.md
Normal file
@@ -0,0 +1,175 @@
|
||||
# 진행/성장 정책 (Progression)
|
||||
|
||||
> 코드: `PR` | 레벨, 경험치, 업적, 보상 관련 정책
|
||||
|
||||
---
|
||||
|
||||
## PR-001: [정책명 - 예: 레벨 시스템]
|
||||
|
||||
### 정책 내용
|
||||
|
||||
| 레벨 | 필요 경험치 | 누적 경험치 | 해금 기능 |
|
||||
|-----|-----------|-----------|---------|
|
||||
| 1 | 0 | 0 | 기본 기능 |
|
||||
| 2 | [N] | [N] | [기능] |
|
||||
| 3 | [N] | [N] | [기능] |
|
||||
| 5 | [N] | [N] | [기능] |
|
||||
| 10 | [N] | [N] | [기능] |
|
||||
| 20 | [N] | [N] | [기능] |
|
||||
| 50 | [N] | [N] | [기능] |
|
||||
| MAX | [N] | [N] | 전체 해금 |
|
||||
|
||||
### 적용 화면
|
||||
- `[화면1].html` - 레벨 표시
|
||||
- `[화면2].html` - 프로필
|
||||
- `[화면3].html` - 진행 상황
|
||||
|
||||
### UI 표시
|
||||
|
||||
```
|
||||
[레벨 배지]
|
||||
Lv.15 ████████░░░░ 67%
|
||||
1,234 / 2,000 EXP
|
||||
|
||||
[레벨업 팝업]
|
||||
┌─────────────────────────────────────┐
|
||||
│ 🎉 레벨 업! │
|
||||
│ │
|
||||
│ Lv.15 → Lv.16 │
|
||||
│ │
|
||||
│ [해금된 기능 표시] │
|
||||
│ ✓ 새로운 기능이 해금되었습니다! │
|
||||
│ │
|
||||
│ [확인] │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PR-002: [정책명 - 예: 경험치 획득]
|
||||
|
||||
### 정책 내용
|
||||
|
||||
| 액션 | 획득 경험치 | 일일 제한 |
|
||||
|-----|-----------|---------|
|
||||
| [액션1] | +[N] EXP | [N]회 |
|
||||
| [액션2] | +[N] EXP | [N]회 |
|
||||
| [액션3] | +[N] EXP | 무제한 |
|
||||
| [첫 액션 보너스] | +[N] EXP | 1회/일 |
|
||||
|
||||
### 적용 화면
|
||||
- `[화면1].html` - 액션 결과
|
||||
- `[화면2].html` - 경험치 현황
|
||||
|
||||
### UI 표시
|
||||
|
||||
```
|
||||
[경험치 획득 토스트]
|
||||
+50 EXP 획득!
|
||||
|
||||
[경험치 상세]
|
||||
┌─────────────────────────────────────┐
|
||||
│ 📊 오늘의 경험치 │
|
||||
├─────────────────────────────────────┤
|
||||
│ [액션1] +150 EXP (3/5회) │
|
||||
│ [액션2] +100 EXP (2/3회) │
|
||||
│ [첫 액션] +50 EXP ✓완료 │
|
||||
├─────────────────────────────────────┤
|
||||
│ 오늘 총: 300 EXP │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PR-003: [정책명 - 예: 업적 시스템]
|
||||
|
||||
### 정책 내용
|
||||
|
||||
| 업적 | 조건 | 보상 |
|
||||
|-----|-----|------|
|
||||
| [업적1] | [조건] | [보상] |
|
||||
| [업적2] | [조건] | [보상] |
|
||||
| [업적3] | [조건] | [보상] |
|
||||
|
||||
### 적용 화면
|
||||
- `[화면1].html` - 업적 목록
|
||||
- `[화면2].html` - 업적 상세
|
||||
|
||||
### UI 표시
|
||||
|
||||
```
|
||||
[업적 목록]
|
||||
┌─────────────────────────────────────┐
|
||||
│ 🏆 업적 │
|
||||
│ 12/50 달성 │
|
||||
├─────────────────────────────────────┤
|
||||
│ ⭐ [업적명] ✓ 완료 │
|
||||
│ [설명] │
|
||||
│ 보상: [보상 내용] │
|
||||
├─────────────────────────────────────┤
|
||||
│ ⭐ [업적명] 75% │
|
||||
│ [설명] │
|
||||
│ ████████░░░░ 15/20 │
|
||||
├─────────────────────────────────────┤
|
||||
│ 🔒 [업적명] 잠김 │
|
||||
│ ??? │
|
||||
└─────────────────────────────────────┘
|
||||
|
||||
[업적 달성 팝업]
|
||||
┌─────────────────────────────────────┐
|
||||
│ 🏆 업적 달성! │
|
||||
│ │
|
||||
│ [업적명] │
|
||||
│ [업적 설명] │
|
||||
│ │
|
||||
│ 보상: [아이콘] +[보상 내용] │
|
||||
│ │
|
||||
│ [받기] │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PR-004: [정책명 - 예: 출석 보상]
|
||||
|
||||
### 정책 내용
|
||||
|
||||
| 일차 | 보상 | 누적 보상 |
|
||||
|-----|-----|---------|
|
||||
| 1일 | [보상] | [보상] |
|
||||
| 2일 | [보상] | [보상] |
|
||||
| 3일 | [보상] | [보상] |
|
||||
| 7일 | **[특별 보상]** | [보상] |
|
||||
| 14일 | [보상] | [보상] |
|
||||
| 30일 | **[월간 보상]** | [보상] |
|
||||
|
||||
### 적용 화면
|
||||
- `[화면1].html` - 출석 체크 팝업
|
||||
- `[화면2].html` - 출석 현황
|
||||
|
||||
### UI 표시
|
||||
|
||||
```
|
||||
[출석 체크 팝업]
|
||||
┌─────────────────────────────────────┐
|
||||
│ 📅 출석 체크! │
|
||||
│ │
|
||||
│ 월 화 수 목 금 토 일 │
|
||||
│ ✓ ✓ ✓ ✓ ● ○ ○ │
|
||||
│ │
|
||||
│ 연속 5일째 출석 중! │
|
||||
│ 내일 보상: [보상 미리보기] │
|
||||
│ │
|
||||
│ [확인] │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 관련 mockup
|
||||
|
||||
| 정책 | mockup 파일 |
|
||||
|-----|------------|
|
||||
| PR-001, PR-002 | profile.html, [결과].html |
|
||||
| PR-003 | achievement.html |
|
||||
| PR-004 | home.html, attendance.html |
|
||||
442
.specify/templates/ui-main.md
Normal file
442
.specify/templates/ui-main.md
Normal file
@@ -0,0 +1,442 @@
|
||||
# [서비스명] - 메인 구조
|
||||
|
||||
## 화면 개요
|
||||
|
||||
| 항목 | 내용 |
|
||||
|-----|------|
|
||||
| 서비스명 | [서비스명] |
|
||||
| UI 컨셉 | [UI 컨셉 - 예: 카카오톡 스타일 메신저] |
|
||||
| 탭 수 | N개 ([탭1] - [탭2] - [탭3] - [탭4] - [탭5]) |
|
||||
| 메인 액션 | N번 탭 ([탭명]) - [메인 액션 설명] |
|
||||
|
||||
---
|
||||
|
||||
## 탭 구조
|
||||
|
||||
### 레퍼런스 비교
|
||||
|
||||
| 위치 | [레퍼런스 앱] | [우리 앱] | 역할 |
|
||||
|-----|--------------|---------|------|
|
||||
| 1 | [레퍼런스 탭] | [우리 탭] | [역할] |
|
||||
| 2 | [레퍼런스 탭] | [우리 탭] | [역할] |
|
||||
| 3 | [레퍼런스 탭] | **[우리 탭]** | [핵심 역할] |
|
||||
| 4 | [레퍼런스 탭] | [우리 탭] | [역할] |
|
||||
| 5 | [레퍼런스 탭] | [우리 탭] | [역할] |
|
||||
|
||||
### 탭바 레이아웃
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ [아이콘] [아이콘] [센터] [아이콘] [아이콘] │
|
||||
│ 탭1 탭2 탭3 탭4 탭5 │
|
||||
│ ^^^^ │
|
||||
│ (센터, 강조 디자인) │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 센터 탭 강조
|
||||
|
||||
| 요소 | 일반 탭 | 센터 탭 |
|
||||
|-----|--------|--------|
|
||||
| 크기 | 기본 | 1.2배 |
|
||||
| 색상 | 회색 | 브랜드 컬러 |
|
||||
| 배경 | 없음 | 서클/글로우 |
|
||||
| 아이콘 | 라인 | 필드 |
|
||||
|
||||
---
|
||||
|
||||
## 상단 헤더
|
||||
|
||||
### 기본 구조
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ [로고] [상태 표시] ⚙️ │
|
||||
│ ^^^^^^^^ ^^^^^^^^^^^^^ ^^ │
|
||||
│ 로고 상태 정보 설정 │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 헤더 요소
|
||||
|
||||
| 요소 | 위치 | 설명 | 액션 |
|
||||
|-----|-----|------|-----|
|
||||
| 로고 | 좌측 | 서비스 로고 "[서비스명]" | 홈(메인탭) 이동 |
|
||||
| [상태 항목] | 우측 | [아이콘] + 수량 | 탭 → [관련 탭] |
|
||||
| 설정 | 우측 끝 | ⚙️ 아이콘 | 탭 → 설정 화면 |
|
||||
|
||||
### 상태 표시 규칙
|
||||
|
||||
| 수량 | 색상 | 효과 |
|
||||
|-----|-----|------|
|
||||
| 100+ | 흰색 | - |
|
||||
| 50~99 | 노란색 | - |
|
||||
| 10~49 | 주황색 | - |
|
||||
| 1~9 | 빨간색 | - |
|
||||
| 0 | 빨간색 | 깜빡임 |
|
||||
|
||||
---
|
||||
|
||||
## 탭별 개요
|
||||
|
||||
### 1탭: [탭명] ([아이콘] [역할])
|
||||
|
||||
**[레퍼런스 앱] [탭명] 탭과 동일한 UX**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ [헤더 영역] │
|
||||
├─────────────────────────────────────┤
|
||||
│ [내 프로필/상태 영역] │
|
||||
│ │
|
||||
├─────────────────────────────────────┤
|
||||
│ [목록 라벨] │
|
||||
│ │
|
||||
│ [아이템 1] │
|
||||
│ [부가 정보] │
|
||||
│ │
|
||||
│ [아이템 2] │
|
||||
│ [부가 정보] │
|
||||
│ │
|
||||
│ [잠금 아이템] │
|
||||
│ [해금 조건] │
|
||||
├─────────────────────────────────────┤
|
||||
│ [탭바] │
|
||||
│ ● │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**상세 문서:** [[문서명].md]([문서명].md)
|
||||
|
||||
---
|
||||
|
||||
### 2탭: [탭명] ([아이콘] [역할])
|
||||
|
||||
**[레퍼런스 앱] [탭명] 탭과 동일한 UX**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ [헤더 영역] │
|
||||
├─────────────────────────────────────┤
|
||||
│ │
|
||||
│ [리스트 아이템 1] │
|
||||
│ [마지막 메시지/상태] [시간] │
|
||||
│ │
|
||||
│ [리스트 아이템 2] │
|
||||
│ [마지막 메시지/상태] [시간] │
|
||||
│ │
|
||||
│ [시스템 아이템] │
|
||||
│ [시스템 메시지] │
|
||||
│ │
|
||||
├─────────────────────────────────────┤
|
||||
│ [탭바] │
|
||||
│ ● │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**상세 문서:** [[문서명].md]([문서명].md)
|
||||
|
||||
---
|
||||
|
||||
### 3탭: [탭명] ([아이콘] [역할]) - **센터**
|
||||
|
||||
**핵심 액션 탭: [메인 버튼 설명]**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ [헤더 영역] │
|
||||
├─────────────────────────────────────┤
|
||||
│ │
|
||||
│ [메인 비주얼 영역] │
|
||||
│ [캐릭터/콘텐츠 표시] │
|
||||
│ │
|
||||
├─────────────────────────────────────┤
|
||||
│ [상태 카드] │
|
||||
│ [주요 스탯/정보 표시] │
|
||||
│ │
|
||||
├─────────────────────────────────────┤
|
||||
│ [액션 선택 영역] │
|
||||
│ [옵션 드롭다운/버튼] │
|
||||
├─────────────────────────────────────┤
|
||||
│ ╭━━━━━━━━━━━━━━╮ │
|
||||
│ ┃ ▶ [버튼명] ┃ ← 펄스 │
|
||||
│ ╰━━━━━━━━━━━━━━╯ 애니 │
|
||||
├─────────────────────────────────────┤
|
||||
│ [탭바] │
|
||||
│ ● │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**상세 문서:** [[문서명].md]([문서명].md)
|
||||
|
||||
---
|
||||
|
||||
### 4탭: [탭명] ([아이콘] [역할])
|
||||
|
||||
**[역할 설명]**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ [헤더 영역] │
|
||||
├─────────────────────────────────────┤
|
||||
│ [현재 상태 표시] │
|
||||
├─────────────────────────────────────┤
|
||||
│ [섹션 1] │
|
||||
│ │
|
||||
│ [상품/아이템 1] │
|
||||
│ [상품/아이템 2] BEST │
|
||||
│ [상품/아이템 3] │
|
||||
│ │
|
||||
├─────────────────────────────────────┤
|
||||
│ [섹션 2] │
|
||||
│ │
|
||||
│ [상품/아이템] │
|
||||
│ [설명] │
|
||||
├─────────────────────────────────────┤
|
||||
│ [탭바] │
|
||||
│ ● │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**상세 문서:** [[문서명].md]([문서명].md)
|
||||
|
||||
---
|
||||
|
||||
### 5탭: [탭명] ([아이콘] [역할])
|
||||
|
||||
**[역할 설명]**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ [프로필 카드] │
|
||||
│ [이름/정보] │
|
||||
│ │
|
||||
│ [스탯/상태 요약] │
|
||||
├─────────────────────────────────────┤
|
||||
│ [메뉴 1] │
|
||||
│ [메뉴 2] │
|
||||
│ [메뉴 3] │
|
||||
│ [메뉴 4] │
|
||||
│ [메뉴 5] │
|
||||
├─────────────────────────────────────┤
|
||||
│ [탭바] │
|
||||
│ ● │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**상세 문서:** [[문서명].md]([문서명].md)
|
||||
|
||||
---
|
||||
|
||||
## 네비게이션 구조
|
||||
|
||||
### 탭 내비게이션 (Bottom Tab)
|
||||
|
||||
```
|
||||
[앱 실행]
|
||||
│
|
||||
├─ [탭1]
|
||||
│ └─ 상세 (push)
|
||||
│
|
||||
├─ [탭2]
|
||||
│ └─ 상세 (push)
|
||||
│
|
||||
├─ [탭3] ← 기본 시작 탭
|
||||
│ └─ 전체화면 액션 (push, 탭바 숨김)
|
||||
│
|
||||
├─ [탭4]
|
||||
│ └─ 결제 화면 (modal)
|
||||
│
|
||||
└─ [탭5]
|
||||
├─ 상세 (push)
|
||||
├─ 설정 (push)
|
||||
└─ 서브메뉴 (push)
|
||||
```
|
||||
|
||||
### 메인 버튼 전체화면 전환
|
||||
|
||||
```
|
||||
[메인 탭] [전체화면 모드]
|
||||
┌───────────────┐ 버튼 탭 ┌─────────────────────────┐
|
||||
│ [상태 카드] │ ──────────> │ │
|
||||
│ [메인 버튼] │ (push) │ 전체화면 연출 │
|
||||
├───────────────┤ │ (탭바 없음) │
|
||||
│ [탭바] │ │ │
|
||||
└───────────────┘ └─────────────────────────┘
|
||||
(탭바) ↓ 완료 후
|
||||
(pop back)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 탭 간 이동
|
||||
|
||||
### 뱃지 표시
|
||||
|
||||
| 탭 | 뱃지 조건 | 표시 |
|
||||
|-----|---------|------|
|
||||
| [탭1] | [조건1] | 숫자 |
|
||||
| [탭2] | [조건2] | 숫자 |
|
||||
| [탭3] | - | - |
|
||||
| [탭4] | [조건4] | 빨간 점 |
|
||||
| [탭5] | [조건5] | 숫자 |
|
||||
|
||||
### 딥링크 지원
|
||||
|
||||
| 경로 | 탭 | 화면 |
|
||||
|-----|-----|------|
|
||||
| `/[tab1]` | [탭1] | 목록 |
|
||||
| `/[tab1]/:id` | [탭1] | 상세 |
|
||||
| `/[tab2]` | [탭2] | 목록 |
|
||||
| `/[tab2]/:id` | [탭2] | 상세 |
|
||||
| `/` | [탭3] | 메인 (기본) |
|
||||
| `/[tab4]` | [탭4] | 상점 |
|
||||
| `/[tab5]` | [탭5] | 메뉴 |
|
||||
| `/profile` | [탭5] | 프로필 상세 |
|
||||
| `/settings` | [탭5] | 설정 |
|
||||
|
||||
---
|
||||
|
||||
## 앱 시작 시 동작
|
||||
|
||||
### 기본 시작 탭
|
||||
|
||||
1. **앱 최초 실행**: 튜토리얼 → [메인 탭]
|
||||
2. **앱 재실행**: 마지막 탭 기억 (기본: [메인 탭])
|
||||
3. **푸시 알림 진입**: 해당 컨텐츠로 이동
|
||||
- [알림 유형1] → [관련 화면]
|
||||
- [알림 유형2] → [관련 화면]
|
||||
|
||||
### 미확인 알림 처리
|
||||
|
||||
```
|
||||
[앱 실행]
|
||||
│
|
||||
├─ 미확인 항목 있음?
|
||||
│ └─ [탭] 뱃지 표시
|
||||
│
|
||||
└─ 자동으로 해당 탭 이동? ❌
|
||||
└─ 유저가 직접 확인
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 테마 및 스타일
|
||||
|
||||
### 컬러 테마
|
||||
|
||||
| 모드 | 배경 | 텍스트 | 포인트 |
|
||||
|-----|-----|-------|-------|
|
||||
| 기본 | #[색상] (다크) | #FFFFFF | #[색상] |
|
||||
| [모드1] | #[색상] | #FFFFFF | #[색상] |
|
||||
| [모드2] | #[색상] | #FFFFFF | #[색상] |
|
||||
|
||||
### 탭바 스타일
|
||||
|
||||
```css
|
||||
.tab-bar {
|
||||
background: #[색상];
|
||||
border-top: 1px solid #[색상];
|
||||
height: 56px;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
color: #[비활성 색상];
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.tab-item.center {
|
||||
/* 센터 탭 강조 */
|
||||
transform: scale(1.2);
|
||||
color: #[포인트 색상];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 데이터 요구사항
|
||||
|
||||
### 탭 뱃지 API
|
||||
|
||||
```
|
||||
GET /api/badges
|
||||
```
|
||||
|
||||
```typescript
|
||||
interface BadgeResponse {
|
||||
[tab1]: number; // [탭1] 뱃지 수
|
||||
[tab2]: number; // [탭2] 뱃지 수
|
||||
[tab4]: boolean; // [탭4] 뱃지 여부
|
||||
[tab5]: number; // [탭5] 뱃지 수
|
||||
}
|
||||
```
|
||||
|
||||
### 전역 상태
|
||||
|
||||
```typescript
|
||||
interface GlobalState {
|
||||
// [상태 항목]
|
||||
[stateName]: [type];
|
||||
|
||||
// 현재 상태
|
||||
currentState: {
|
||||
[field]: [type];
|
||||
};
|
||||
|
||||
// 알림
|
||||
badges: BadgeResponse;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 접근성 고려사항
|
||||
|
||||
- 탭바: `role="tablist"`, 각 탭 `role="tab"`
|
||||
- 현재 탭: `aria-selected="true"`
|
||||
- 뱃지: 스크린 리더용 `aria-label` 제공
|
||||
- 터치 영역: 최소 44x44px
|
||||
- 색상 대비: WCAG 2.1 AA 준수
|
||||
|
||||
---
|
||||
|
||||
## MVP 구현 범위
|
||||
|
||||
### Phase 1 (필수)
|
||||
|
||||
- [ ] N탭 구조 구현
|
||||
- [ ] 센터 탭 강조 디자인
|
||||
- [ ] 상단 헤더 (로고, 상태, 설정)
|
||||
- [ ] 뱃지 표시 시스템
|
||||
- [ ] 기본 라우팅
|
||||
|
||||
### Phase 2
|
||||
|
||||
- [ ] 딥링크 지원
|
||||
- [ ] 탭 전환 애니메이션
|
||||
- [ ] 푸시 알림 연동
|
||||
- [ ] 테마 전환
|
||||
|
||||
### Phase 3
|
||||
|
||||
- [ ] 탭바 숨김/표시 애니메이션
|
||||
- [ ] 제스처 네비게이션 (스와이프)
|
||||
- [ ] iPad 대응 (탭바 → 사이드바)
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
| 문서 | 설명 |
|
||||
|-----|------|
|
||||
| [concept.md](concept.md) | 서비스 컨셉 |
|
||||
| [[tab1_doc].md]([tab1_doc].md) | [탭1] 상세 |
|
||||
| [[tab2_doc].md]([tab2_doc].md) | [탭2] 상세 |
|
||||
| [[tab3_doc].md]([tab3_doc].md) | [탭3] 상세 |
|
||||
| [router.md](router.md) | 라우팅 구조 |
|
||||
228
.specify/templates/ui-router.md
Normal file
228
.specify/templates/ui-router.md
Normal file
@@ -0,0 +1,228 @@
|
||||
# [서비스명] - Router & UI 목록
|
||||
|
||||
## 라우터 구조
|
||||
|
||||
```
|
||||
/ # 기본 = [메인탭]
|
||||
├── /[tab1] # 탭1 (목록)
|
||||
│ └── /:id # 상세
|
||||
├── /[tab2] # 탭2 (목록)
|
||||
│ └── /:id # 상세
|
||||
├── /[tab3] # 탭3 (메인 액션)
|
||||
├── /[tab4] # 탭4
|
||||
├── /[tab5] # 탭5
|
||||
└── /settings # 설정
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 탭 구조
|
||||
|
||||
### 하단 탭바 (Bottom Tab)
|
||||
|
||||
| # | 탭 | 아이콘 | 경로 | 뱃지 |
|
||||
|---|-----|------|-----|------|
|
||||
| 1 | [탭명] | [아이콘] | `/[경로]` | [뱃지 조건] |
|
||||
| 2 | [탭명] | [아이콘] | `/[경로]` | [뱃지 조건] |
|
||||
| 3 | **[센터탭]** | [아이콘] | `/` | - |
|
||||
| 4 | [탭명] | [아이콘] | `/[경로]` | [뱃지 조건] |
|
||||
| 5 | [탭명] | [아이콘] | `/[경로]` | [뱃지 조건] |
|
||||
|
||||
### 탭바 레이아웃
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ [아이콘] [아이콘] [센터] [아이콘] [아이콘] │
|
||||
│ 탭1 탭2 탭3 탭4 탭5 │
|
||||
│ ^^^^ │
|
||||
│ (크게, 강조 배경) │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 화면 목록
|
||||
|
||||
### 1. [탭명] 탭 ([아이콘] [TabName])
|
||||
|
||||
| 항목 | 내용 |
|
||||
|-----|------|
|
||||
| 경로 | `/[경로]` |
|
||||
| 문서 | `[문서명].md` |
|
||||
| 탭바 | [아이콘] [탭명] (활성) |
|
||||
| 역할 | [역할 설명] |
|
||||
|
||||
#### 컴포넌트
|
||||
|
||||
| ID | 유형 | 이름 | 설명 |
|
||||
|----|-----|-----|------|
|
||||
| `[screen]-header` | Header | 상단 헤더 | [헤더 요소 설명] |
|
||||
| `[screen]-list` | List | 목록 | [목록 설명] |
|
||||
| `[screen]-item` | Card | 아이템 카드 | [카드 설명] |
|
||||
| `[screen]-empty` | Empty | 빈 상태 | [빈 상태 메시지] |
|
||||
|
||||
#### 바텀시트
|
||||
|
||||
| ID | 유형 | 트리거 | 내용 |
|
||||
|----|-----|-------|------|
|
||||
| `bs-[name]` | BottomSheet | [트리거 설명] | [내용 설명] |
|
||||
|
||||
#### 팝업/모달
|
||||
|
||||
| ID | 유형 | 트리거 | 설명 |
|
||||
|----|-----|-------|------|
|
||||
| `popup-[name]` | Modal | [트리거 설명] | [설명] |
|
||||
|
||||
---
|
||||
|
||||
### 2. [화면명] 상세 화면 ([Screen] Detail)
|
||||
|
||||
| 항목 | 내용 |
|
||||
|-----|------|
|
||||
| 경로 | `/[경로]/:id` |
|
||||
| 문서 | `[문서명].md` |
|
||||
| 진입 | [어디서] (Push) |
|
||||
|
||||
#### 컴포넌트
|
||||
|
||||
| ID | 유형 | 이름 | 설명 |
|
||||
|----|-----|-----|------|
|
||||
| `detail-header` | Header | 헤더 | 뒤로, 이름, [추가 요소] |
|
||||
| `detail-content` | Display | 컨텐츠 | [컨텐츠 설명] |
|
||||
| `detail-tabs` | TabGroup | 탭 | [탭 목록] |
|
||||
| `detail-actions` | ActionBar | 액션 버튼 | [버튼 목록] |
|
||||
|
||||
#### 바텀시트
|
||||
|
||||
| ID | 유형 | 트리거 | 내용 |
|
||||
|----|-----|-------|------|
|
||||
| `bs-[name]` | BottomSheet | [트리거] | [내용] |
|
||||
|
||||
---
|
||||
|
||||
## 전역 컴포넌트
|
||||
|
||||
### 상단 헤더 (Global Header)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ [로고] [상태 표시] ⚙️ │
|
||||
│ ^^^^^^^^ ^^^^^^^^^^^^^ ^^ │
|
||||
│ 로고 상태 정보 설정 │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| 요소 | 위치 | 설명 | 액션 |
|
||||
|-----|-----|------|-----|
|
||||
| 로고 | 좌측 | 서비스 로고 | 홈 이동 |
|
||||
| [상태] | 우측 | [상태 설명] | [액션] |
|
||||
| 설정 | 우측 끝 | ⚙️ 아이콘 | 설정 화면 |
|
||||
|
||||
### 전역 팝업
|
||||
|
||||
| ID | 유형 | 트리거 | 설명 |
|
||||
|----|-----|-------|------|
|
||||
| `global-network-error` | Modal | 네트워크 오류 | 재시도/취소 |
|
||||
| `global-session-expired` | Modal | 세션 만료 | 재로그인 |
|
||||
| `global-update-required` | Modal | 업데이트 필요 | 스토어 이동 |
|
||||
| `global-maintenance` | Modal | 점검 중 | 점검 안내 |
|
||||
|
||||
### 전역 토스트
|
||||
|
||||
| ID | 유형 | 트리거 | 설명 |
|
||||
|----|-----|-------|------|
|
||||
| `toast-success` | Toast | 성공 액션 | 성공 메시지 |
|
||||
| `toast-error` | Toast | 오류 발생 | 오류 메시지 |
|
||||
| `toast-info` | Toast | 정보 알림 | 정보 메시지 |
|
||||
|
||||
---
|
||||
|
||||
## 네비게이션 플로우
|
||||
|
||||
### 기본 플로우
|
||||
|
||||
```
|
||||
[앱 실행]
|
||||
│
|
||||
└─→ [메인 탭] (기본)
|
||||
│
|
||||
├─→ [액션 버튼] → [액션 결과 화면]
|
||||
│
|
||||
├─→ [아이템 탭] → [상세 화면]
|
||||
│
|
||||
└─→ [알림] → [관련 화면]
|
||||
```
|
||||
|
||||
### 메인 액션 플로우
|
||||
|
||||
```
|
||||
[메인 탭]
|
||||
│
|
||||
└─→ [메인 버튼] (Push)
|
||||
│
|
||||
└─→ [전체화면 모드 - 탭바 숨김]
|
||||
│
|
||||
├─→ Phase 1: [단계1 설명]
|
||||
│
|
||||
├─→ Phase 2: [단계2 설명]
|
||||
│
|
||||
└─→ 완료 → [확인] → Pop back
|
||||
```
|
||||
|
||||
### 탭 간 이동
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ 탭1 ←─────→ 탭2 ←─────→ 탭3 ←─────→ 탭4 ←─────→ 탭5 │
|
||||
│ │ │ │ │ │ │
|
||||
│ └─ 상세 └─ 상세 └─ 액션 └─ 결제 └─ 설정 │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 요약 통계
|
||||
|
||||
| 카테고리 | 개수 |
|
||||
|---------|-----|
|
||||
| 탭 화면 | N개 |
|
||||
| 상세 화면 | N개 |
|
||||
| 전체화면 모드 | N개 |
|
||||
| 바텀시트 | N개 |
|
||||
| 팝업/모달 | N개 |
|
||||
| 토스트 | N개 |
|
||||
| **총 UI 요소** | **N개** |
|
||||
|
||||
---
|
||||
|
||||
## URL 스킴
|
||||
|
||||
### 딥링크
|
||||
|
||||
| 경로 | 설명 |
|
||||
|-----|------|
|
||||
| `[scheme]://` | 앱 실행 (메인 탭) |
|
||||
| `[scheme]://[tab1]` | 탭1 |
|
||||
| `[scheme]://[tab1]/:id` | 탭1 상세 |
|
||||
| `[scheme]://[tab2]` | 탭2 |
|
||||
| `[scheme]://[tab2]/:id` | 탭2 상세 |
|
||||
|
||||
### 푸시 알림 딥링크
|
||||
|
||||
| 알림 유형 | 딥링크 |
|
||||
|---------|-------|
|
||||
| [알림 유형1] | `[scheme]://[경로]` |
|
||||
| [알림 유형2] | `[scheme]://[경로]` |
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
| 문서 | 설명 |
|
||||
|-----|------|
|
||||
| [main.md](main.md) | 메인 구조 |
|
||||
| [concept.md](concept.md) | 서비스 컨셉 |
|
||||
| [policies/](policies/) | 정책 문서 |
|
||||
Reference in New Issue
Block a user