diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 6c9b290..4dd09cf 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -6,9 +6,21 @@ "Bash(mkdir:*)", "Bash(xargs:*)", "Bash(python3:*)", - "Bash(git pull:*)" + "Bash(git pull:*)", + "Bash(git add design/uiux/uiux.md)", + "Bash(git add -A)", + "Bash(git commit -m \"UI/UX 설계서 작성 완료\n\n- Mobile First 설계 원칙에 따라 UI/UX 설계서 작성\n- 9개 주요 화면 설계 (로그인, 대시보드, 회의예약, 템플릿선택, 회의진행, 검증완료, 회의종료, 회의록공유, Todo관리)\n- 화면별 상세 설계 (개요, 기능, UI 구성, 인터랙션, 데이터 요구사항, 에러 처리)\n- 화면 간 사용자 플로우 및 네비게이션 전략 정의\n- 반응형 설계 전략 (Mobile/Tablet/Desktop 브레이크포인트)\n- WCAG 2.1 Level AA 접근성 보장 방안\n- 성능 최적화 방안 (코드 스플리팅, 캐싱, WebSocket 최적화)\n- 유저스토리와 1:1 매칭 확인\n\n🤖 Generated with Claude Code\n\nCo-Authored-By: Claude \")", + "Bash(git push)", + "Bash(git stash)", + "Bash(git stash pop)", + "Bash(for i in {1..3})", + "Bash(do echo \"Scroll $i\")", + "Bash(done)", + "Bash(git add design/uiux_다람지/uiux.md)", + "Bash(git commit -m \"$(cat <<''EOF''\nUI/UX 설계서 작성 완료\n\n- Mobile First 설계 원칙에 따라 UI/UX 설계서 작성\n- 9개 주요 화면 설계 (로그인, 대시보드, 회의예약, 템플릿선택, 회의진행, 검증완료, 회의종료, 회의록공유, Todo관리)\n- 화면별 상세 설계 (개요, 기능, UI 구성, 인터랙션, 데이터 요구사항, 에러 처리)\n- 화면 간 사용자 플로우 및 네비게이션 전략 정의\n- 반응형 설계 전략 (Mobile/Tablet/Desktop 브레이크포인트)\n- WCAG 2.1 Level AA 접근성 보장 방안\n- 성능 최적화 방안 (코드 스플리팅, 캐싱, WebSocket 최적화)\n- 유저스토리와 1:1 매칭 확인\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude \nEOF\n)\")", + "Bash(git add .claude/settings.local.json)" ], "deny": [], "ask": [] } -} \ No newline at end of file +} diff --git a/design/uiux_다람지/prototype/01-로그인.html b/design/uiux_다람지/prototype/01-로그인.html new file mode 100644 index 0000000..4b31041 --- /dev/null +++ b/design/uiux_다람지/prototype/01-로그인.html @@ -0,0 +1,143 @@ + + + + + + 로그인 - 회의록 작성 서비스 + + + +
+
+
+ +
+ 📝 +
+ + +

회의록 자동 작성 서비스

+

AI가 도와주는 스마트한 회의록 관리

+ + +
+
+ + + + 테스트 계정: kimmin +
+ +
+ + + + 테스트 비밀번호: password123 +
+ + +
+
+
+
+ + + + + diff --git a/design/uiux_다람지/prototype/02-대시보드.html b/design/uiux_다람지/prototype/02-대시보드.html new file mode 100644 index 0000000..60ca4bb --- /dev/null +++ b/design/uiux_다람지/prototype/02-대시보드.html @@ -0,0 +1,212 @@ + + + + + + 대시보드 - 회의록 작성 서비스 + + + + +
+
KM
+

회의록

+
+ +
+
+ + +
+
+ +
+

오늘의 회의

+
+ +
+
+ + +
+

최근 회의록

+
+ +
+
+ + +
+

Todo 요약

+
+
+
+
진행 중
+
-
+
+
+
완료
+
-
+
+
+
전체
+
-
+
+
+
+
+
+ + + +
+ + + + + + + + diff --git a/design/uiux_다람지/prototype/03-회의예약.html b/design/uiux_다람지/prototype/03-회의예약.html new file mode 100644 index 0000000..e261a67 --- /dev/null +++ b/design/uiux_다람지/prototype/03-회의예약.html @@ -0,0 +1,328 @@ + + + + + + 회의 예약 - 회의록 작성 서비스 + + + + +
+ +

회의 예약

+
+ +
+
+ + +
+
+
+ +
+ + + +
+ + +
+ +
+
+ +
+
+ +
+
+ +
+ + +
+ + +
+ + +
+ + +
+ +
+ +
+ + + +
+
+
+ + + + + diff --git a/design/uiux_다람지/prototype/04-템플릿선택.html b/design/uiux_다람지/prototype/04-템플릿선택.html new file mode 100644 index 0000000..dd3ae2e --- /dev/null +++ b/design/uiux_다람지/prototype/04-템플릿선택.html @@ -0,0 +1,159 @@ + + + + + + 템플릿 선택 - 회의록 작성 서비스 + + + + +
+ +

템플릿 선택

+
+ + +
+
+

회의 유형을 선택하세요

+ +
+ +
+ + +
+
+ + + + + diff --git a/design/uiux_다람지/prototype/05-회의진행.html b/design/uiux_다람지/prototype/05-회의진행.html new file mode 100644 index 0000000..dcee994 --- /dev/null +++ b/design/uiux_다람지/prototype/05-회의진행.html @@ -0,0 +1,239 @@ + + + + + + 회의 진행 - 회의록 작성 서비스 + + + + + +
+ +

주간 회의

+
+ +
+
+ + +
+ 🔴 녹음 중 00:00:00 +
+ + +
+
+ +
+

참석자 (5명)

+
+
+
+
+
+
+
+
+ + +
+

📝 회의록

+
+

## 참석자

+

