push prototype

This commit is contained in:
cherry2250 2025-10-21 15:30:20 +09:00
parent e38f8776bb
commit 1abf224a6d
12 changed files with 4297 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

View File

@ -0,0 +1,196 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>로그인 - KT AI 이벤트 마케팅</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
</head>
<body>
<div class="page flex flex-col items-center justify-center" style="padding-bottom: 0;">
<div class="container" style="max-width: 400px;">
<!-- Logo & Title -->
<div class="text-center mb-2xl">
<div class="mb-lg">
<span class="material-icons" style="font-size: 64px; color: var(--color-kt-red);">celebration</span>
</div>
<h1 class="text-display text-kt-red mb-sm">KT AI 이벤트</h1>
<p class="text-body text-secondary">소상공인을 위한 스마트 마케팅</p>
</div>
<!-- Login Form -->
<form id="loginForm" class="mb-lg">
<div class="form-group">
<label for="email" class="form-label form-label-required">이메일</label>
<input
type="email"
id="email"
name="email"
class="form-input"
placeholder="example@email.com"
required
autocomplete="email"
>
<span id="emailError" class="form-error hidden"></span>
</div>
<div class="form-group">
<label for="password" class="form-label form-label-required">비밀번호</label>
<input
type="password"
id="password"
name="password"
class="form-input"
placeholder="8자 이상 입력하세요"
required
autocomplete="current-password"
>
<span id="passwordError" class="form-error hidden"></span>
</div>
<div class="form-check mb-lg">
<input type="checkbox" id="remember" name="remember" class="form-check-input">
<label for="remember" class="form-check-label">로그인 상태 유지</label>
</div>
<button type="submit" class="btn btn-primary btn-large btn-full mb-md">
로그인
</button>
<div class="flex justify-between text-body-small">
<a href="#" class="text-kt-red" id="findPassword">비밀번호 찾기</a>
<a href="02-회원가입.html" class="text-kt-red">회원가입</a>
</div>
</form>
<!-- Divider -->
<div class="flex items-center gap-md mb-lg">
<div class="flex-1 border-t"></div>
<span class="text-caption text-tertiary">또는</span>
<div class="flex-1 border-t"></div>
</div>
<!-- SNS Login -->
<div class="flex flex-col gap-sm">
<button class="btn btn-secondary btn-large btn-full" id="kakaoLogin">
<span class="material-icons">chat_bubble</span>
카카오톡으로 시작하기
</button>
<button class="btn btn-secondary btn-large btn-full" id="naverLogin">
<span style="color: #03C75A; font-weight: bold;">N</span>
네이버로 시작하기
</button>
</div>
<!-- Footer -->
<div class="text-center mt-2xl">
<p class="text-caption text-tertiary">
회원가입 시 <a href="#" class="text-kt-red">이용약관</a><a href="#" class="text-kt-red">개인정보처리방침</a>에 동의하게 됩니다.
</p>
</div>
</div>
</div>
<script src="common.js"></script>
<script>
// 폼 제출 처리
document.getElementById('loginForm').addEventListener('submit', function(e) {
e.preventDefault();
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
const remember = document.getElementById('remember').checked;
// 에러 메시지 초기화
document.getElementById('emailError').classList.add('hidden');
document.getElementById('passwordError').classList.add('hidden');
// 유효성 검사
let hasError = false;
if (!KTEventApp.Utils.validateEmail(email)) {
document.getElementById('emailError').textContent = '올바른 이메일 형식이 아닙니다.';
document.getElementById('emailError').classList.remove('hidden');
hasError = true;
}
if (password.length < 8) {
document.getElementById('passwordError').textContent = '비밀번호는 8자 이상이어야 합니다.';
document.getElementById('passwordError').classList.remove('hidden');
hasError = true;
}
if (hasError) {
return;
}
// 로딩 표시
const submitBtn = e.target.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.textContent = '로그인 중...';
// 로그인 시뮬레이션 (실제로는 API 호출)
setTimeout(() => {
// 예제 사용자 정보 저장
const user = KTEventApp.MockData.getDefaultUser();
user.email = email;
KTEventApp.Session.saveUser(user);
// 대시보드로 이동
window.location.href = '05-대시보드.html';
}, 1000);
});
// 비밀번호 찾기
document.getElementById('findPassword').addEventListener('click', function(e) {
e.preventDefault();
KTEventApp.Feedback.showModal({
title: '비밀번호 찾기',
content: `
<p class="text-body mb-md">가입하신 이메일 주소를 입력해주세요.</p>
<input type="email" id="resetEmail" class="form-input" placeholder="example@email.com">
`,
buttons: [
{
text: '취소',
variant: 'text',
onClick: function() {
this.closest('.modal-backdrop').remove();
}
},
{
text: '확인',
variant: 'primary',
onClick: function() {
const resetEmail = document.getElementById('resetEmail').value;
if (KTEventApp.Utils.validateEmail(resetEmail)) {
this.closest('.modal-backdrop').remove();
KTEventApp.Feedback.showToast('비밀번호 재설정 이메일을 발송했습니다.');
} else {
KTEventApp.Feedback.showToast('올바른 이메일을 입력해주세요.');
}
}
}
]
});
});
// SNS 로그인 시뮬레이션
document.getElementById('kakaoLogin').addEventListener('click', function() {
KTEventApp.Feedback.showToast('카카오톡 로그인은 준비 중입니다.');
});
document.getElementById('naverLogin').addEventListener('click', function() {
KTEventApp.Feedback.showToast('네이버 로그인은 준비 중입니다.');
});
// 이미 로그인된 경우 대시보드로 이동
if (KTEventApp.Session.isLoggedIn()) {
window.location.href = '05-대시보드.html';
}
</script>
</body>
</html>

View File

