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:
2025-12-14 20:07:13 +09:00
parent 721cf3a5dd
commit e9b0c00be7
23 changed files with 5221 additions and 3902 deletions

View 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>

View 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');
}
}
});
});

View 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; }

View 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 |

View 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 |

View 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 |

View 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) | 메인 구조 |

View 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 |

View 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 |

View 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) | 라우팅 구조 |

View 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/) | 정책 문서 |