- 김민준
- 박서연
- 이준호
- 최유진
- 정도현

+ +

## 논의 내용

+

[김민준] 이번 분기 KPI 목표는 매출 20% 증가입니다.

+ +

[박서연 typing...]

+
+
+ + +
+ + +
+
+
+ + + + + diff --git a/design/uiux_다람지/prototype/06-검증완료.html b/design/uiux_다람지/prototype/06-검증완료.html new file mode 100644 index 0000000..9b8efb0 --- /dev/null +++ b/design/uiux_다람지/prototype/06-검증완료.html @@ -0,0 +1,268 @@ + + + + + + 회의록 검증 - 회의록 작성 서비스 + + + + + +
+ +

회의록 검증

+
+ + +
+
+
+

주간 회의

+

2025-01-15

+
+ + +
+
+ 검증 현황 + + 0/5 + +
+
+
+
+
+ + +
+ +
+
+
+ + + + + diff --git a/design/uiux_다람지/prototype/07-회의종료.html b/design/uiux_다람지/prototype/07-회의종료.html new file mode 100644 index 0000000..b21dc48 --- /dev/null +++ b/design/uiux_다람지/prototype/07-회의종료.html @@ -0,0 +1,243 @@ + + + + + + 회의 종료 - 회의록 작성 서비스 + + + + + +
+ +

회의 종료

+
+ + +
+
+
+

주간 회의

+

2025-01-15 14:00 - 14:45

+
+ +

📊 회의 통계

+ + +
+ +
45분 30초
+
총 시간
+
+ + +
+ +
5명
+
참석자
+
+ + +
+

💬 발언 횟수

+
+ +
+
+ + +
+

🔑 주요 키워드

+
+ +
+
+ + +
+ + +
+
+
+ + + + + diff --git a/design/uiux_다람지/prototype/08-회의록공유.html b/design/uiux_다람지/prototype/08-회의록공유.html new file mode 100644 index 0000000..92acfd1 --- /dev/null +++ b/design/uiux_다람지/prototype/08-회의록공유.html @@ -0,0 +1,333 @@ + + + + + + 회의록 공유 - 회의록 작성 서비스 + + + + + +
+ +

회의록 확정

+
+ + +
+
+ +
+

필수 항목 확인

+ +
+
+ + 회의 제목 +
+
+ + 참석자 목록 +
+
+ + 주요 논의 내용 +
+
+ + 결정 사항 +
+
+ +

선택 항목

+
+
+ + Todo 항목 +
+
+ + +
+ + + + + + +
+
+ + + + + diff --git a/design/uiux_다람지/prototype/09-Todo관리.html b/design/uiux_다람지/prototype/09-Todo관리.html new file mode 100644 index 0000000..98c558f --- /dev/null +++ b/design/uiux_다람지/prototype/09-Todo관리.html @@ -0,0 +1,403 @@ + + + + + + Todo 관리 - 회의록 작성 서비스 + + + + + +
+ +

Todo