@ -0,0 +1,400 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>회원가입 - KT AI 이벤트 마케팅</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
</head>
<body>
<div class="page" style="padding-bottom: 0;">
<!-- Custom Header -->
<header class="header">
<div class="header-left">
<button class="header-icon-btn" id="backBtn" aria-label="뒤로가기">
<span class="material-icons">arrow_back</span>
</button>
<h1 class="header-title">회원가입</h1>
</div>
</header>
<!-- Progress Indicator -->
<div class="p-md bg-primary">
<div id="progressBar"></div>
<p class="text-body-small text-secondary mt-sm text-center" id="stepInfo">1/3 단계</p>
</div>
<div class="container" style="max-width: 500px; padding-top: var(--spacing-lg);">
<!-- Step 1: 계정 정보 -->
<div id="step1" class="signup-step">
<h2 class="text-title mb-sm">계정 정보를 입력해주세요</h2>
<p class="text-body text-secondary mb-lg">로그인에 사용할 이메일과 비밀번호를 설정합니다</p>
<form id="step1Form">
<div class="form-group">
<label for="email" class="form-label form-label-required">이메일</label>
<input
type="email"
id="email"
class="form-input"
placeholder="example@email.com"
required
autocomplete="email"
>
<span id="emailError" class="form-error hidden"></span>
<span class="form-hint">이메일 주소로 로그인합니다</span>
</div>
<div class="form-group">
<label for="password" class="form-label form-label-required">비밀번호</label>
<input
type="password"
id="password"
class="form-input"
placeholder="8자 이상, 영문+숫자 조합"
required
autocomplete="new-password"
>
<span id="passwordError" class="form-error hidden"></span>
</div>
<div class="form-group">
<label for="passwordConfirm" class="form-label form-label-required">비밀번호 확인</label>
<input
type="password"
id="passwordConfirm"
class="form-input"
placeholder="비밀번호를 다시 입력하세요"
required
autocomplete="new-password"
>
<span id="passwordConfirmError" class="form-error hidden"></span>
</div>
<button type="submit" class="btn btn-primary btn-large btn-full mt-xl">
다음
</button>
</form>
</div>
<!-- Step 2: 개인 정보 -->
<div id="step2" class="signup-step hidden">
<h2 class="text-title mb-sm">개인 정보를 입력해주세요</h2>
<p class="text-body text-secondary mb-lg">서비스 이용을 위한 기본 정보입니다</p>
<form id="step2Form">
<div class="form-group">
<label for="name" class="form-label form-label-required">이름</label>
<input
type="text"
id="name"
class="form-input"
placeholder="홍길동"
required
autocomplete="name"
>
</div>
<div class="form-group">
<label for="phone" class="form-label form-label-required">휴대폰 번호</label>
<input
type="tel"
id="phone"
class="form-input"
placeholder="010-1234-5678"
required
autocomplete="tel"
>
<span id="phoneError" class="form-error hidden"></span>
</div>
<div class="flex gap-sm mt-xl">
<button type="button" class="btn btn-secondary btn-large flex-1" id="prevBtn1">
이전
</button>
<button type="submit" class="btn btn-primary btn-large flex-1">
다음
</button>
</div>
</form>
</div>
<!-- Step 3: 사업장 정보 -->
<div id="step3" class="signup-step hidden">
<h2 class="text-title mb-sm">사업장 정보를 입력해주세요</h2>
<p class="text-body text-secondary mb-lg">맞춤형 이벤트 추천을 위한 정보입니다</p>
<form id="step3Form">
<div class="form-group">
<label for="businessName" class="form-label form-label-required">상호명</label>
<input
type="text"
id="businessName"
class="form-input"
placeholder="홍길동 고깃집"
required
autocomplete="organization"
>
</div>
<div class="form-group">
<label for="businessType" class="form-label form-label-required">업종</label>
<select id="businessType" class="form-select" required>
<option value="">업종을 선택하세요</option>
<option value="restaurant">음식점</option>
<option value="cafe">카페/베이커리</option>
<option value="retail">소매/편의점</option>
<option value="beauty">미용/뷰티</option>
<option value="fitness">헬스/피트니스</option>
<option value="education">학원/교육</option>
<option value="service">서비스업</option>
<option value="other">기타</option>
</select>
</div>
<div class="form-group">
<label for="businessLocation" class="form-label">주요 지역</label>
<input
type="text"
id="businessLocation"
class="form-input"
placeholder="예: 강남구"
autocomplete="address-level2"
>
<span class="form-hint">선택 사항입니다</span>
</div>
<!-- 약관 동의 -->
<div class="mt-xl mb-lg">
<div class="form-check mb-md">
<input type="checkbox" id="agreeAll" class="form-check-input">
<label for="agreeAll" class="form-check-label text-bold">전체 동의</label>
</div>
<div class="border-t pt-md">
<div class="form-check mb-sm">
<input type="checkbox" id="agreeTerms" class="form-check-input agree-item" required>
<label for="agreeTerms" class="form-check-label">
<span>[필수]</span> 이용약관 동의
</label>
</div>
<div class="form-check mb-sm">
<input type="checkbox" id="agreePrivacy" class="form-check-input agree-item" required>
<label for="agreePrivacy" class="form-check-label">
<span>[필수]</span> 개인정보 처리방침 동의
</label>
</div>
<div class="form-check">
<input type="checkbox" id="agreeMarketing" class="form-check-input agree-item">
<label for="agreeMarketing" class="form-check-label">
<span>[선택]</span> 마케팅 정보 수신 동의
</label>
</div>
</div>
</div>
<div class="flex gap-sm">
<button type="button" class="btn btn-secondary btn-large flex-1" id="prevBtn2">
이전
</button>
<button type="submit" class="btn btn-primary btn-large flex-1">
가입완료
</button>
</div>
</form>
</div>
</div>
</div>
<script src="common.js"></script>
<script>
let currentStep = 1;
const signupData = {};
// Progress Bar 초기화
const progressBar = KTEventApp.Feedback.createProgressBar(33);
document.getElementById('progressBar').appendChild(progressBar.element);
// Step 전환 함수
function showStep(step) {
document.querySelectorAll('.signup-step').forEach(el => el.classList.add('hidden'));
document.getElementById(`step${step}`).classList.remove('hidden');
currentStep = step;
progressBar.setValue(step * 33);
document.getElementById('stepInfo').textContent = `${step}/3 단계`;
}
// 뒤로가기
document.getElementById('backBtn').addEventListener('click', () => {
if (currentStep === 1) {
window.location.href = '01-로그인.html';
} else {
showStep(currentStep - 1);
}
});
// Step 1: 계정 정보
document.getElementById('step1Form').addEventListener('submit', function(e) {
e.preventDefault();
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
const passwordConfirm = document.getElementById('passwordConfirm').value;
// 에러 초기화
document.getElementById('emailError').classList.add('hidden');
document.getElementById('passwordError').classList.add('hidden');
document.getElementById('passwordConfirmError').classList.add('hidden');
let hasError = false;
// 이메일 검증
if (!KTEventApp.Utils.validateEmail(email)) {
document.getElementById('emailError').textContent = '올바른 이메일 형식이 아닙니다.';
document.getElementById('emailError').classList.remove('hidden');
hasError = true;
}
// 비밀번호 검증
if (!KTEventApp.Utils.validatePassword(password)) {
document.getElementById('passwordError').textContent = '8자 이상, 영문과 숫자를 포함해야 합니다.';
document.getElementById('passwordError').classList.remove('hidden');
hasError = true;
}
// 비밀번호 확인
if (password !== passwordConfirm) {
document.getElementById('passwordConfirmError').textContent = '비밀번호가 일치하지 않습니다.';
document.getElementById('passwordConfirmError').classList.remove('hidden');
hasError = true;
}
if (!hasError) {
signupData.email = email;
signupData.password = password;
showStep(2);
}
});
// Step 2: 개인 정보
document.getElementById('prevBtn1').addEventListener('click', () => showStep(1));
document.getElementById('step2Form').addEventListener('submit', function(e) {
e.preventDefault();
const name = document.getElementById('name').value;
const phone = document.getElementById('phone').value;
// 전화번호 형식 검증
const phonePattern = /^010-\d{3,4}-\d{4}$/;
if (!phonePattern.test(phone)) {
document.getElementById('phoneError').textContent = '올바른 전화번호 형식이 아닙니다 (010-1234-5678).';
document.getElementById('phoneError').classList.remove('hidden');
return;
}
signupData.name = name;
signupData.phone = phone;
showStep(3);
});
// 전화번호 자동 포맷팅
document.getElementById('phone').addEventListener('input', function(e) {
e.target.value = KTEventApp.Utils.formatPhoneNumber(e.target.value);
document.getElementById('phoneError').classList.add('hidden');
});
// Step 3: 사업장 정보
document.getElementById('prevBtn2').addEventListener('click', () => showStep(2));
// 전체 동의 체크박스
document.getElementById('agreeAll').addEventListener('change', function(e) {
const checked = e.target.checked;
document.querySelectorAll('.agree-item').forEach(cb => {
cb.checked = checked;
});
});
// 개별 체크박스
document.querySelectorAll('.agree-item').forEach(cb => {
cb.addEventListener('change', function() {
const allChecked = Array.from(document.querySelectorAll('.agree-item')).every(c => c.checked);
document.getElementById('agreeAll').checked = allChecked;
});
});
document.getElementById('step3Form').addEventListener('submit', function(e) {
e.preventDefault();
const businessName = document.getElementById('businessName').value;
const businessType = document.getElementById('businessType').value;
const businessLocation = document.getElementById('businessLocation').value;
const agreeTerms = document.getElementById('agreeTerms').checked;
const agreePrivacy = document.getElementById('agreePrivacy').checked;
const agreeMarketing = document.getElementById('agreeMarketing').checked;
if (!agreeTerms || !agreePrivacy) {
KTEventApp.Feedback.showToast('필수 약관에 동의해주세요.');
return;
}
signupData.businessName = businessName;
signupData.businessType = businessType;
signupData.businessLocation = businessLocation;
signupData.agreeMarketing = agreeMarketing;
// 로딩 표시
const submitBtn = e.target.querySelector('button[type="submit"]');
submitBtn.disabled = true;
submitBtn.textContent = '가입 처리 중...';
// 회원가입 시뮬레이션
setTimeout(() => {
// 사용자 정보 저장
const user = {
id: KTEventApp.Utils.generateId(),
name: signupData.name,
email: signupData.email,
phone: signupData.phone,
businessName: signupData.businessName,
businessType: signupData.businessType,
businessLocation: signupData.businessLocation,
joinDate: new Date().toISOString()
};
KTEventApp.Session.saveUser(user);
// 성공 모달 표시
KTEventApp.Feedback.showModal({
title: '회원가입 완료',
content: `
<div class="text-center p-lg">
<span class="material-icons" style="font-size: 64px; color: var(--color-success);">check_circle</span>
<p class="text-headline mt-md mb-sm">환영합니다!</p>
<p class="text-body text-secondary">회원가입이 완료되었습니다.<br>지금 바로 AI 이벤트를 시작해보세요.</p>
</div>
`,
buttons: [
{
text: '시작하기',
variant: 'primary',
size: 'large',
fullWidth: true,
onClick: function() {
window.location.href = '05-대시보드.html';
}
}
]
});
}, 1500);
});
// 이미 로그인된 경우 대시보드로 이동
if (KTEventApp.Session.isLoggedIn()) {
window.location.href = '05-대시보드.html';
}
</script>
</body>
</html>

View File

@ -0,0 +1,235 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>대시보드 - KT AI 이벤트 마케팅</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
</head>
<body>
<div class="page page-with-header">
<!-- Header -->
<div id="header"></div>
<div class="container">
<!-- Welcome Section -->
<section class="mt-lg mb-xl">
<h2 class="text-title-large mb-sm" id="welcomeMessage">안녕하세요, 사용자님!</h2>
<p class="text-body text-secondary">오늘도 성공적인 이벤트를 준비해보세요</p>
</section>
<!-- KPI Cards -->
<section class="mb-xl">
<div class="grid grid-cols-3 gap-sm tablet:grid-cols-3">
<div id="kpiEvents"></div>
<div id="kpiParticipants"></div>
<div id="kpiROI"></div>
</div>
</section>
<!-- Quick Actions -->
<section class="mb-lg">
<div class="flex items-center justify-between mb-md">
<h3 class="text-headline">빠른 시작</h3>
</div>
<div class="grid grid-cols-2 gap-sm tablet:grid-cols-4">
<button class="card card-clickable p-md" id="createEvent">
<span class="material-icons text-kt-red" style="font-size: 32px;">add_circle</span>
<p class="text-body-small mt-sm">새 이벤트</p>
</button>
<button class="card card-clickable p-md" id="aiRecommend">
<span class="material-icons text-ai-blue" style="font-size: 32px;">auto_awesome</span>
<p class="text-body-small mt-sm">AI 추천</p>
</button>
<button class="card card-clickable p-md" id="viewTemplates">
<span class="material-icons text-secondary" style="font-size: 32px;">dashboard</span>
<p class="text-body-small mt-sm">템플릿</p>
</button>
<button class="card card-clickable p-md" id="viewAnalytics">
<span class="material-icons text-secondary" style="font-size: 32px;">analytics</span>
<p class="text-body-small mt-sm">분석</p>
</button>
</div>
</section>
<!-- Active Events -->
<section class="mb-xl">
<div class="flex items-center justify-between mb-md">
<h3 class="text-headline">진행 중인 이벤트</h3>
<a href="06-이벤트목록.html" class="text-body-small text-kt-red">
전체보기
<span class="material-icons" style="font-size: 16px; vertical-align: middle;">chevron_right</span>
</a>
</div>
<div id="activeEvents" class="flex flex-col gap-md">
<!-- 이벤트 카드가 동적으로 추가됩니다 -->
</div>
</section>
<!-- Recent Activity -->
<section class="mb-2xl">
<h3 class="text-headline mb-md">최근 활동</h3>
<div class="card">
<div id="recentActivity">
<!-- 최근 활동 목록 -->
</div>
</div>
</section>
</div>
<!-- Bottom Navigation -->
<div id="bottomNav"></div>
<!-- FAB -->
<div id="fab"></div>
</div>
<script src="common.js"></script>
<script>
// 로그인 확인
KTEventApp.Session.requireAuth();
const user = KTEventApp.Session.getUser();
// Header 생성
const header = KTEventApp.Navigation.createHeader({
title: '대시보드',
showBack: false,
showMenu: true,
showProfile: true
});
document.getElementById('header').appendChild(header);
// Welcome 메시지
document.getElementById('welcomeMessage').textContent = `안녕하세요, ${user.name}님!`;
// KPI 데이터 계산
const events = KTEventApp.MockData.getEvents();
const activeEvents = events.filter(e => e.status === '진행중');
const totalParticipants = events.reduce((sum, e) => sum + e.participants, 0);
const avgROI = events.length > 0
? Math.round(events.reduce((sum, e) => sum + e.roi, 0) / events.length)
: 0;
// KPI 카드 생성
const kpiEventsCard = KTEventApp.Cards.createKPICard({
icon: 'celebration',
iconType: 'primary',
label: '진행 중',
value: `${activeEvents.length}개`
});
document.getElementById('kpiEvents').appendChild(kpiEventsCard);
const kpiParticipantsCard = KTEventApp.Cards.createKPICard({
icon: 'group',
iconType: 'success',
label: '총 참여자',
value: `${KTEventApp.Utils.formatNumber(totalParticipants)}명`
});
document.getElementById('kpiParticipants').appendChild(kpiParticipantsCard);
const kpiROICard = KTEventApp.Cards.createKPICard({
icon: 'trending_up',
iconType: 'ai',
label: '평균 ROI',
value: `${avgROI}%`
});
document.getElementById('kpiROI').appendChild(kpiROICard);
// 진행 중인 이벤트 표시
const activeEventsContainer = document.getElementById('activeEvents');
if (activeEvents.length === 0) {
activeEventsContainer.innerHTML = `
<div class="card text-center p-xl">
<span class="material-icons text-tertiary" style="font-size: 48px;">event_busy</span>
<p class="text-body text-secondary mt-md">진행 중인 이벤트가 없습니다</p>
<button class="btn btn-primary btn-medium mt-md" onclick="createNewEvent()">
<span class="material-icons">add</span>
새 이벤트 만들기
</button>
</div>
`;
} else {
activeEvents.forEach(event => {
const card = KTEventApp.Cards.createEventCard({
...event,
onClick: (id) => {
window.location.href = `13-이벤트상세.html?id=${id}`;
}
});
activeEventsContainer.appendChild(card);
});
}
// 최근 활동 표시
const recentActivityContainer = document.getElementById('recentActivity');
const activities = [
{ icon: 'person_add', text: 'SNS 팔로우 이벤트에 새로운 참여자 12명', time: '5분 전' },
{ icon: 'edit', text: '설 맞이 할인 이벤트 내용을 수정했습니다', time: '1시간 전' },
{ icon: 'check_circle', text: '고객 만족도 조사가 종료되었습니다', time: '3시간 전' }
];
activities.forEach((activity, index) => {
const item = document.createElement('div');
item.className = 'flex items-start gap-md';
if (index > 0) item.classList.add('mt-md', 'pt-md', 'border-t');
item.innerHTML = `
<span class="material-icons text-kt-red">${activity.icon}</span>
<div class="flex-1">
<p class="text-body">${activity.text}</p>
<p class="text-caption text-tertiary mt-xs">${activity.time}</p>
</div>
`;
recentActivityContainer.appendChild(item);
});
// Bottom Navigation 생성
const bottomNav = KTEventApp.Navigation.createBottomNav('home');
document.getElementById('bottomNav').appendChild(bottomNav);
// FAB 생성
const fab = KTEventApp.Navigation.createFAB('add', createNewEvent);
document.getElementById('fab').appendChild(fab);
// 빠른 시작 버튼 이벤트
document.getElementById('createEvent').addEventListener('click', createNewEvent);
document.getElementById('aiRecommend').addEventListener('click', () => {
window.location.href = '07-이벤트목적선택.html';
});
document.getElementById('viewTemplates').addEventListener('click', () => {
KTEventApp.Feedback.showToast('템플릿 기능은 준비 중입니다.');
});
document.getElementById('viewAnalytics').addEventListener('click', () => {
window.location.href = '13-이벤트상세.html?id=' + events[0].id;
});
// 새 이벤트 생성 함수
function createNewEvent() {
KTEventApp.Feedback.showBottomSheet(`
<h3 class="text-headline mb-lg">이벤트 만들기</h3>
<div class="flex flex-col gap-sm">
<button class="btn btn-primary btn-large btn-full" onclick="window.location.href='07-이벤트목적선택.html'">
<span class="material-icons">auto_awesome</span>
AI 추천으로 시작하기
</button>
<button class="btn btn-secondary btn-large btn-full" onclick="KTEventApp.Feedback.showToast('템플릿 기능은 준비 중입니다.')">
<span class="material-icons">dashboard</span>
템플릿으로 시작하기
</button>
<button class="btn btn-text btn-large btn-full" onclick="KTEventApp.Feedback.showToast('직접 만들기 기능은 준비 중입니다.')">
<span class="material-icons">edit</span>
직접 만들기
</button>
</div>
`);
}
</script>
</body>
</html>

View File

@ -0,0 +1,216 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>이벤트 목적 선택 - KT AI 이벤트 마케팅</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
</head>
<body>
<div class="page page-with-header">
<!-- Header -->
<div id="header"></div>
<div class="container" style="max-width: 600px;">
<!-- Title Section -->
<section class="mt-lg mb-xl text-center">
<div class="mb-md">
<span class="material-icons" style="font-size: 64px; color: var(--color-ai-blue);">auto_awesome</span>
</div>
<h2 class="text-title-large mb-sm">이벤트 목적을 선택해주세요</h2>
<p class="text-body text-secondary">AI가 목적에 맞는 최적의 이벤트를 추천해드립니다</p>
</section>
<!-- Purpose Options -->
<section class="mb-xl">
<div id="purposeOptions" class="grid gap-md tablet:grid-cols-2">
<!-- 옵션 카드가 동적으로 추가됩니다 -->
</div>
</section>
<!-- Action Buttons -->
<section class="mb-2xl">
<button id="nextBtn" class="btn btn-primary btn-large btn-full" disabled>
다음
</button>
<button id="skipBtn" class="btn btn-text btn-large btn-full mt-sm">
건너뛰기
</button>
</section>
<!-- Info Box -->
<section class="mb-2xl">
<div class="card" style="background: rgba(0, 102, 255, 0.05); border: 1px solid rgba(0, 102, 255, 0.2);">
<div class="flex items-start gap-md">
<span class="material-icons text-ai-blue">info</span>
<div class="flex-1">
<p class="text-body-small text-secondary">
선택하신 목적에 따라 AI가 업종, 지역, 계절 트렌드를 분석하여 가장 효과적인 이벤트를 추천합니다.
</p>
</div>
</div>
</div>
</section>
</div>
<!-- Bottom Navigation -->
<div id="bottomNav"></div>
</div>
<script src="common.js"></script>
<script>
// 로그인 확인
KTEventApp.Session.requireAuth();
const user = KTEventApp.Session.getUser();
// Header 생성
const header = KTEventApp.Navigation.createHeader({
title: '이벤트 목적 선택',
showBack: true,
showMenu: false,
showProfile: false
});
document.getElementById('header').appendChild(header);
// Bottom Navigation 생성
const bottomNav = KTEventApp.Navigation.createBottomNav('events');
document.getElementById('bottomNav').appendChild(bottomNav);
// 이벤트 목적 옵션 데이터
const purposes = KTEventApp.MockData.getEventPurposes();
// 선택된 목적 저장
let selectedPurpose = null;
// 옵션 카드 생성
const purposeOptionsContainer = document.getElementById('purposeOptions');
purposes.forEach(purpose => {
const card = document.createElement('div');
card.className = 'card option-card';
card.setAttribute('data-purpose-id', purpose.id);
card.innerHTML = `
<div class="option-card-radio">
<input type="radio" name="purpose" value="${purpose.id}">
</div>
<div class="flex items-start gap-md mb-md">
<span class="material-icons text-kt-red" style="font-size: 40px;">${purpose.icon}</span>
<div class="flex-1">
<h3 class="text-headline mb-xs">${purpose.title}</h3>
</div>
</div>
<p class="text-body-small text-secondary">${purpose.description}</p>
`;
card.addEventListener('click', () => {
// 모든 카드에서 selected 클래스 제거
document.querySelectorAll('.option-card').forEach(c => {
c.classList.remove('selected');
c.querySelector('input[type="radio"]').checked = false;
});
// 선택된 카드에 selected 클래스 추가
card.classList.add('selected');
card.querySelector('input[type="radio"]').checked = true;
// 선택된 목적 저장
selectedPurpose = purpose.id;
// 다음 버튼 활성화
document.getElementById('nextBtn').disabled = false;
});
purposeOptionsContainer.appendChild(card);
});
// 다음 버튼 클릭
document.getElementById('nextBtn').addEventListener('click', () => {
if (selectedPurpose) {
// 선택한 목적 저장
KTEventApp.Utils.saveToStorage('selected_purpose', selectedPurpose);
// AI 로딩 및 추천 페이지로 이동
showLoadingAndNavigate();
}
});
// 건너뛰기 버튼 클릭
document.getElementById('skipBtn').addEventListener('click', () => {
KTEventApp.Feedback.showModal({
title: '건너뛰기',
content: '<p class="text-body">목적을 선택하지 않으면 일반적인 추천을 받게 됩니다. 계속하시겠습니까?</p>',
buttons: [
{
text: '취소',
variant: 'text',
onClick: function() {
this.closest('.modal-backdrop').remove();
}
},
{
text: '확인',
variant: 'primary',
onClick: function() {
this.closest('.modal-backdrop').remove();
KTEventApp.Utils.saveToStorage('selected_purpose', 'general');
showLoadingAndNavigate();
}
}
]
});
});
// AI 로딩 표시 및 페이지 이동
function showLoadingAndNavigate() {
const loadingModal = KTEventApp.Feedback.showModal({
content: `
<div class="p-xl text-center">
<div class="spinner mx-auto mb-lg"></div>
<h3 class="text-headline mb-sm">AI가 분석 중입니다</h3>
<p class="text-body text-secondary mb-md">
업종, 지역, 계절 트렌드를 분석하여<br>
최적의 이벤트를 찾고 있습니다
</p>
<div class="progress mt-lg">
<div id="loadingProgress" class="progress-bar" style="width: 0%"></div>
</div>
<p class="text-caption text-tertiary mt-sm" id="loadingStatus">트렌드 분석 중...</p>
</div>
`
});
// 프로그레스 바 애니메이션
const statusMessages = [
'트렌드 분석 중...',
'경쟁사 이벤트 분석 중...',
'고객 선호도 분석 중...',
'최적 이벤트 생성 중...'
];
let progress = 0;
let messageIndex = 0;
const interval = setInterval(() => {
progress += 25;
document.getElementById('loadingProgress').style.width = `${progress}%`;
if (messageIndex < statusMessages.length) {
document.getElementById('loadingStatus').textContent = statusMessages[messageIndex];
messageIndex++;
}
if (progress >= 100) {
clearInterval(interval);
setTimeout(() => {
window.location.href = '08-AI이벤트추천.html';
}, 500);
}
}, 800);
}
</script>
</body>
</html>

View File