+
+ + +
+
+ +
+ + + +
+ + +
+ +
+ + + +
+
+ + + + + + + + diff --git a/design/uiux_다람지/prototype/common.css b/design/uiux_다람지/prototype/common.css new file mode 100644 index 0000000..bc88d82 --- /dev/null +++ b/design/uiux_다람지/prototype/common.css @@ -0,0 +1,719 @@ +/* 회의록 작성 서비스 - 공통 스타일시트 */ + +/* ========== CSS Variables ========== */ +:root { + /* Colors */ + --primary: #0066CC; + --primary-dark: #004A99; + --primary-light: #E6F2FF; + --text-primary: #1A1A1A; + --text-secondary: #666666; + --text-disabled: #999999; + --text-inverse: #FFFFFF; + --bg-white: #FFFFFF; + --bg-gray: #F5F5F5; + --bg-dark: #1A1A1A; + --success: #0A7029; + --error: #C41E3A; + --warning: #856404; + --info: #0066CC; + --border-light: #E0E0E0; + --border-medium: #CCCCCC; + + /* Shadows */ + --shadow-sm: 0 1px 3px rgba(0,0,0,0.1); + --shadow-md: 0 4px 6px rgba(0,0,0,0.1); + --shadow-lg: 0 10px 15px rgba(0,0,0,0.1); + + /* Typography */ + --font-primary: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + --font-mono: "SF Mono", Monaco, "Cascadia Code", monospace; + --font-xs: 0.75rem; + --font-sm: 0.875rem; + --font-base: 1rem; + --font-lg: 1.125rem; + --font-xl: 1.25rem; + --font-2xl: 1.5rem; + --font-3xl: 2rem; + --font-regular: 400; + --font-medium: 500; + --font-semibold: 600; + --font-bold: 700; + --leading-tight: 1.25; + --leading-normal: 1.5; + --leading-relaxed: 1.75; + + /* Spacing */ + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-5: 1.25rem; + --space-6: 1.5rem; + --space-8: 2rem; + --space-10: 2.5rem; + --space-12: 3rem; + --space-16: 4rem; + + /* Animation */ + --duration-fast: 150ms; + --duration-base: 200ms; + --duration-slow: 300ms; + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Dark Mode */ +@media (prefers-color-scheme: dark) { + :root { + --bg-white: #1A1A1A; + --bg-gray: #2A2A2A; + --text-primary: #FFFFFF; + --text-secondary: #CCCCCC; + --border-light: #404040; + --shadow-sm: 0 1px 3px rgba(0,0,0,0.3); + } +} + +/* ========== Reset & Base ========== */ +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + font-size: 16px; + -webkit-text-size-adjust: 100%; +} + +body { + font-family: var(--font-primary); + font-size: var(--font-base); + line-height: var(--leading-normal); + color: var(--text-primary); + background-color: var(--bg-gray); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +h1, h2, h3, h4, h5, h6 { + font-weight: var(--font-semibold); + line-height: var(--leading-tight); + margin-bottom: var(--space-4); +} + +h1 { font-size: var(--font-3xl); } +h2 { font-size: var(--font-2xl); } +h3 { font-size: var(--font-xl); } +h4 { font-size: var(--font-lg); } + +p { margin-bottom: var(--space-4); } + +a { + color: var(--primary); + text-decoration: none; + transition: color var(--duration-base); +} + +a:hover { color: var(--primary-dark); } + +button { + font-family: inherit; + cursor: pointer; + border: none; + background: none; +} + +input, textarea, select { + font-family: inherit; + font-size: inherit; +} + +/* ========== Layout ========== */ +.container { + width: 100%; + max-width: 100%; + margin: 0 auto; + padding: 0 var(--space-4); +} + +@media (min-width: 768px) { + .container { max-width: 720px; } +} + +@media (min-width: 1024px) { + .container { max-width: 960px; padding: 0 var(--space-8); } +} + +.main-content { + min-height: 100vh; + padding-bottom: 80px; /* Space for bottom nav on mobile */ +} + +@media (min-width: 768px) { + .main-content { padding-bottom: 0; } +} + +/* ========== Header ========== */ +.header { + background: var(--bg-white); + box-shadow: var(--shadow-sm); + padding: var(--space-4); + position: sticky; + top: 0; + z-index: 100; + display: flex; + align-items: center; + justify-content: space-between; +} + +.header-title { + font-size: var(--font-lg); + font-weight: var(--font-semibold); + margin: 0; +} + +.header-back { + background: none; + border: none; + font-size: var(--font-2xl); + color: var(--text-primary); + cursor: pointer; + padding: var(--space-2); +} + +.header-actions { + display: flex; + gap: var(--space-2); +} + +/* ========== Buttons ========== */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 12px 24px; + border-radius: 8px; + font-weight: var(--font-semibold); + font-size: var(--font-sm); + transition: all var(--duration-base) var(--ease-in-out); + cursor: pointer; + border: none; + text-decoration: none; + min-height: 44px; +} + +.btn-primary { + background: var(--primary); + color: var(--text-inverse); +} + +.btn-primary:hover:not(:disabled) { + background: var(--primary-dark); + transform: translateY(-1px); + box-shadow: var(--shadow-md); +} + +.btn-primary:active:not(:disabled) { + transform: translateY(0); +} + +.btn-secondary { + background: transparent; + color: var(--primary); + border: 2px solid var(--primary); + padding: 10px 22px; +} + +.btn-secondary:hover:not(:disabled) { + background: var(--primary-light); +} + +.btn-ghost { + background: transparent; + color: var(--text-primary); +} + +.btn-ghost:hover:not(:disabled) { + background: var(--bg-gray); +} + +.btn-success { + background: var(--success); + color: var(--text-inverse); +} + +.btn-error { + background: var(--error); + color: var(--text-inverse); +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-full { + width: 100%; +} + +.btn-icon { + padding: var(--space-3); + border-radius: 50%; + min-height: 44px; + min-width: 44px; +} + +/* ========== Form Elements ========== */ +.form-group { + margin-bottom: var(--space-6); +} + +.form-label { + display: block; + font-size: var(--font-sm); + font-weight: var(--font-medium); + color: var(--text-primary); + margin-bottom: var(--space-2); +} + +.required { + color: var(--error); +} + +.form-input, +.form-textarea, +.form-select { + width: 100%; + padding: 12px 16px; + border: 1px solid var(--border-light); + border-radius: 8px; + font-size: var(--font-sm); + background: var(--bg-white); + color: var(--text-primary); + transition: border-color var(--duration-base); + min-height: 44px; +} + +.form-input:focus, +.form-textarea:focus, +.form-select:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 3px var(--primary-light); +} + +.form-input.error, +.form-textarea.error, +.form-select.error { + border-color: var(--error); +} + +.form-input:disabled, +.form-textarea:disabled, +.form-select:disabled { + background: var(--bg-gray); + cursor: not-allowed; +} + +.form-textarea { + min-height: 120px; + resize: vertical; +} + +.form-error { + display: none; + color: var(--error); + font-size: var(--font-xs); + margin-top: var(--space-2); +} + +.form-group.has-error .form-error { + display: block; +} + +.form-hint { + font-size: var(--font-xs); + color: var(--text-secondary); + margin-top: var(--space-2); +} + +/* ========== Cards ========== */ +.card { + background: var(--bg-white); + border-radius: 12px; + padding: var(--space-6); + box-shadow: var(--shadow-sm); + transition: all var(--duration-base); +} + +.card-hover { + cursor: pointer; +} + +.card-hover:hover { + box-shadow: var(--shadow-md); + transform: translateY(-2px); +} + +.card-title { + font-size: var(--font-lg); + font-weight: var(--font-semibold); + margin-bottom: var(--space-3); +} + +.card-subtitle { + font-size: var(--font-sm); + color: var(--text-secondary); + margin-bottom: var(--space-4); +} + +/* ========== Badge & Chip ========== */ +.badge { + display: inline-flex; + align-items: center; + padding: 4px 12px; + border-radius: 16px; + font-size: var(--font-xs); + font-weight: var(--font-medium); +} + +.badge-success { + background: var(--success); + color: var(--text-inverse); +} + +.badge-error { + background: var(--error); + color: var(--text-inverse); +} + +.badge-warning { + background: var(--warning); + color: var(--text-inverse); +} + +.badge-info { + background: var(--primary-light); + color: var(--primary); +} + +.chip { + display: inline-flex; + align-items: center; + padding: 6px 12px; + border-radius: 20px; + background: var(--bg-gray); + font-size: var(--font-sm); + gap: var(--space-2); +} + +.chip-remove { + background: none; + border: none; + color: var(--text-secondary); + font-size: var(--font-lg); + cursor: pointer; + padding: 0; + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; +} + +/* ========== Toast ========== */ +.toast { + position: fixed; + bottom: 100px; + left: 50%; + transform: translateX(-50%); + background: var(--bg-dark); + color: var(--text-inverse); + padding: var(--space-4) var(--space-6); + border-radius: 8px; + box-shadow: var(--shadow-lg); + z-index: 1000; + animation: slideUp var(--duration-slow) var(--ease-in-out); +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateX(-50%) translateY(20px); + } + to { + opacity: 1; + transform: translateX(-50%) translateY(0); + } +} + +.toast-success { background: var(--success); } +.toast-error { background: var(--error); } +.toast-warning { background: var(--warning); } + +/* ========== Modal ========== */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + padding: var(--space-4); +} + +.modal { + background: var(--bg-white); + border-radius: 12px; + padding: var(--space-6); + max-width: 500px; + width: 100%; + max-height: 90vh; + overflow-y: auto; +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--space-4); +} + +.modal-title { + font-size: var(--font-xl); + font-weight: var(--font-semibold); + margin: 0; +} + +.modal-close { + background: none; + border: none; + font-size: var(--font-2xl); + color: var(--text-secondary); + cursor: pointer; +} + +.modal-footer { + display: flex; + gap: var(--space-3); + margin-top: var(--space-6); + justify-content: flex-end; +} + +/* ========== Loading ========== */ +.loading { + display: inline-block; + width: 40px; + height: 40px; + border: 4px solid var(--border-light); + border-top-color: var(--primary); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.loading-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255,255,255,0.9); + display: flex; + align-items: center; + justify-content: center; + z-index: 2000; +} + +/* ========== Bottom Navigation (Mobile) ========== */ +.bottom-nav { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: var(--bg-white); + box-shadow: 0 -2px 10px rgba(0,0,0,0.1); + display: flex; + justify-content: space-around; + padding: var(--space-2) 0; + z-index: 100; +} + +@media (min-width: 768px) { + .bottom-nav { display: none; } +} + +.bottom-nav-item { + display: flex; + flex-direction: column; + align-items: center; + padding: var(--space-2); + color: var(--text-secondary); + text-decoration: none; + font-size: var(--font-xs); + min-width: 60px; + transition: color var(--duration-base); +} + +.bottom-nav-item.active { + color: var(--primary); +} + +.bottom-nav-icon { + font-size: var(--font-2xl); + margin-bottom: var(--space-1); +} + +/* ========== FAB (Floating Action Button) ========== */ +.fab { + position: fixed; + bottom: 100px; + right: var(--space-4); + width: 56px; + height: 56px; + border-radius: 50%; + background: var(--primary); + color: var(--text-inverse); + font-size: var(--font-2xl); + box-shadow: var(--shadow-lg); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + border: none; + transition: all var(--duration-base); + z-index: 50; +} + +.fab:hover { + background: var(--primary-dark); + transform: scale(1.1); +} + +@media (min-width: 768px) { + .fab { + bottom: var(--space-6); + } +} + +/* ========== Avatar ========== */ +.avatar { + display: inline-block; + width: 40px; + height: 40px; + border-radius: 50%; + background: var(--primary-light); + color: var(--primary); + display: flex; + align-items: center; + justify-content: center; + font-weight: var(--font-semibold); + font-size: var(--font-sm); +} + +.avatar-sm { width: 32px; height: 32px; font-size: var(--font-xs); } +.avatar-lg { width: 56px; height: 56px; font-size: var(--font-lg); } + +.avatar-group { + display: flex; + align-items: center; +} + +.avatar-group .avatar { + margin-left: -8px; + border: 2px solid var(--bg-white); +} + +.avatar-group .avatar:first-child { + margin-left: 0; +} + +/* ========== Empty State ========== */ +.empty-state { + text-align: center; + padding: var(--space-12) var(--space-4); + color: var(--text-secondary); +} + +.empty-state-icon { + font-size: 64px; + margin-bottom: var(--space-4); + opacity: 0.5; +} + +.empty-state-title { + font-size: var(--font-xl); + color: var(--text-primary); + margin-bottom: var(--space-2); +} + +.empty-state-description { + font-size: var(--font-sm); + margin-bottom: var(--space-6); +} + +/* ========== Accessibility ========== */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0,0,0,0); + white-space: nowrap; + border-width: 0; +} + +*:focus-visible { + outline: 2px solid var(--primary); + outline-offset: 2px; +} + +/* Reduced Motion */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} + +/* ========== Utility Classes ========== */ +.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-success { color: var(--success); } +.text-error { color: var(--error); } +.text-warning { color: var(--warning); } + +.mt-0 { margin-top: 0; } +.mt-2 { margin-top: var(--space-2); } +.mt-4 { margin-top: var(--space-4); } +.mt-6 { margin-top: var(--space-6); } +.mt-8 { margin-top: var(--space-8); } +.mb-0 { margin-bottom: 0; } +.mb-2 { margin-bottom: var(--space-2); } +.mb-4 { margin-bottom: var(--space-4); } +.mb-6 { margin-bottom: var(--space-6); } +.mb-8 { margin-bottom: var(--space-8); } + +.hidden { display: none; } +.flex { display: flex; } +.flex-col { flex-direction: column; } +.items-center { align-items: center; } +.justify-center { justify-content: center; } +.justify-between { justify-content: space-between; } +.gap-2 { gap: var(--space-2); } +.gap-4 { gap: var(--space-4); } +.gap-6 { gap: var(--space-6); } diff --git a/design/uiux_다람지/prototype/common.js b/design/uiux_다람지/prototype/common.js new file mode 100644 index 0000000..e6ceb8e --- /dev/null +++ b/design/uiux_다람지/prototype/common.js @@ -0,0 +1,556 @@ +// 회의록 작성 서비스 - 공통 JavaScript + +// ========== Mock Data ========== +const MockData = { + user: { + id: 'user-001', + username: 'kimmin', + name: '김민준', + email: 'kimmin@example.com', + role: 'user' + }, + + meetings: [ + { + id: 'meeting-001', + title: '주간 회의', + startTime: '2025-01-20T14:00:00Z', + endTime: '2025-01-20T15:00:00Z', + location: '회의실 A', + attendees: ['김민준', '박서연', '이준호', '최유진', '정도현'], + attendeesCount: 5, + status: 'scheduled' + }, + { + id: 'meeting-002', + title: 'Q1 기획 회의', + startTime: '2025-01-20T16:00:00Z', + endTime: '2025-01-20T17:00:00Z', + location: '회의실 B', + attendees: ['김민준', '박서연', '이준호'], + attendeesCount: 3, + status: 'scheduled' + } + ], + + minutes: [ + { + id: 'minutes-001', + meetingId: 'meeting-001', + title: 'Q4 기획 회의', + date: '2025-01-15', + attendees: ['김민준', '박서연', '이준호'], + content: '# 참석자\n- 김민준\n- 박서연\n- 이준호\n\n# 논의 내용\n...', + status: 'completed' + }, + { + id: 'minutes-002', + meetingId: 'meeting-002', + title: '개발팀 스크럼', + date: '2025-01-14', + attendees: ['이준호', '최유진'], + content: '# 어제 한 일\n...', + status: 'completed' + } + ], + + todos: [ + { + id: 'todo-001', + content: 'API 명세서 작성', + assignee: '이준호', + dueDate: '2025-01-25', + status: 'inprogress', + progress: 60, + minutesId: 'minutes-001' + }, + { + id: 'todo-002', + content: 'UI 프로토타입 완성', + assignee: '최유진', + dueDate: '2025-01-23', + status: 'inprogress', + progress: 80, + minutesId: 'minutes-001' + }, + { + id: 'todo-003', + content: '테스트 케이스 작성', + assignee: '정도현', + dueDate: '2025-01-22', + status: 'completed', + progress: 100, + minutesId: 'minutes-002' + } + ], + + templates: [ + { + id: 'general', + name: '일반 회의', + description: '참석자, 안건, 논의, 결정, Todo', + sections: [ + { id: 'attendees', name: '참석자', order: 1, required: true }, + { id: 'agenda', name: '안건', order: 2, required: false }, + { id: 'discussion', name: '논의 내용', order: 3, required: true }, + { id: 'decisions', name: '결정 사항', order: 4, required: true }, + { id: 'todos', name: 'Todo', order: 5, required: false } + ] + }, + { + id: 'scrum', + name: '스크럼 회의', + description: '어제, 오늘, 이슈', + sections: [ + { id: 'yesterday', name: '어제 한 일', order: 1, required: true }, + { id: 'today', name: '오늘 할 일', order: 2, required: true }, + { id: 'issues', name: '이슈', order: 3, required: false } + ] + }, + { + id: 'kickoff', + name: '프로젝트 킥오프', + description: '개요, 목표, 일정, 역할', + sections: [ + { id: 'overview', name: '프로젝트 개요', order: 1, required: true }, + { id: 'goals', name: '목표', order: 2, required: true }, + { id: 'schedule', name: '일정', order: 3, required: true }, + { id: 'roles', name: '역할', order: 4, required: true }, + { id: 'risks', name: '리스크', order: 5, required: false } + ] + }, + { + id: 'weekly', + name: '주간 회의', + description: '실적, 이슈, 계획', + sections: [ + { id: 'achievements', name: '주간 실적', order: 1, required: true }, + { id: 'issues', name: '주요 이슈', order: 2, required: false }, + { id: 'plan', name: '다음 주 계획', order: 3, required: true } + ] + } + ] +}; + +// ========== Storage Utilities ========== +const Storage = { + set(key, value) { + try { + localStorage.setItem(key, JSON.stringify(value)); + } catch (e) { + console.error('Storage set error:', e); + } + }, + + get(key) { + try { + const item = localStorage.getItem(key); + return item ? JSON.parse(item) : null; + } catch (e) { + console.error('Storage get error:', e); + return null; + } + }, + + remove(key) { + try { + localStorage.removeItem(key); + } catch (e) { + console.error('Storage remove error:', e); + } + }, + + clear() { + try { + localStorage.clear(); + } catch (e) { + console.error('Storage clear error:', e); + } + } +}; + +// ========== Navigation ========== +const Navigation = { + goTo(page) { + window.location.href = page; + }, + + goBack() { + window.history.back(); + }, + + reload() { + window.location.reload(); + } +}; + +// ========== Toast Notifications ========== +const Toast = { + show(message, type = 'info', duration = 3000) { + const toast = document.createElement('div'); + toast.className = `toast toast-${type}`; + toast.textContent = message; + toast.setAttribute('role', 'alert'); + toast.setAttribute('aria-live', 'assertive'); + + document.body.appendChild(toast); + + setTimeout(() => { + toast.remove(); + }, duration); + }, + + success(message) { + this.show(message, 'success'); + }, + + error(message) { + this.show(message, 'error'); + }, + + warning(message) { + this.show(message, 'warning'); + }, + + info(message) { + this.show(message, 'info'); + } +}; + +// ========== Modal ========== +const Modal = { + show(options) { + const { title, content, buttons = [] } = options; + + const overlay = document.createElement('div'); + overlay.className = 'modal-overlay'; + overlay.setAttribute('role', 'dialog'); + overlay.setAttribute('aria-modal', 'true'); + overlay.setAttribute('aria-labelledby', 'modal-title'); + + const modal = document.createElement('div'); + modal.className = 'modal'; + + const header = document.createElement('div'); + header.className = 'modal-header'; + header.innerHTML = ` + + + `; + + const body = document.createElement('div'); + body.className = 'modal-body'; + body.innerHTML = content; + + const footer = document.createElement('div'); + footer.className = 'modal-footer'; + buttons.forEach(btn => { + const button = document.createElement('button'); + button.className = `btn ${btn.className || 'btn-secondary'}`; + button.textContent = btn.text; + button.onclick = () => { + if (btn.onClick) btn.onClick(); + this.close(overlay); + }; + footer.appendChild(button); + }); + + modal.appendChild(header); + modal.appendChild(body); + if (buttons.length > 0) modal.appendChild(footer); + overlay.appendChild(modal); + document.body.appendChild(overlay); + + // Close handlers + header.querySelector('.modal-close').onclick = () => this.close(overlay); + overlay.onclick = (e) => { + if (e.target === overlay) this.close(overlay); + }; + + return overlay; + }, + + close(overlay) { + if (overlay && overlay.parentElement) { + overlay.remove(); + } + }, + + confirm(message) { + return new Promise((resolve) => { + this.show({ + title: '확인', + content: message, + buttons: [ + { + text: '취소', + className: 'btn-secondary', + onClick: () => resolve(false) + }, + { + text: '확인', + className: 'btn-primary', + onClick: () => resolve(true) + } + ] + }); + }); + } +}; + +// ========== Loading ========== +const Loading = { + show() { + const overlay = document.createElement('div'); + overlay.className = 'loading-overlay'; + overlay.id = 'loading-overlay'; + overlay.setAttribute('role', 'status'); + overlay.setAttribute('aria-live', 'polite'); + overlay.innerHTML = '
로딩 중...'; + document.body.appendChild(overlay); + }, + + hide() { + const overlay = document.getElementById('loading-overlay'); + if (overlay) overlay.remove(); + } +}; + +// ========== Form Validation ========== +const Validator = { + required(value) { + return value && value.trim().length > 0; + }, + + email(value) { + const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return re.test(value); + }, + + minLength(value, length) { + return value && value.length >= length; + }, + + maxLength(value, length) { + return value && value.length <= length; + }, + + validateForm(formId, rules) { + const form = document.getElementById(formId); + if (!form) return false; + + let isValid = true; + + Object.keys(rules).forEach(fieldName => { + const field = form.querySelector(`[name="${fieldName}"]`); + const fieldRules = rules[fieldName]; + const formGroup = field.closest('.form-group'); + + formGroup.classList.remove('has-error'); + + for (const rule of fieldRules) { + if (!rule.validator(field.value)) { + isValid = false; + formGroup.classList.add('has-error'); + const errorEl = formGroup.querySelector('.form-error'); + if (errorEl) errorEl.textContent = rule.message; + break; + } + } + }); + + return isValid; + } +}; + +// ========== Date/Time Utilities ========== +const DateTime = { + formatDate(dateString) { + const date = new Date(dateString); + return date.toLocaleDateString('ko-KR', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }); + }, + + formatTime(dateString) { + const date = new Date(dateString); + return date.toLocaleTimeString('ko-KR', { + hour: '2-digit', + minute: '2-digit', + hour12: false + }); + }, + + formatDateTime(dateString) { + return `${this.formatDate(dateString)} ${this.formatTime(dateString)}`; + }, + + formatDuration(seconds) { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const secs = seconds % 60; + return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`; + } +}; + +// ========== Authentication ========== +const Auth = { + login(username, password) { + // Mock login - in real app, call API + if (username === 'kimmin' && password === 'password123') { + const token = 'mock-jwt-token-' + Date.now(); + Storage.set('auth_token', token); + Storage.set('user', MockData.user); + return true; + } + return false; + }, + + logout() { + Storage.remove('auth_token'); + Storage.remove('user'); + Navigation.goTo('01-로그인.html'); + }, + + isAuthenticated() { + return !!Storage.get('auth_token'); + }, + + getCurrentUser() { + return Storage.get('user'); + }, + + requireAuth() { + if (!this.isAuthenticated()) { + Navigation.goTo('01-로그인.html'); + return false; + } + return true; + } +}; + +// ========== API Mock ========== +const API = { + delay() { + return new Promise(resolve => setTimeout(resolve, 500)); + }, + + async getMeetings() { + await this.delay(); + return { success: true, data: MockData.meetings }; + }, + + async getMinutes() { + await this.delay(); + return { success: true, data: MockData.minutes }; + }, + + async getTodos() { + await this.delay(); + const todos = MockData.todos; + const summary = { + inProgress: todos.filter(t => t.status === 'inprogress').length, + completed: todos.filter(t => t.status === 'completed').length, + total: todos.length + }; + return { success: true, data: { todos, summary } }; + }, + + async createMeeting(data) { + await this.delay(); + const newMeeting = { + id: 'meeting-' + Date.now(), + ...data, + status: 'scheduled' + }; + MockData.meetings.push(newMeeting); + return { success: true, data: newMeeting }; + }, + + async getTemplates() { + await this.delay(); + return { success: true, data: MockData.templates }; + }, + + async updateTodo(id, updates) { + await this.delay(); + const todo = MockData.todos.find(t => t.id === id); + if (todo) { + Object.assign(todo, updates); + return { success: true, data: todo }; + } + return { success: false, error: 'Todo not found' }; + } +}; + +// ========== UI Helpers ========== +const UI = { + showLoading() { + Loading.show(); + }, + + hideLoading() { + Loading.hide(); + }, + + showToast(message, type = 'info') { + Toast.show(message, type); + }, + + showModal(options) { + return Modal.show(options); + }, + + confirm(message) { + return Modal.confirm(message); + }, + + setTitle(title) { + document.title = title + ' - 회의록 작성 서비스'; + const headerTitle = document.querySelector('.header-title'); + if (headerTitle) headerTitle.textContent = title; + }, + + createElement(tag, className, content) { + const el = document.createElement(tag); + if (className) el.className = className; + if (content) el.innerHTML = content; + return el; + } +}; + +// ========== Initialize ========== +document.addEventListener('DOMContentLoaded', () => { + // Back button handler + const backButtons = document.querySelectorAll('.header-back'); + backButtons.forEach(btn => { + btn.addEventListener('click', () => Navigation.goBack()); + }); + + // Bottom navigation active state + const currentPage = window.location.pathname.split('/').pop(); + const navItems = document.querySelectorAll('.bottom-nav-item'); + navItems.forEach(item => { + const href = item.getAttribute('href'); + if (href === currentPage) { + item.classList.add('active'); + } + }); +}); + +// ========== Export for use in pages ========== +window.App = { + MockData, + Storage, + Navigation, + Toast, + Modal, + Loading, + Validator, + DateTime, + Auth, + API, + UI +}; diff --git a/design/uiux_다람지/style-guide.md b/design/uiux_다람지/style-guide.md new file mode 100644 index 0000000..f167f16 --- /dev/null +++ b/design/uiux_다람지/style-guide.md @@ -0,0 +1,420 @@ +# 회의록 작성 서비스 - 스타일 가이드 + +## 1. 디자인 시스템 개요 + +### 1.1 디자인 철학 +- **Mobile First**: 모바일 환경을 우선으로 설계하고 점진적으로 향상 +- **접근성 우선**: WCAG 2.1 Level AA 준수 +- **일관성**: 모든 화면에서 동일한 시각적 언어 사용 + +--- + +## 2. 색상 팔레트 + +### 2.1 Primary Colors +```css +--primary: #0066CC; /* 주요 액션 버튼, 링크 */ +--primary-dark: #004A99; /* Hover, Active 상태 */ +--primary-light: #E6F2FF; /* 배경, 하이라이트 */ +``` + +### 2.2 Text Colors +```css +--text-primary: #1A1A1A; /* 제목, 본문 (15:1 대비) */ +--text-secondary: #666666; /* 부가 정보 (5:1 대비) */ +--text-disabled: #999999; /* 비활성 텍스트 */ +--text-inverse: #FFFFFF; /* 어두운 배경의 텍스트 */ +``` + +### 2.3 Background Colors +```css +--bg-white: #FFFFFF; /* 주요 배경 */ +--bg-gray: #F5F5F5; /* 보조 배경 */ +--bg-dark: #1A1A1A; /* 다크 모드 배경 */ +``` + +### 2.4 Status Colors +```css +--success: #0A7029; /* 성공, 완료 (4.5:1 대비) */ +--error: #C41E3A; /* 오류, 위험 (4.5:1 대비) */ +--warning: #856404; /* 경고, 주의 (4.5:1 대비) */ +--info: #0066CC; /* 정보 */ +``` + +### 2.5 Border & Shadow +```css +--border-light: #E0E0E0; /* 기본 테두리 */ +--border-medium: #CCCCCC; /* 강조 테두리 */ +--shadow-sm: 0 1px 3px rgba(0,0,0,0.1); +--shadow-md: 0 4px 6px rgba(0,0,0,0.1); +--shadow-lg: 0 10px 15px rgba(0,0,0,0.1); +``` + +--- + +## 3. 타이포그래피 + +### 3.1 폰트 패밀리 +```css +--font-primary: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, sans-serif; +--font-mono: "SF Mono", Monaco, "Cascadia Code", monospace; +``` + +### 3.2 폰트 크기 +```css +--font-xs: 0.75rem; /* 12px - 캡션, 레이블 */ +--font-sm: 0.875rem; /* 14px - 본문, 입력 필드 */ +--font-base: 1rem; /* 16px - 기본 본문 */ +--font-lg: 1.125rem; /* 18px - 소제목 */ +--font-xl: 1.25rem; /* 20px - 제목 */ +--font-2xl: 1.5rem; /* 24px - 큰 제목 */ +--font-3xl: 2rem; /* 32px - 페이지 제목 */ +``` + +### 3.3 폰트 굵기 +```css +--font-regular: 400; +--font-medium: 500; +--font-semibold: 600; +--font-bold: 700; +``` + +### 3.4 행간 +```css +--leading-tight: 1.25; /* 제목 */ +--leading-normal: 1.5; /* 본문 */ +--leading-relaxed: 1.75; /* 긴 텍스트 */ +``` + +--- + +## 4. 간격 시스템 + +### 4.1 Spacing Scale +```css +--space-1: 0.25rem; /* 4px */ +--space-2: 0.5rem; /* 8px */ +--space-3: 0.75rem; /* 12px */ +--space-4: 1rem; /* 16px */ +--space-5: 1.25rem; /* 20px */ +--space-6: 1.5rem; /* 24px */ +--space-8: 2rem; /* 32px */ +--space-10: 2.5rem; /* 40px */ +--space-12: 3rem; /* 48px */ +--space-16: 4rem; /* 64px */ +``` + +### 4.2 적용 규칙 +- **컴포넌트 내부 패딩**: space-4 (16px) +- **컴포넌트 간 간격**: space-6 (24px) +- **섹션 간 간격**: space-8 (32px) +- **페이지 여백**: space-4 (모바일), space-8 (데스크톱) + +--- + +## 5. 레이아웃 + +### 5.1 Breakpoints +```css +/* Mobile First */ +--breakpoint-sm: 640px; /* 작은 태블릿 */ +--breakpoint-md: 768px; /* 태블릿 */ +--breakpoint-lg: 1024px; /* 데스크톱 */ +--breakpoint-xl: 1280px; /* 큰 데스크톱 */ +``` + +### 5.2 Container +```css +--container-mobile: 100%; +--container-tablet: 720px; +--container-desktop: 960px; +--container-wide: 1200px; +``` + +### 5.3 Grid System +- **Mobile (< 768px)**: 1 column +- **Tablet (768px - 1023px)**: 2 columns +- **Desktop (≥ 1024px)**: 3 columns +- **Gap**: 24px + +--- + +## 6. 컴포넌트 스타일 + +### 6.1 버튼 + +#### Primary Button +```css +background: var(--primary); +color: var(--text-inverse); +padding: 12px 24px; +border-radius: 8px; +font-weight: var(--font-semibold); +``` + +**상태:** +- Hover: `background: var(--primary-dark)` +- Active: `transform: scale(0.98)` +- Disabled: `opacity: 0.5; cursor: not-allowed` + +#### Secondary Button +```css +background: transparent; +color: var(--primary); +border: 2px solid var(--primary); +padding: 10px 22px; +``` + +#### Ghost Button +```css +background: transparent; +color: var(--text-primary); +padding: 12px 24px; +``` + +### 6.2 입력 필드 + +```css +border: 1px solid var(--border-light); +border-radius: 8px; +padding: 12px 16px; +font-size: var(--font-sm); +transition: border-color 0.2s; +``` + +**상태:** +- Focus: `border-color: var(--primary); outline: 2px solid var(--primary-light)` +- Error: `border-color: var(--error)` +- Disabled: `background: var(--bg-gray); cursor: not-allowed` + +### 6.3 카드 + +```css +background: var(--bg-white); +border-radius: 12px; +padding: var(--space-6); +box-shadow: var(--shadow-sm); +transition: box-shadow 0.3s; +``` + +**Hover:** +```css +box-shadow: var(--shadow-md); +transform: translateY(-2px); +``` + +### 6.4 Badge/Chip + +```css +display: inline-flex; +align-items: center; +padding: 4px 12px; +border-radius: 16px; +font-size: var(--font-xs); +font-weight: var(--font-medium); +``` + +**변형:** +- Success: `background: var(--success); color: white` +- Error: `background: var(--error); color: white` +- Info: `background: var(--primary-light); color: var(--primary)` + +--- + +## 7. 아이콘 + +### 7.1 아이콘 크기 +```css +--icon-sm: 16px; /* 인라인 아이콘 */ +--icon-md: 24px; /* 버튼, 입력 필드 */ +--icon-lg: 32px; /* 큰 아이콘 */ +--icon-xl: 48px; /* 일러스트 아이콘 */ +``` + +### 7.2 아이콘 사용 원칙 +- SVG 형식 사용 +- `currentColor` 사용하여 텍스트 색상 상속 +- 접근성을 위해 `aria-label` 또는 `sr-only` 텍스트 제공 + +--- + +## 8. 애니메이션 + +### 8.1 Transition Duration +```css +--duration-fast: 150ms; /* Hover 효과 */ +--duration-base: 200ms; /* 일반 전환 */ +--duration-slow: 300ms; /* Modal, Drawer */ +``` + +### 8.2 Easing Functions +```css +--ease-in: cubic-bezier(0.4, 0, 1, 1); +--ease-out: cubic-bezier(0, 0, 0.2, 1); +--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); +``` + +### 8.3 애니메이션 원칙 +- 성능을 위해 `transform`과 `opacity`만 애니메이션 +- `prefers-reduced-motion` 미디어 쿼리 지원 +- 불필요한 애니메이션 지양 + +--- + +## 9. 접근성 + +### 9.1 색상 대비 +- 일반 텍스트: 최소 4.5:1 +- 큰 텍스트 (18pt+ 또는 14pt bold+): 최소 3:1 +- UI 컴포넌트: 최소 3:1 + +### 9.2 포커스 표시 +```css +*:focus-visible { + outline: 2px solid var(--primary); + outline-offset: 2px; +} +``` + +### 9.3 터치 타겟 +- 최소 크기: 44x44px +- 간격: 최소 8px + +### 9.4 스크린 리더 +```css +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} +``` + +--- + +## 10. 다크 모드 + +```css +@media (prefers-color-scheme: dark) { + :root { + --bg-white: #1A1A1A; + --bg-gray: #2A2A2A; + --text-primary: #FFFFFF; + --text-secondary: #CCCCCC; + --border-light: #404040; + --shadow-sm: 0 1px 3px rgba(0,0,0,0.3); + } +} +``` + +--- + +## 11. 모바일 최적화 + +### 11.1 터치 최적화 +- 버튼/링크 최소 크기: 44x44px +- 스와이프 제스처 지원 +- Pull-to-refresh 지원 + +### 11.2 성능 최적화 +- 이미지 lazy loading +- 코드 스플리팅 +- CSS 최소화 +- 시스템 폰트 우선 사용 + +--- + +## 12. 컴포넌트 라이브러리 + +### 12.1 공통 컴포넌트 +1. **Header**: 페이지 상단 네비게이션 +2. **Button**: Primary, Secondary, Ghost +3. **Input**: Text, Email, Password, Date, Time +4. **Card**: 정보 카드, 클릭 가능한 카드 +5. **Badge**: 상태 표시 +6. **Modal**: 팝업 다이얼로그 +7. **Toast**: 알림 메시지 +8. **Loading**: 로딩 스피너 +9. **Empty State**: 빈 상태 일러스트 +10. **Bottom Navigation**: 모바일 하단 네비게이션 + +### 12.2 도메인 컴포넌트 +1. **Meeting Card**: 회의 정보 카드 +2. **Minutes Editor**: 회의록 편집기 +3. **Todo Item**: Todo 항목 +4. **Attendee Avatar**: 참석자 아바타 +5. **Term Tooltip**: 전문용어 툴팁 + +--- + +## 13. 사용 예시 + +### 13.1 페이지 구조 +```html + + + + + + 페이지 제목 + + + +
+ +
+ +
+ +
+ + + + +``` + +### 13.2 버튼 사용 +```html + + + + + + + + + + + +``` + +### 13.3 입력 필드 +```html +
+ + + 회의 제목을 입력해주세요 +
+``` + +--- + +## 14. 변경 이력 + +| 버전 | 날짜 | 변경 내용 | +|------|------|-----------| +| 1.0 | 2025-01-20 | 초기 스타일 가이드 작성 |