@ -0,0 +1,473 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI 이벤트 추천 - KT AI 이벤트 마케팅</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
<style>
.editable-field {
border: 1px dashed var(--color-gray-300);
padding: 4px 8px;
border-radius: var(--radius-sm);
cursor: text;
transition: all var(--duration-fast) var(--ease-out);
}
.editable-field:hover {
border-color: var(--color-kt-red);
background-color: var(--color-gray-50);
}
.editable-field:focus {
outline: none;
border-style: solid;
border-color: var(--color-kt-red);
background-color: var(--color-bg-primary);
}
.budget-section {
scroll-margin-top: 72px;
}
</style>
</head>
<body>
<div class="page page-with-header">
<!-- Header -->
<div id="header"></div>
<div class="container">
<!-- Trends Section -->
<section class="mt-lg mb-xl">
<h2 class="text-headline mb-md flex items-center gap-sm">
<span class="material-icons text-ai-blue">insights</span>
AI 트렌드 분석
</h2>
<div class="card">
<div class="mb-md">
<div class="flex items-center gap-sm mb-xs">
<span class="material-icons text-kt-red" style="font-size: 20px;">store</span>
<span class="text-body-small text-bold">업종 트렌드</span>
</div>
<p id="industryTrend" class="text-body text-secondary pl-lg">음식점업 신년 프로모션 트렌드</p>
</div>
<div class="mb-md">
<div class="flex items-center gap-sm mb-xs">
<span class="material-icons text-kt-red" style="font-size: 20px;">location_on</span>
<span class="text-body-small text-bold">지역 트렌드</span>
</div>
<p id="locationTrend" class="text-body text-secondary pl-lg">강남구 음식점 할인 이벤트 증가</p>
</div>
<div>
<div class="flex items-center gap-sm mb-xs">
<span class="material-icons text-kt-red" style="font-size: 20px;">wb_sunny</span>
<span class="text-body-small text-bold">시즌 트렌드</span>
</div>
<p id="seasonTrend" class="text-body text-secondary pl-lg">설 연휴 특수 대비 고객 유치 전략</p>
</div>
</div>
</section>
<!-- Recommendations Title -->
<section class="mb-lg">
<h2 class="text-headline mb-sm">예산별 추천 이벤트</h2>
<p class="text-body-small text-secondary">
각 예산별 2가지 방식 (온라인 1개, 오프라인 1개)을 추천합니다
</p>
</section>
<!-- Budget Options Navigation -->
<section class="mb-lg" style="position: sticky; top: 56px; background: var(--color-bg-secondary); z-index: 10; padding: var(--spacing-md) 0;">
<div class="flex gap-sm">
<button class="btn btn-medium flex-1 budget-nav active" data-budget="low">
💰 저비용
</button>
<button class="btn btn-medium flex-1 budget-nav" data-budget="medium">
💰💰 중비용
</button>
<button class="btn btn-medium flex-1 budget-nav" data-budget="high">
💰💰💰 고비용
</button>
</div>
</section>
<!-- 저비용 옵션 -->
<section id="budget-low" class="budget-section mb-2xl">
<h3 class="text-title mb-md">💰 옵션 1: 저비용 (25~30만원)</h3>
<div class="grid gap-md tablet:grid-cols-2">
<div class="card option-card" data-recommendation-id="low-online">
<div class="option-card-radio">
<input type="radio" name="recommendation" value="low-online">
</div>
<div class="mb-md">
<span class="badge-active event-card-badge mb-sm">🌐 온라인 방식</span>
<h4 class="text-headline mb-xs">
<span class="editable-field" contenteditable="true">SNS 팔로우 이벤트</span>
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
</h4>
</div>
<div class="text-body-small mb-sm">
<span class="text-secondary">경품:</span>
<span class="editable-field" contenteditable="true">커피 쿠폰</span>
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
</div>
<div class="grid grid-cols-2 gap-sm text-body-small">
<div>
<span class="text-tertiary">참여 방법:</span>
<span class="text-semibold">SNS 팔로우</span>
</div>
<div>
<span class="text-tertiary">예상 참여:</span>
<span class="text-semibold">180명</span>
</div>
<div>
<span class="text-tertiary">예상 비용:</span>
<span class="text-semibold">25만원</span>
</div>
<div>
<span class="text-tertiary">투자대비수익률:</span>
<span class="text-kt-red text-bold">520%</span>
</div>
</div>
</div>
<div class="card option-card" data-recommendation-id="low-offline">
<div class="option-card-radio">
<input type="radio" name="recommendation" value="low-offline">
</div>
<div class="mb-md">
<span class="badge-scheduled event-card-badge mb-sm">🏪 오프라인 방식</span>
<h4 class="text-headline mb-xs">
<span class="editable-field" contenteditable="true">전화번호 등록 이벤트</span>
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
</h4>
</div>
<div class="text-body-small mb-sm">
<span class="text-secondary">경품:</span>
<span class="editable-field" contenteditable="true">커피 쿠폰</span>
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
</div>
<div class="grid grid-cols-2 gap-sm text-body-small">
<div>
<span class="text-tertiary">참여 방법:</span>
<span class="text-semibold">전화번호 등록</span>
</div>
<div>
<span class="text-tertiary">예상 참여:</span>
<span class="text-semibold">150명</span>
</div>
<div>
<span class="text-tertiary">예상 비용:</span>
<span class="text-semibold">30만원</span>
</div>
<div>
<span class="text-tertiary">투자대비수익률:</span>
<span class="text-kt-red text-bold">450%</span>
</div>
</div>
</div>
</div>
</section>
<!-- 중비용 옵션 -->
<section id="budget-medium" class="budget-section mb-2xl">
<h3 class="text-title mb-md">💰💰 옵션 2: 중비용 (150~180만원)</h3>
<div class="grid gap-md tablet:grid-cols-2">
<div class="card option-card" data-recommendation-id="medium-online">
<div class="option-card-radio">
<input type="radio" name="recommendation" value="medium-online">
</div>
<div class="mb-md">
<span class="badge-active event-card-badge mb-sm">🌐 온라인 방식</span>
<h4 class="text-headline mb-xs">
<span class="editable-field" contenteditable="true">리뷰 작성 이벤트</span>
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
</h4>
</div>
<div class="text-body-small mb-sm">
<span class="text-secondary">경품:</span>
<span class="editable-field" contenteditable="true">5천원 상품권</span>
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
</div>
<div class="grid grid-cols-2 gap-sm text-body-small">
<div>
<span class="text-tertiary">참여 방법:</span>
<span class="text-semibold">리뷰 작성</span>
</div>
<div>
<span class="text-tertiary">예상 참여:</span>
<span class="text-semibold">250명</span>
</div>
<div>
<span class="text-tertiary">예상 비용:</span>
<span class="text-semibold">150만원</span>
</div>
<div>
<span class="text-tertiary">투자대비수익률:</span>
<span class="text-kt-red text-bold">380%</span>
</div>
</div>
</div>
<div class="card option-card" data-recommendation-id="medium-offline">
<div class="option-card-radio">
<input type="radio" name="recommendation" value="medium-offline">
</div>
<div class="mb-md">
<span class="badge-scheduled event-card-badge mb-sm">🏪 오프라인 방식</span>
<h4 class="text-headline mb-xs">
<span class="editable-field" contenteditable="true">방문 도장 적립 이벤트</span>
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
</h4>
</div>
<div class="text-body-small mb-sm">
<span class="text-secondary">경품:</span>
<span class="editable-field" contenteditable="true">무료 식사권</span>
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
</div>
<div class="grid grid-cols-2 gap-sm text-body-small">
<div>
<span class="text-tertiary">참여 방법:</span>
<span class="text-semibold">방문 도장 적립</span>
</div>
<div>
<span class="text-tertiary">예상 참여:</span>
<span class="text-semibold">200명</span>
</div>
<div>
<span class="text-tertiary">예상 비용:</span>
<span class="text-semibold">180만원</span>
</div>
<div>
<span class="text-tertiary">투자대비수익률:</span>
<span class="text-kt-red text-bold">320%</span>
</div>
</div>
</div>
</div>
</section>
<!-- 고비용 옵션 -->
<section id="budget-high" class="budget-section mb-2xl">
<h3 class="text-title mb-md">💰💰💰 옵션 3: 고비용 (500~600만원)</h3>
<div class="grid gap-md tablet:grid-cols-2">
<div class="card option-card" data-recommendation-id="high-online">
<div class="option-card-radio">
<input type="radio" name="recommendation" value="high-online">
</div>
<div class="mb-md">
<span class="badge-active event-card-badge mb-sm">🌐 온라인 방식</span>
<h4 class="text-headline mb-xs">
<span class="editable-field" contenteditable="true">인플루언서 협업 이벤트</span>
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
</h4>
</div>
<div class="text-body-small mb-sm">
<span class="text-secondary">경품:</span>
<span class="editable-field" contenteditable="true">1만원 할인권</span>
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
</div>
<div class="grid grid-cols-2 gap-sm text-body-small">
<div>
<span class="text-tertiary">참여 방법:</span>
<span class="text-semibold">인플루언서 팔로우</span>
</div>
<div>
<span class="text-tertiary">예상 참여:</span>
<span class="text-semibold">400명</span>
</div>
<div>
<span class="text-tertiary">예상 비용:</span>
<span class="text-semibold">500만원</span>
</div>
<div>
<span class="text-tertiary">투자대비수익률:</span>
<span class="text-kt-red text-bold">280%</span>
</div>
</div>
</div>
<div class="card option-card" data-recommendation-id="high-offline">
<div class="option-card-radio">
<input type="radio" name="recommendation" value="high-offline">
</div>
<div class="mb-md">
<span class="badge-scheduled event-card-badge mb-sm">🏪 오프라인 방식</span>
<h4 class="text-headline mb-xs">
<span class="editable-field" contenteditable="true">VIP 고객 초대 이벤트</span>
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
</h4>
</div>
<div class="text-body-small mb-sm">
<span class="text-secondary">경품:</span>
<span class="editable-field" contenteditable="true">특별 메뉴 제공</span>
<span class="material-icons text-tertiary" style="font-size: 16px; vertical-align: middle;">edit</span>
</div>
<div class="grid grid-cols-2 gap-sm text-body-small">
<div>
<span class="text-tertiary">참여 방법:</span>
<span class="text-semibold">VIP 초대장</span>
</div>
<div>
<span class="text-tertiary">예상 참여:</span>
<span class="text-semibold">300명</span>
</div>
<div>
<span class="text-tertiary">예상 비용:</span>
<span class="text-semibold">600만원</span>
</div>
<div>
<span class="text-tertiary">투자대비수익률:</span>
<span class="text-kt-red text-bold">240%</span>
</div>
</div>
</div>
</div>
</section>
<!-- Action Buttons -->
<section class="mb-2xl">
<button id="nextBtn" class="btn btn-primary btn-large btn-full" disabled>
선택한 이벤트로 계속
</button>
<button id="regenerateBtn" class="btn btn-secondary btn-large btn-full mt-sm">
<span class="material-icons">refresh</span>
다시 추천받기
</button>
</section>
</div>
<!-- Bottom Navigation -->
<div id="bottomNav"></div>
</div>
<script src="common.js"></script>
<script>
// 로그인 확인
KTEventApp.Session.requireAuth();
// Header 생성
const header = KTEventApp.Navigation.createHeader({
title: 'AI 이벤트 추천',
showBack: true,
showMenu: false,
showProfile: false
});
document.getElementById('header').appendChild(header);
// Bottom Navigation 생성
const bottomNav = KTEventApp.Navigation.createBottomNav('events');
document.getElementById('bottomNav').appendChild(bottomNav);
// 선택된 추천 저장
let selectedRecommendation = null;
// 옵션 카드 클릭 이벤트
document.querySelectorAll('.option-card').forEach(card => {
card.addEventListener('click', () => {
// 모든 카드에서 selected 클래스 제거
document.querySelectorAll('.option-card').forEach(c => {
c.classList.remove('selected');
c.querySelector('input[type="radio"]').checked = false;
});
// 선택된 카드에 selected 클래스 추가
card.classList.add('selected');
card.querySelector('input[type="radio"]').checked = true;
// 선택된 추천 저장
selectedRecommendation = card.getAttribute('data-recommendation-id');
// 다음 버튼 활성화
document.getElementById('nextBtn').disabled = false;
// Toast 표시
KTEventApp.Feedback.showToast('이벤트가 선택되었습니다');
});
});
// 예산 옵션 네비게이션
document.querySelectorAll('.budget-nav').forEach(btn => {
btn.addEventListener('click', function() {
const budget = this.getAttribute('data-budget');
// 버튼 활성화 상태 변경
document.querySelectorAll('.budget-nav').forEach(b => {
b.classList.remove('active');
b.classList.add('btn-secondary');
b.classList.remove('btn-primary');
});
this.classList.add('active');
this.classList.add('btn-primary');
this.classList.remove('btn-secondary');
// 해당 섹션으로 스크롤
document.getElementById(`budget-${budget}`).scrollIntoView({
behavior: 'smooth',
block: 'start'
});
});
});
// 다음 버튼 클릭
document.getElementById('nextBtn').addEventListener('click', () => {
if (selectedRecommendation) {
// 선택한 추천 데이터 저장
const selectedCard = document.querySelector(`[data-recommendation-id="${selectedRecommendation}"]`);
const title = selectedCard.querySelector('.editable-field').textContent.trim();
const prize = selectedCard.querySelectorAll('.editable-field')[1].textContent.trim();
const recommendationData = {
id: selectedRecommendation,
title: title,
prize: prize,
timestamp: new Date().toISOString()
};
KTEventApp.Utils.saveToStorage('selected_recommendation', recommendationData);
// 콘텐츠 편집 페이지로 이동
window.location.href = '10-콘텐츠편집.html';
}
});
// 다시 추천받기 버튼
document.getElementById('regenerateBtn').addEventListener('click', () => {
KTEventApp.Feedback.showModal({
title: '다시 추천받기',
content: '<p class="text-body">새로운 추천을 받으시겠습니까? 현재 선택한 내용은 초기화됩니다.</p>',
buttons: [
{
text: '취소',
variant: 'text',
onClick: function() {
this.closest('.modal-backdrop').remove();
}
},
{
text: '확인',
variant: 'primary',
onClick: function() {
this.closest('.modal-backdrop').remove();
window.location.href = '07-이벤트목적선택.html';
}
}
]
});
});
// 편집 가능 필드 엔터 키 방지
document.querySelectorAll('.editable-field').forEach(field => {
field.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
field.blur();
}
});
field.addEventListener('blur', () => {
KTEventApp.Feedback.showToast('수정되었습니다', 1500);
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,296 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>최종 승인 - KT AI 이벤트 마케팅</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
</head>
<body>
<div class="page page-with-header">
<!-- Header -->
<div id="header"></div>
<div class="container" style="max-width: 600px;">
<!-- Title Section -->
<section class="mt-lg mb-lg text-center">
<div class="mb-md">
<span class="material-icons" style="font-size: 64px; color: var(--color-success);">check_circle</span>
</div>
<h2 class="text-title-large mb-sm">이벤트를 확인해주세요</h2>
<p class="text-body text-secondary">모든 정보를 검토한 후 배포하세요</p>
</section>
<!-- Event Summary Card -->
<section class="mb-lg">
<div class="card">
<h3 class="text-headline mb-md" id="eventTitle">SNS 팔로우 이벤트</h3>
<div class="flex items-center gap-sm mb-md">
<span class="event-card-badge badge-scheduled">배포 대기</span>
<span class="event-card-badge" style="background: rgba(0, 102, 255, 0.1); color: var(--color-ai-blue);">
AI 추천
</span>
</div>
<div class="border-t pt-md">
<div class="grid grid-cols-2 gap-md text-body-small">
<div>
<p class="text-tertiary mb-xs">이벤트 기간</p>
<p class="text-semibold" id="eventPeriod">2025.02.01 ~ 2025.02.28</p>
</div>
<div>
<p class="text-tertiary mb-xs">목표 참여자</p>
<p class="text-semibold" id="targetParticipants">180명</p>
</div>
<div>
<p class="text-tertiary mb-xs">예상 비용</p>
<p class="text-semibold" id="estimatedCost">250,000원</p>
</div>
<div>
<p class="text-tertiary mb-xs">예상 ROI</p>
<p class="text-semibold text-kt-red" id="estimatedROI">520%</p>
</div>
</div>
</div>
</div>
</section>
<!-- Event Details -->
<section class="mb-lg">
<h3 class="text-headline mb-md">이벤트 상세</h3>
<div class="card mb-sm">
<div class="flex items-start gap-md">
<span class="material-icons text-kt-red">celebration</span>
<div class="flex-1">
<p class="text-body-small text-tertiary mb-xs">이벤트 제목</p>
<p class="text-body" id="detailTitle">SNS 팔로우 이벤트</p>
</div>
<button class="header-icon-btn" onclick="window.location.href='10-콘텐츠편집.html'">
<span class="material-icons">edit</span>
</button>
</div>
</div>
<div class="card mb-sm">
<div class="flex items-start gap-md">
<span class="material-icons text-kt-red">card_giftcard</span>
<div class="flex-1">
<p class="text-body-small text-tertiary mb-xs">경품</p>
<p class="text-body" id="detailPrize">커피 쿠폰</p>
</div>
<button class="header-icon-btn" onclick="window.location.href='10-콘텐츠편집.html'">
<span class="material-icons">edit</span>
</button>
</div>
</div>
<div class="card mb-sm">
<div class="flex items-start gap-md">
<span class="material-icons text-kt-red">description</span>
<div class="flex-1">
<p class="text-body-small text-tertiary mb-xs">이벤트 설명</p>
<p class="text-body" id="detailDescription">
SNS를 팔로우하고 커피 쿠폰을 받으세요!<br>
많은 참여 부탁드립니다.
</p>
</div>
<button class="header-icon-btn" onclick="window.location.href='10-콘텐츠편집.html'">
<span class="material-icons">edit</span>
</button>
</div>
</div>
<div class="card">
<div class="flex items-start gap-md">
<span class="material-icons text-kt-red">how_to_reg</span>
<div class="flex-1">
<p class="text-body-small text-tertiary mb-xs">참여 방법</p>
<p class="text-body" id="detailMethod">SNS 팔로우</p>
</div>
</div>
</div>
</section>
<!-- Distribution Channels -->
<section class="mb-lg">
<h3 class="text-headline mb-md">배포 채널</h3>
<div class="card">
<div id="channels" class="flex flex-wrap gap-sm">
<span class="event-card-badge badge-active">
<span class="material-icons" style="font-size: 16px; vertical-align: middle;">language</span>
홈페이지
</span>
<span class="event-card-badge badge-active">
<span class="material-icons" style="font-size: 16px; vertical-align: middle;">chat_bubble</span>
카카오톡
</span>
<span class="event-card-badge badge-active">
<span class="material-icons" style="font-size: 16px; vertical-align: middle;">share</span>
Instagram
</span>
</div>
<button class="btn btn-text btn-small mt-md" onclick="window.location.href='11-배포채널선택.html'">
<span class="material-icons">edit</span>
채널 수정하기
</button>
</div>
</section>
<!-- Terms Agreement -->
<section class="mb-xl">
<div class="card" style="background: var(--color-gray-50);">
<div class="form-check">
<input type="checkbox" id="agreeTerms" class="form-check-input" required>
<label for="agreeTerms" class="form-check-label">
이벤트 약관 및 개인정보 처리방침에 동의합니다 <span class="text-kt-red">(필수)</span>
</label>
</div>
<a href="#" class="text-body-small text-kt-red mt-sm inline-block" id="viewTerms">약관 보기</a>
</div>
</section>
<!-- Action Buttons -->
<section class="mb-2xl">
<button id="approveBtn" class="btn btn-primary btn-large btn-full mb-sm" disabled>
<span class="material-icons">rocket_launch</span>
배포하기
</button>
<button id="saveBtn" class="btn btn-secondary btn-large btn-full mb-sm">
<span class="material-icons">save</span>
임시저장
</button>
<button id="backBtn" class="btn btn-text btn-large btn-full">
<span class="material-icons">arrow_back</span>
이전으로
</button>
</section>
</div>
<!-- Bottom Navigation -->
<div id="bottomNav"></div>
</div>
<script src="common.js"></script>
<script>
// 로그인 확인
KTEventApp.Session.requireAuth();
// Header 생성
const header = KTEventApp.Navigation.createHeader({
title: '최종 승인',
showBack: true,
showMenu: false,
showProfile: false
});
document.getElementById('header').appendChild(header);
// Bottom Navigation 생성
const bottomNav = KTEventApp.Navigation.createBottomNav('events');
document.getElementById('bottomNav').appendChild(bottomNav);
// 저장된 데이터 불러오기
const recommendation = KTEventApp.Utils.getFromStorage('selected_recommendation') || {
title: 'SNS 팔로우 이벤트',
prize: '커피 쿠폰'
};
// 데이터 표시
document.getElementById('eventTitle').textContent = recommendation.title;
document.getElementById('detailTitle').textContent = recommendation.title;
document.getElementById('detailPrize').textContent = recommendation.prize;
// 약관 동의 체크박스
document.getElementById('agreeTerms').addEventListener('change', function(e) {
document.getElementById('approveBtn').disabled = !e.target.checked;
});
// 약관 보기
document.getElementById('viewTerms').addEventListener('click', function(e) {
e.preventDefault();
KTEventApp.Feedback.showModal({
title: '이벤트 약관',
content: `
<div class="p-md" style="max-height: 400px; overflow-y: auto;">
<h4 class="text-headline mb-md">제1조 (목적)</h4>
<p class="text-body-small mb-lg">
본 약관은 KT AI 이벤트 마케팅 서비스를 통해 진행되는 이벤트의 참여 및 개인정보 처리에 관한 사항을 규정합니다.
</p>
<h4 class="text-headline mb-md">제2조 (개인정보 수집 및 이용)</h4>
<p class="text-body-small mb-lg">
수집 항목: 이름, 전화번호, 이메일<br>
이용 목적: 이벤트 참여 확인 및 경품 제공<br>
보유 기간: 이벤트 종료 후 6개월
</p>
<h4 class="text-headline mb-md">제3조 (당첨자 발표)</h4>
<p class="text-body-small">
당첨자는 이벤트 종료 후 7일 이내 개별 연락 드립니다.
</p>
</div>
`,
buttons: [
{
text: '확인',
variant: 'primary',
onClick: function() {
this.closest('.modal-backdrop').remove();
}
}
]
});
});
// 배포하기 버튼
document.getElementById('approveBtn').addEventListener('click', function() {
const button = this;
button.disabled = true;
button.innerHTML = '<span class="material-icons">hourglass_empty</span> 배포 중...';
// 배포 시뮬레이션
setTimeout(() => {
// 성공 모달 표시
const successModal = KTEventApp.Feedback.showModal({
content: `
<div class="p-xl text-center">
<span class="material-icons" style="font-size: 80px; color: var(--color-success);">check_circle</span>
<h3 class="text-title-large mt-md mb-sm">배포 완료!</h3>
<p class="text-body text-secondary mb-lg">
이벤트가 성공적으로 배포되었습니다.<br>
실시간으로 참여자를 확인할 수 있습니다.
</p>
<div class="flex flex-col gap-sm">
<button class="btn btn-primary btn-large btn-full" onclick="window.location.href='13-이벤트상세.html?id=new'">
이벤트 상세 보기
</button>
<button class="btn btn-secondary btn-large btn-full" onclick="window.location.href='05-대시보드.html'">
대시보드로 이동
</button>
</div>
</div>
`
});
}, 2000);
});
// 임시저장 버튼
document.getElementById('saveBtn').addEventListener('click', function() {
// 현재 데이터 저장
const draftData = {
...recommendation,
savedAt: new Date().toISOString()
};
KTEventApp.Utils.saveToStorage('event_draft', draftData);
KTEventApp.Feedback.showToast('임시저장되었습니다');
});
// 이전으로 버튼
document.getElementById('backBtn').addEventListener('click', function() {
window.history.back();
});
</script>
</body>
</html>

View File

@ -0,0 +1,437 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>이벤트 상세 - KT AI 이벤트 마케팅</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
</head>
<body>
<div class="page page-with-header">
<!-- Header -->
<div id="header"></div>
<div class="container">
<!-- Event Header -->
<section class="mt-lg mb-lg">
<div class="flex items-center justify-between mb-md">
<h2 class="text-title-large" id="eventTitle">SNS 팔로우 이벤트</h2>
<button class="header-icon-btn" id="moreBtn">
<span class="material-icons">more_vert</span>
</button>
</div>
<div class="flex items-center gap-sm mb-md">
<span class="event-card-badge badge-active" id="statusBadge">진행중</span>
<span class="event-card-badge" style="background: rgba(0, 102, 255, 0.1); color: var(--color-ai-blue);">
AI 추천
</span>
</div>
<p class="text-body text-secondary" id="eventPeriod">
2025.01.15 ~ 2025.02.15
</p>
</section>
<!-- Real-time KPIs -->
<section class="mb-xl">
<div class="flex items-center justify-between mb-md">
<h3 class="text-headline">실시간 현황</h3>
<div class="flex items-center gap-xs text-body-small text-success">
<span class="material-icons" style="font-size: 16px;">fiber_manual_record</span>
<span>실시간 업데이트</span>
</div>
</div>
<div class="grid grid-cols-2 gap-sm tablet:grid-cols-4">
<div id="kpiParticipants"></div>
<div id="kpiViews"></div>
<div id="kpiROI"></div>
<div id="kpiConversion"></div>
</div>
</section>
<!-- Chart Section -->
<section class="mb-xl">
<h3 class="text-headline mb-md">참여 추이</h3>
<div class="card">
<div class="flex justify-between items-center mb-md">
<div class="flex gap-xs">
<button class="btn btn-small active" data-period="7d">7일</button>
<button class="btn btn-small btn-text" data-period="30d">30일</button>
<button class="btn btn-small btn-text" data-period="all">전체</button>
</div>
</div>
<!-- Simple Chart Placeholder -->
<div id="chartPlaceholder" style="height: 200px; background: var(--color-gray-50); border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center;">
<div class="text-center">
<span class="material-icons text-tertiary" style="font-size: 48px;">show_chart</span>
<p class="text-body-small text-tertiary mt-sm">참여자 추이 차트</p>
</div>
</div>
</div>
</section>
<!-- Event Details -->
<section class="mb-xl">
<h3 class="text-headline mb-md">이벤트 정보</h3>
<div class="card mb-sm">
<div class="flex items-start gap-md">
<span class="material-icons text-kt-red">card_giftcard</span>
<div class="flex-1">
<p class="text-body-small text-tertiary mb-xs">경품</p>
<p class="text-body" id="detailPrize">커피 쿠폰</p>
</div>
</div>
</div>
<div class="card mb-sm">
<div class="flex items-start gap-md">
<span class="material-icons text-kt-red">how_to_reg</span>
<div class="flex-1">
<p class="text-body-small text-tertiary mb-xs">참여 방법</p>
<p class="text-body" id="detailMethod">SNS 팔로우</p>
</div>
</div>
</div>
<div class="card mb-sm">
<div class="flex items-start gap-md">
<span class="material-icons text-kt-red">attach_money</span>
<div class="flex-1">
<p class="text-body-small text-tertiary mb-xs">예상 비용</p>
<p class="text-body" id="detailCost">250,000원</p>
</div>
</div>
</div>
<div class="card">
<div class="flex items-start gap-md">
<span class="material-icons text-kt-red">share</span>
<div class="flex-1">
<p class="text-body-small text-tertiary mb-xs">배포 채널</p>
<div class="flex flex-wrap gap-xs mt-sm">
<span class="event-card-badge badge-active">홈페이지</span>
<span class="event-card-badge badge-active">카카오톡</span>
<span class="event-card-badge badge-active">Instagram</span>
</div>
</div>
</div>
</div>
</section>
<!-- Quick Actions -->
<section class="mb-xl">
<h3 class="text-headline mb-md">빠른 작업</h3>
<div class="grid grid-cols-2 gap-sm tablet:grid-cols-4">
<button class="card card-clickable p-md" id="viewParticipants">
<span class="material-icons text-kt-red" style="font-size: 32px;">people</span>
<p class="text-body-small mt-sm">참여자 목록</p>
</button>
<button class="card card-clickable p-md" id="editEvent">
<span class="material-icons text-ai-blue" style="font-size: 32px;">edit</span>
<p class="text-body-small mt-sm">이벤트 수정</p>
</button>
<button class="card card-clickable p-md" id="shareEvent">
<span class="material-icons text-secondary" style="font-size: 32px;">share</span>
<p class="text-body-small mt-sm">공유하기</p>
</button>
<button class="card card-clickable p-md" id="exportData">
<span class="material-icons text-secondary" style="font-size: 32px;">download</span>
<p class="text-body-small mt-sm">데이터 다운</p>
</button>
</div>
</section>
<!-- Recent Participants -->
<section class="mb-2xl">
<div class="flex items-center justify-between mb-md">
<h3 class="text-headline">최근 참여자</h3>
<a href="14-참여자목록.html" class="text-body-small text-kt-red">
전체보기
<span class="material-icons" style="font-size: 16px; vertical-align: middle;">chevron_right</span>
</a>
</div>
<div class="card">
<div id="recentParticipants">
<!-- 참여자 목록 -->
</div>
</div>
</section>
</div>
<!-- Bottom Navigation -->
<div id="bottomNav"></div>
</div>
<script src="common.js"></script>
<script>
// 로그인 확인
KTEventApp.Session.requireAuth();
// Header 생성
const header = KTEventApp.Navigation.createHeader({
title: '이벤트 상세',
showBack: true,
showMenu: false,
showProfile: false
});
document.getElementById('header').appendChild(header);
// Bottom Navigation 생성
const bottomNav = KTEventApp.Navigation.createBottomNav('analytics');
document.getElementById('bottomNav').appendChild(bottomNav);
// URL에서 이벤트 ID 가져오기
const urlParams = new URLSearchParams(window.location.search);
const eventId = urlParams.get('id');
// 이벤트 데이터 가져오기
const events = KTEventApp.MockData.getEvents();
const event = events.find(e => e.id === eventId) || events[0];
// 이벤트 정보 표시
document.getElementById('eventTitle').textContent = event.title;
document.getElementById('eventPeriod').textContent =
`${KTEventApp.Utils.formatDate(event.startDate)} ~ ${KTEventApp.Utils.formatDate(event.endDate)}`;
document.getElementById('detailPrize').textContent = event.prize;
document.getElementById('detailMethod').textContent = `${event.channel} - SNS 팔로우`;
document.getElementById('detailCost').textContent = event.budget === '저비용' ? '250,000원' : '1,500,000원';
// 상태 배지
const statusBadge = document.getElementById('statusBadge');
statusBadge.textContent = event.status;
statusBadge.className = 'event-card-badge';
if (event.status === '진행중') {
statusBadge.classList.add('badge-active');
} else if (event.status === '예정') {
statusBadge.classList.add('badge-scheduled');
} else {
statusBadge.classList.add('badge-ended');
}
// KPI 카드 생성
const kpiParticipants = KTEventApp.Cards.createKPICard({
icon: 'group',
iconType: 'primary',
label: '참여자',
value: `${KTEventApp.Utils.formatNumber(event.participants)}명`
});
document.getElementById('kpiParticipants').appendChild(kpiParticipants);
const kpiViews = KTEventApp.Cards.createKPICard({
icon: 'visibility',
iconType: 'ai',
label: '조회수',
value: `${KTEventApp.Utils.formatNumber(event.views)}`
});
document.getElementById('kpiViews').appendChild(kpiViews);
const kpiROI = KTEventApp.Cards.createKPICard({
icon: 'trending_up',
iconType: 'success',
label: 'ROI',
value: `${event.roi}%`
});
document.getElementById('kpiROI').appendChild(kpiROI);
const conversion = event.views > 0 ? Math.round((event.participants / event.views) * 100) : 0;
const kpiConversion = KTEventApp.Cards.createKPICard({
icon: 'conversion_path',
iconType: 'primary',
label: '전환율',
value: `${conversion}%`
});
document.getElementById('kpiConversion').appendChild(kpiConversion);
// 최근 참여자 표시
const recentParticipantsContainer = document.getElementById('recentParticipants');
const participants = [
{ name: '김*진', phone: '010-****-1234', time: '5분 전' },
{ name: '이*수', phone: '010-****-5678', time: '12분 전' },
{ name: '박*영', phone: '010-****-9012', time: '25분 전' },
{ name: '최*민', phone: '010-****-3456', time: '1시간 전' },
{ name: '정*희', phone: '010-****-7890', time: '2시간 전' }
];
participants.forEach((participant, index) => {
const item = document.createElement('div');
item.className = 'flex items-center justify-between';
if (index > 0) item.classList.add('mt-md', 'pt-md', 'border-t');
item.innerHTML = `
<div class="flex items-center gap-md">
<div class="flex items-center justify-center" style="width: 40px; height: 40px; background: var(--color-gray-100); border-radius: var(--radius-full);">
<span class="material-icons text-tertiary">person</span>
</div>
<div>
<p class="text-body text-semibold">${participant.name}</p>
<p class="text-caption text-tertiary">${participant.phone}</p>
</div>
</div>
<p class="text-caption text-tertiary">${participant.time}</p>
`;
recentParticipantsContainer.appendChild(item);
});
// 더보기 메뉴
document.getElementById('moreBtn').addEventListener('click', function() {
KTEventApp.Feedback.showBottomSheet(`
<div class="flex flex-col gap-sm">
<button class="btn btn-text btn-large btn-full" onclick="editEvent()">
<span class="material-icons">edit</span>
이벤트 수정
</button>
<button class="btn btn-text btn-large btn-full" onclick="shareEvent()">
<span class="material-icons">share</span>
공유하기
</button>
<button class="btn btn-text btn-large btn-full" onclick="exportData()">
<span class="material-icons">download</span>
데이터 다운로드
</button>
<button class="btn btn-text btn-large btn-full text-error" onclick="deleteEvent()">
<span class="material-icons">delete</span>
이벤트 삭제
</button>
</div>
`);
});
// 빠른 작업 버튼들
document.getElementById('viewParticipants').addEventListener('click', () => {
window.location.href = '14-참여자목록.html?eventId=' + event.id;
});
document.getElementById('editEvent').addEventListener('click', editEvent);
document.getElementById('shareEvent').addEventListener('click', shareEvent);
document.getElementById('exportData').addEventListener('click', exportData);
// 차트 기간 버튼
document.querySelectorAll('[data-period]').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('[data-period]').forEach(b => {
b.classList.remove('active');
b.classList.add('btn-text');
});
this.classList.add('active');
this.classList.remove('btn-text');
KTEventApp.Feedback.showToast('차트가 업데이트되었습니다');
});
});
// 이벤트 수정
function editEvent() {
KTEventApp.Feedback.showToast('이벤트 수정 기능은 준비 중입니다.');
}
// 공유하기
function shareEvent() {
KTEventApp.Feedback.showModal({
title: '이벤트 공유',
content: `
<p class="text-body mb-md">이벤트 링크를 공유하세요</p>
<div class="form-group">
<input type="text" class="form-input" value="https://kt-event.co.kr/evt/${event.id}" readonly>
</div>
<div class="flex gap-sm mt-lg">
<button class="btn btn-secondary btn-large flex-1" onclick="KTEventApp.Feedback.showToast('카카오톡 공유 준비 중')">
카카오톡
</button>
<button class="btn btn-secondary btn-large flex-1" onclick="copyLink()">
링크 복사
</button>
</div>
`
});
}
// 링크 복사
function copyLink() {
KTEventApp.Feedback.showToast('링크가 복사되었습니다');
}
// 데이터 다운로드
function exportData() {
KTEventApp.Feedback.showModal({
title: '데이터 다운로드',
content: `
<p class="text-body mb-md">다운로드할 데이터 형식을 선택하세요</p>
<div class="flex flex-col gap-sm">
<button class="btn btn-secondary btn-large btn-full" onclick="download('excel')">
<span class="material-icons">table_chart</span>
Excel (XLSX)
</button>
<button class="btn btn-secondary btn-large btn-full" onclick="download('csv')">
<span class="material-icons">description</span>
CSV
</button>
<button class="btn btn-secondary btn-large btn-full" onclick="download('pdf')">
<span class="material-icons">picture_as_pdf</span>
PDF 리포트
</button>
</div>
`
});
}
function download(format) {
KTEventApp.Feedback.showToast(`${format.toUpperCase()} 파일 다운로드 준비 중...`);
}
// 이벤트 삭제
function deleteEvent() {
KTEventApp.Feedback.showModal({
title: '이벤트 삭제',
content: '<p class="text-body">정말로 이벤트를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.</p>',
buttons: [
{
text: '취소',
variant: 'text',
onClick: function() {
this.closest('.modal-backdrop').remove();
}
},
{
text: '삭제',
variant: 'primary',
onClick: function() {
this.closest('.modal-backdrop').remove();
KTEventApp.Feedback.showToast('이벤트가 삭제되었습니다');
setTimeout(() => {
window.location.href = '05-대시보드.html';
}, 1500);
}
}
]
});
}
// 실시간 업데이트 시뮬레이션 (5초마다)
if (event.status === '진행중') {
setInterval(() => {
// 참여자 수 업데이트 (랜덤 증가)
const increase = Math.floor(Math.random() * 3);
if (increase > 0) {
event.participants += increase;
document.querySelector('#kpiParticipants .kpi-value').textContent =
`${KTEventApp.Utils.formatNumber(event.participants)}명`;
// 작은 애니메이션 효과
const kpiCard = document.getElementById('kpiParticipants').firstChild;
kpiCard.style.transform = 'scale(1.05)';
setTimeout(() => {
kpiCard.style.transform = 'scale(1)';
}, 200);
}
}, 5000);
}
</script>
</body>
</html>

1071
design/uiux/prototype/common.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,973 @@
/* =================================================================
KT AI 이벤트 마케팅 서비스 - 공통 스타일시트
Version: 1.0.0
Mobile First Design Philosophy
================================================================= */
/* =================================================================
1. CSS Variables - Design System
================================================================= */
:root {
/* Brand Colors */
--color-kt-red: #E31E24;
--color-ai-blue: #0066FF;
/* Grayscale */
--color-gray-50: #F9FAFB;
--color-gray-100: #F3F4F6;
--color-gray-200: #E5E7EB;
--color-gray-300: #D1D5DB;
--color-gray-400: #9CA3AF;
--color-gray-500: #6B7280;
--color-gray-600: #4B5563;
--color-gray-700: #374151;
--color-gray-800: #1F2937;
--color-gray-900: #111827;
/* Semantic Colors */
--color-success: #10B981;
--color-warning: #F59E0B;
--color-error: #EF4444;
--color-info: #3B82F6;
/* Background */
--color-bg-primary: #FFFFFF;
--color-bg-secondary: #F9FAFB;
--color-bg-tertiary: #F3F4F6;
/* Text */
--color-text-primary: #111827;
--color-text-secondary: #4B5563;
--color-text-tertiary: #9CA3AF;
--color-text-inverse: #FFFFFF;
/* Gradients */
--gradient-primary: linear-gradient(135deg, #E31E24 0%, #B71419 100%);
--gradient-ai: linear-gradient(135deg, #0066FF 0%, #0052CC 100%);
/* Typography Scale */
--font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
--font-size-display: 28px;
--font-size-title-large: 24px;
--font-size-title: 20px;
--font-size-headline: 18px;
--font-size-body-large: 16px;
--font-size-body: 14px;
--font-size-body-small: 13px;
--font-size-caption: 12px;
--line-height-display: 1.2;
--line-height-title: 1.3;
--line-height-body: 1.5;
--line-height-caption: 1.4;
--font-weight-bold: 700;
--font-weight-semibold: 600;
--font-weight-medium: 500;
--font-weight-regular: 400;
/* Spacing System (4px grid) */
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--spacing-2xl: 48px;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15);
/* Border Radius */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
--radius-full: 9999px;
/* Z-Index */
--z-dropdown: 1000;
--z-sticky: 1020;
--z-fixed: 1030;
--z-modal-backdrop: 1040;
--z-modal: 1050;
--z-popover: 1060;
--z-tooltip: 1070;
/* Animation */
--duration-instant: 0ms;
--duration-fast: 150ms;
--duration-normal: 300ms;
--duration-slow: 500ms;
--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);
}
/* Tablet Responsive Variables */
@media (min-width: 768px) {
:root {
--font-size-display: 32px;
--font-size-title-large: 28px;
--font-size-title: 24px;
--font-size-headline: 20px;
}
}
/* Desktop Responsive Variables */
@media (min-width: 1024px) {
:root {
--font-size-display: 36px;
--font-size-title-large: 32px;
--font-size-title: 28px;
--font-size-headline: 22px;
}
}
/* =================================================================
2. Reset & Base Styles
================================================================= */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-size: 16px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font-family: var(--font-family);
font-size: var(--font-size-body);
line-height: var(--line-height-body);
color: var(--color-text-primary);
background-color: var(--color-bg-primary);
overflow-x: hidden;
}
/* =================================================================
3. Typography
================================================================= */
.text-display {
font-size: var(--font-size-display);
line-height: var(--line-height-display);
font-weight: var(--font-weight-bold);
}
.text-title-large {
font-size: var(--font-size-title-large);
line-height: var(--line-height-title);
font-weight: var(--font-weight-bold);
}
.text-title {
font-size: var(--font-size-title);
line-height: var(--line-height-title);
font-weight: var(--font-weight-semibold);
}
.text-headline {
font-size: var(--font-size-headline);
line-height: var(--line-height-title);
font-weight: var(--font-weight-semibold);
}
.text-body-large {
font-size: var(--font-size-body-large);
line-height: var(--line-height-body);
font-weight: var(--font-weight-regular);
}
.text-body {
font-size: var(--font-size-body);
line-height: var(--line-height-body);
font-weight: var(--font-weight-regular);
}
.text-body-small {
font-size: var(--font-size-body-small);
line-height: var(--line-height-body);
font-weight: var(--font-weight-regular);
}
.text-caption {
font-size: var(--font-size-caption);
line-height: var(--line-height-caption);
font-weight: var(--font-weight-regular);
}
.text-primary { color: var(--color-text-primary); }
.text-secondary { color: var(--color-text-secondary); }
.text-tertiary { color: var(--color-text-tertiary); }
.text-inverse { color: var(--color-text-inverse); }
.text-kt-red { color: var(--color-kt-red); }
.text-ai-blue { color: var(--color-ai-blue); }
.text-success { color: var(--color-success); }
.text-warning { color: var(--color-warning); }
.text-error { color: var(--color-error); }
.text-bold { font-weight: var(--font-weight-bold); }
.text-semibold { font-weight: var(--font-weight-semibold); }
.text-medium { font-weight: var(--font-weight-medium); }
.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; }
/* =================================================================
4. Layout Utilities
================================================================= */
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--spacing-md);
}
.page {
min-height: 100vh;
background-color: var(--color-bg-secondary);
padding-bottom: 76px; /* Bottom nav height + spacing */
}
.page-with-header {
padding-top: 56px; /* Header height */
}
/* =================================================================
5. Button Components
================================================================= */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-sm);
border: none;
border-radius: var(--radius-md);
font-family: var(--font-family);
font-weight: var(--font-weight-semibold);
cursor: pointer;
transition: all var(--duration-fast) var(--ease-out);
text-decoration: none;
user-select: none;
}
.btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
/* Button Sizes */
.btn-large {
height: 48px;
padding: 0 var(--spacing-lg);
font-size: var(--font-size-body-large);
}
.btn-medium {
height: 44px;
padding: 0 var(--spacing-md);
font-size: var(--font-size-body);
}
.btn-small {
height: 36px;
padding: 0 var(--spacing-md);
font-size: var(--font-size-body-small);
}
/* Button Variants */
.btn-primary {
background: var(--gradient-primary);
color: var(--color-text-inverse);
box-shadow: var(--shadow-sm);
}
.btn-primary:hover:not(:disabled) {
box-shadow: var(--shadow-md);
transform: translateY(-1px);
}
.btn-primary:active:not(:disabled) {
transform: translateY(0);
box-shadow: var(--shadow-sm);
}
.btn-secondary {
background-color: var(--color-bg-primary);
color: var(--color-kt-red);
border: 1px solid var(--color-gray-300);
}
.btn-secondary:hover:not(:disabled) {
background-color: var(--color-gray-50);
border-color: var(--color-kt-red);
}
.btn-text {
background-color: transparent;
color: var(--color-kt-red);
}
.btn-text:hover:not(:disabled) {
background-color: rgba(227, 30, 36, 0.08);
}
.btn-full {
width: 100%;
}
/* =================================================================
6. Card Components
================================================================= */
.card {
background-color: var(--color-bg-primary);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
box-shadow: var(--shadow-sm);
transition: box-shadow var(--duration-fast) var(--ease-out);
}
.card:hover {
box-shadow: var(--shadow-md);
}
.card-clickable {
cursor: pointer;
}
.card-clickable:active {
transform: scale(0.98);
}
.event-card {
display: flex;
flex-direction: column;
gap: var(--spacing-md);
}
.event-card-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--spacing-sm);
}
.event-card-badge {
display: inline-flex;
align-items: center;
padding: 4px 12px;
border-radius: var(--radius-full);
font-size: var(--font-size-caption);
font-weight: var(--font-weight-medium);
}
.badge-active {
background-color: rgba(16, 185, 129, 0.1);
color: var(--color-success);
}
.badge-scheduled {
background-color: rgba(59, 130, 246, 0.1);
color: var(--color-info);
}
.badge-ended {
background-color: rgba(156, 163, 175, 0.1);
color: var(--color-gray-500);
}
.event-card-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--spacing-md);
padding-top: var(--spacing-md);
border-top: 1px solid var(--color-gray-200);
}
.stat-item {
text-align: center;
}
.stat-label {
font-size: var(--font-size-caption);
color: var(--color-text-tertiary);
margin-bottom: 4px;
}
.stat-value {
font-size: var(--font-size-headline);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
}
/* KPI Card */
.kpi-card {
display: flex;
align-items: center;
gap: var(--spacing-md);
padding: var(--spacing-md);
}
.kpi-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-md);
font-size: 24px;
}
.kpi-icon-primary {
background: rgba(227, 30, 36, 0.1);
color: var(--color-kt-red);
}
.kpi-icon-ai {
background: rgba(0, 102, 255, 0.1);
color: var(--color-ai-blue);
}
.kpi-icon-success {
background: rgba(16, 185, 129, 0.1);
color: var(--color-success);
}
.kpi-content {
flex: 1;
}
.kpi-label {
font-size: var(--font-size-body-small);
color: var(--color-text-secondary);
margin-bottom: 4px;
}
.kpi-value {
font-size: var(--font-size-title);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
}
/* Option Card for Selection */
.option-card {
position: relative;
border: 2px solid var(--color-gray-200);
transition: all var(--duration-fast) var(--ease-out);
}
.option-card:hover {
border-color: var(--color-kt-red);
}
.option-card.selected {
border-color: var(--color-kt-red);
background-color: rgba(227, 30, 36, 0.02);
}
.option-card-radio {
position: absolute;
top: var(--spacing-md);
right: var(--spacing-md);
}
/* =================================================================
7. Form Components
================================================================= */
.form-group {
margin-bottom: var(--spacing-lg);
}
.form-label {
display: block;
font-size: var(--font-size-body);
font-weight: var(--font-weight-medium);
color: var(--color-text-primary);
margin-bottom: var(--spacing-sm);
}
.form-label-required::after {
content: " *";
color: var(--color-error);
}
.form-input,
.form-select,
.form-textarea {
width: 100%;
padding: 12px var(--spacing-md);
border: 1px solid var(--color-gray-300);
border-radius: var(--radius-md);
font-family: var(--font-family);
font-size: var(--font-size-body);
color: var(--color-text-primary);
background-color: var(--color-bg-primary);
transition: all var(--duration-fast) var(--ease-out);
}
.form-input::placeholder,
.form-textarea::placeholder {
color: var(--color-text-tertiary);
}
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
outline: none;
border-color: var(--color-kt-red);
box-shadow: 0 0 0 3px rgba(227, 30, 36, 0.1);
}
.form-input:disabled,
.form-select:disabled,
.form-textarea:disabled {
background-color: var(--color-gray-100);
cursor: not-allowed;
}
.form-textarea {
min-height: 100px;
resize: vertical;
}
.form-error {
display: block;
margin-top: var(--spacing-sm);
font-size: var(--font-size-body-small);
color: var(--color-error);
}
.form-hint {
display: block;
margin-top: var(--spacing-sm);
font-size: var(--font-size-body-small);
color: var(--color-text-tertiary);
}
/* Checkbox & Radio */
.form-check {
display: flex;
align-items: center;
gap: var(--spacing-sm);
cursor: pointer;
}
.form-check-input {
width: 20px;
height: 20px;
cursor: pointer;
}
.form-check-label {
font-size: var(--font-size-body);
color: var(--color-text-primary);
cursor: pointer;
}
/* =================================================================
8. Navigation Components
================================================================= */
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 56px;
background-color: var(--color-bg-primary);
border-bottom: 1px solid var(--color-gray-200);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 var(--spacing-md);
z-index: var(--z-sticky);
}
.header-left,
.header-right {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.header-title {
font-size: var(--font-size-headline);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
.header-icon-btn {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border: none;
background: transparent;
color: var(--color-text-primary);
cursor: pointer;
border-radius: var(--radius-md);
transition: background-color var(--duration-fast) var(--ease-out);
}
.header-icon-btn:hover {
background-color: var(--color-gray-100);
}
.bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 60px;
background-color: var(--color-bg-primary);
border-top: 1px solid var(--color-gray-200);
display: flex;
align-items: center;
justify-content: space-around;
padding: 0 var(--spacing-sm);
z-index: var(--z-sticky);
}
.bottom-nav-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
padding: var(--spacing-sm);
color: var(--color-text-tertiary);
text-decoration: none;
font-size: var(--font-size-caption);
transition: color var(--duration-fast) var(--ease-out);
cursor: pointer;
}
.bottom-nav-item:hover {
color: var(--color-text-secondary);
}
.bottom-nav-item.active {
color: var(--color-kt-red);
}
.bottom-nav-icon {
font-size: 24px;
}
/* FAB (Floating Action Button) */
.fab {
position: fixed;
bottom: 76px; /* Bottom nav height + spacing */
right: var(--spacing-md);
width: 56px;
height: 56px;
background: var(--gradient-primary);
color: var(--color-text-inverse);
border: none;
border-radius: var(--radius-full);
box-shadow: var(--shadow-lg);
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
cursor: pointer;
transition: all var(--duration-fast) var(--ease-out);
z-index: var(--z-fixed);
}
.fab:hover {
box-shadow: var(--shadow-xl);
transform: scale(1.05);
}
.fab:active {
transform: scale(0.95);
}
/* =================================================================
9. Feedback Components
================================================================= */
/* Toast */
.toast {
position: fixed;
bottom: 92px; /* Bottom nav + spacing */
left: 50%;
transform: translateX(-50%);
padding: var(--spacing-md) var(--spacing-lg);
background-color: var(--color-gray-900);
color: var(--color-text-inverse);
border-radius: var(--radius-md);
box-shadow: var(--shadow-lg);
font-size: var(--font-size-body);
z-index: var(--z-tooltip);
animation: slideUp var(--duration-normal) var(--ease-out);
}
@keyframes slideUp {
from {
opacity: 0;
transform: translate(-50%, 20px);
}
to {
opacity: 1;
transform: translate(-50%, 0);
}
}
/* Modal */
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: var(--z-modal-backdrop);
animation: fadeIn var(--duration-normal) var(--ease-out);
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: var(--color-bg-primary);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-xl);
z-index: var(--z-modal);
max-width: 90%;
max-height: 90vh;
overflow-y: auto;
animation: scaleIn var(--duration-normal) var(--ease-out);
}
@keyframes scaleIn {
from {
opacity: 0;
transform: translate(-50%, -50%) scale(0.95);
}
to {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
}
.modal-header {
padding: var(--spacing-lg);
border-bottom: 1px solid var(--color-gray-200);
}
.modal-title {
font-size: var(--font-size-title);
font-weight: var(--font-weight-semibold);
}
.modal-body {
padding: var(--spacing-lg);
}
.modal-footer {
padding: var(--spacing-lg);
border-top: 1px solid var(--color-gray-200);
display: flex;
gap: var(--spacing-sm);
justify-content: flex-end;
}
/* Bottom Sheet */
.bottom-sheet {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: var(--color-bg-primary);
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
box-shadow: var(--shadow-xl);
z-index: var(--z-modal);
max-height: 80vh;
overflow-y: auto;
animation: slideUpSheet var(--duration-normal) var(--ease-out);
}
@keyframes slideUpSheet {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
.bottom-sheet-handle {
width: 40px;
height: 4px;
background-color: var(--color-gray-300);
border-radius: var(--radius-full);
margin: var(--spacing-sm) auto;
}
.bottom-sheet-content {
padding: var(--spacing-lg);
}
/* Spinner */
.spinner {
width: 40px;
height: 40px;
border: 4px solid var(--color-gray-200);
border-top-color: var(--color-kt-red);
border-radius: var(--radius-full);
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner-center {
display: flex;
align-items: center;
justify-content: center;
padding: var(--spacing-2xl);
}
/* Progress Bar */
.progress {
width: 100%;
height: 8px;
background-color: var(--color-gray-200);
border-radius: var(--radius-full);
overflow: hidden;
}
.progress-bar {
height: 100%;
background: var(--gradient-primary);
border-radius: var(--radius-full);
transition: width var(--duration-normal) var(--ease-out);
}
/* =================================================================
10. Utility Classes
================================================================= */
/* Spacing */
.m-0 { margin: 0; }
.mt-xs { margin-top: var(--spacing-xs); }
.mt-sm { margin-top: var(--spacing-sm); }
.mt-md { margin-top: var(--spacing-md); }
.mt-lg { margin-top: var(--spacing-lg); }
.mt-xl { margin-top: var(--spacing-xl); }
.mt-2xl { margin-top: var(--spacing-2xl); }
.mb-xs { margin-bottom: var(--spacing-xs); }
.mb-sm { margin-bottom: var(--spacing-sm); }
.mb-md { margin-bottom: var(--spacing-md); }
.mb-lg { margin-bottom: var(--spacing-lg); }
.mb-xl { margin-bottom: var(--spacing-xl); }
.mb-2xl { margin-bottom: var(--spacing-2xl); }
.p-0 { padding: 0; }
.p-xs { padding: var(--spacing-xs); }
.p-sm { padding: var(--spacing-sm); }
.p-md { padding: var(--spacing-md); }
.p-lg { padding: var(--spacing-lg); }
.p-xl { padding: var(--spacing-xl); }
.p-2xl { padding: var(--spacing-2xl); }
.gap-xs { gap: var(--spacing-xs); }
.gap-sm { gap: var(--spacing-sm); }
.gap-md { gap: var(--spacing-md); }
.gap-lg { gap: var(--spacing-lg); }
/* Flexbox */
.flex { display: flex; }
.flex-col { flex-direction: column; }
.flex-row { flex-direction: row; }
.items-center { align-items: center; }
.items-start { align-items: flex-start; }
.items-end { align-items: flex-end; }
.justify-center { justify-content: center; }
.justify-between { justify-content: space-between; }
.justify-end { justify-content: flex-end; }
.flex-1 { flex: 1; }
.flex-wrap { flex-wrap: wrap; }
/* Grid */
.grid { display: grid; }
.grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
.grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
.grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
/* Display */
.hidden { display: none; }
.block { display: block; }
.inline-block { display: inline-block; }
/* Width */
.w-full { width: 100%; }
.w-auto { width: auto; }
/* Background */
.bg-primary { background-color: var(--color-bg-primary); }
.bg-secondary { background-color: var(--color-bg-secondary); }
.bg-tertiary { background-color: var(--color-bg-tertiary); }
/* Border */
.border { border: 1px solid var(--color-gray-200); }
.border-t { border-top: 1px solid var(--color-gray-200); }
.border-b { border-bottom: 1px solid var(--color-gray-200); }
.border-none { border: none; }
.rounded-sm { border-radius: var(--radius-sm); }
.rounded-md { border-radius: var(--radius-md); }
.rounded-lg { border-radius: var(--radius-lg); }
.rounded-xl { border-radius: var(--radius-xl); }
.rounded-full { border-radius: var(--radius-full); }
/* Shadow */
.shadow-sm { box-shadow: var(--shadow-sm); }
.shadow-md { box-shadow: var(--shadow-md); }
.shadow-lg { box-shadow: var(--shadow-lg); }
.shadow-xl { box-shadow: var(--shadow-xl); }
.shadow-none { box-shadow: none; }
/* Cursor */
.cursor-pointer { cursor: pointer; }
.cursor-not-allowed { cursor: not-allowed; }
/* =================================================================
11. Responsive Grid System
================================================================= */
@media (min-width: 768px) {
.container {
padding: 0 var(--spacing-lg);
}
.tablet\:grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
.tablet\:grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
.tablet\:grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
}
@media (min-width: 1024px) {
.container {
padding: 0 var(--spacing-xl);
}
.desktop\:grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
.desktop\:grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
.desktop\:grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
.desktop\:grid-cols-5 { grid-template-columns: repeat(5, 1fr); }
}