프로토~

This commit is contained in:
doyeon 2025-10-20 14:27:45 +09:00
parent 58ab3bac44
commit 729b776600
18 changed files with 8530 additions and 0 deletions

View File

@ -0,0 +1,324 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="KT AI 기반 소상공인 이벤트 자동 생성 서비스 - 회원가입">
<title>회원가입 - KT 이벤트 마케팅</title>
<!-- Styles -->
<link rel="stylesheet" href="css/common.css">
<!-- Fonts -->
<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>
<!-- Skip Link -->
<a href="#main-content" class="skip-link">본문으로 건너뛰기</a>
<!-- App Wrapper -->
<div class="app-wrapper">
<!-- Top App Bar -->
<header class="app-bar">
<button class="app-bar__back" aria-label="뒤로가기">
<span class="material-icons">arrow_back</span>
</button>
<h1 class="app-bar__title">회원가입</h1>
</header>
<!-- Main Content -->
<main id="main-content" class="app-content">
<div class="container" style="max-width: 500px; margin: 0 auto; padding-top: 32px;">
<!-- Welcome Section -->
<div style="text-align: center; margin-bottom: 48px;">
<h2 class="display" style="margin-bottom: 8px;">KT 이벤트 마케팅</h2>
<p class="body-l text-muted">환영합니다 🎉</p>
</div>
<!-- Registration Form -->
<form id="registrationForm" novalidate>
<!-- 이름 -->
<div class="form-group">
<label for="name" class="form-label required">이름</label>
<input
type="text"
id="name"
name="name"
class="form-input"
placeholder="홍길동"
required
minlength="2"
autocomplete="name"
>
</div>
<!-- 전화번호 -->
<div class="form-group">
<label for="phone" class="form-label required">전화번호</label>
<input
type="tel"
id="phone"
name="phone"
class="form-input"
placeholder="010-1234-5678"
required
maxlength="13"
autocomplete="tel"
>
</div>
<!-- 이메일 -->
<div class="form-group">
<label for="email" class="form-label required">이메일</label>
<input
type="email"
id="email"
name="email"
class="form-input"
placeholder="example@domain.com"
required
autocomplete="email"
>
</div>
<!-- KT 본인 인증 (선택) -->
<div class="card" style="margin-bottom: 24px; padding: 16px;">
<div class="form-check">
<input
type="checkbox"
id="ktAuth"
name="ktAuth"
>
<label for="ktAuth" style="flex: 1; cursor: pointer;">
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">
KT 본인 인증 (선택)
</div>
<div class="body-s text-muted">
인증 시 추가 혜택이 제공됩니다
</div>
</label>
</div>
</div>
<!-- 개인정보 수집 동의 (필수) -->
<div class="form-group">
<div class="form-check">
<input
type="checkbox"
id="privacyAgree"
name="privacyAgree"
required
>
<label for="privacyAgree" style="flex: 1; cursor: pointer;">
<span class="body-m">개인정보 수집 및 이용 동의 (필수)</span>
<button type="button" class="btn-text btn-sm" style="padding: 0; margin-left: 8px;" onclick="showPrivacyModal()">
자세히보기
</button>
</label>
</div>
</div>
<!-- Submit Button -->
<button
type="submit"
id="submitBtn"
class="btn btn-primary btn-lg btn-block"
style="margin-top: 32px;"
disabled
>
다음 단계
</button>
</form>
<!-- Additional Info -->
<div style="text-align: center; margin-top: 24px;">
<p class="body-s text-muted">
이미 계정이 있으신가요?
<a href="#" class="text-primary" style="text-decoration: none; font-weight: 600;">로그인</a>
</p>
</div>
</div>
</main>
</div>
<!-- Scripts -->
<script src="js/common.js"></script>
<script>
(function() {
'use strict';
const form = document.getElementById('registrationForm');
const submitBtn = document.getElementById('submitBtn');
const nameInput = document.getElementById('name');
const phoneInput = document.getElementById('phone');
const emailInput = document.getElementById('email');
const ktAuthCheckbox = document.getElementById('ktAuth');
const privacyCheckbox = document.getElementById('privacyAgree');
// 폼 검증 및 버튼 활성화 체크
function checkFormValidity() {
const isNameValid = nameInput.value.trim().length >= 2;
const isPhoneValid = FormValidator.isValidPhone(phoneInput.value);
const isEmailValid = FormValidator.isValidEmail(emailInput.value);
const isPrivacyAgreed = privacyCheckbox.checked;
const isFormValid = isNameValid && isPhoneValid && isEmailValid && isPrivacyAgreed;
submitBtn.disabled = !isFormValid;
}
// 입력 필드 이벤트 리스너
[nameInput, phoneInput, emailInput].forEach(input => {
input.addEventListener('input', checkFormValidity);
input.addEventListener('blur', function() {
if (this.value) {
validateField(this);
}
});
});
privacyCheckbox.addEventListener('change', checkFormValidity);
// 개별 필드 검증
function validateField(input) {
let isValid = true;
let errorMessage = '';
switch(input.id) {
case 'name':
if (input.value.trim().length < 2) {
isValid = false;
errorMessage = '이름은 2자 이상 입력해주세요.';
}
break;
case 'phone':
if (!FormValidator.isValidPhone(input.value)) {
isValid = false;
errorMessage = '올바른 전화번호 형식을 입력하세요. (010-XXXX-XXXX)';
}
break;
case 'email':
if (!FormValidator.isValidEmail(input.value)) {
isValid = false;
errorMessage = '올바른 이메일 형식을 입력하세요.';
}
break;
}
if (isValid) {
FormValidator.clearError(input);
} else {
FormValidator.showError(input, errorMessage);
}
return isValid;
}
// KT 본인 인증 체크박스 변경 시
ktAuthCheckbox.addEventListener('change', function() {
if (this.checked) {
// KT 인증 시뮬레이션
simulateKTAuth();
}
});
// KT 인증 시뮬레이션
function simulateKTAuth() {
// 실제로는 KT 인증 시스템 연동
Loading.show('KT 본인 인증 중...');
setTimeout(() => {
Loading.hide();
Toast.success('KT 본인 인증이 완료되었습니다. 추가 혜택이 적용됩니다!');
}, 2000);
}
// 폼 제출
form.addEventListener('submit', function(e) {
e.preventDefault();
// 모든 필드 검증
const isNameValid = validateField(nameInput);
const isPhoneValid = validateField(phoneInput);
const isEmailValid = validateField(emailInput);
if (!isNameValid || !isPhoneValid || !isEmailValid) {
Toast.error('입력 내용을 확인해주세요.');
return;
}
if (!privacyCheckbox.checked) {
Toast.error('개인정보 수집 및 이용에 동의해주세요.');
return;
}
// 중복 가입 체크 시뮬레이션
Loading.show('가입 정보 확인 중...');
setTimeout(() => {
Loading.hide();
// 사용자 정보 저장
const userData = {
id: Utils.generateId(),
name: nameInput.value.trim(),
phone: phoneInput.value,
email: emailInput.value,
ktAuth: ktAuthCheckbox.checked,
createdAt: new Date().toISOString()
};
window.AppState.user = userData;
window.AppState.save();
// 성공 메시지
Toast.success('회원가입이 완료되었습니다!');
// 다음 화면으로 이동 (매장정보등록)
setTimeout(() => {
window.location.href = '02-매장정보등록.html';
}, 1000);
}, 1500);
});
// 개인정보 처리방침 모달
window.showPrivacyModal = function() {
Modal.alert(
'개인정보 수집 및 이용 동의',
`
<div class="body-m" style="max-height: 300px; overflow-y: auto;">
<h3 class="h3" style="margin-bottom: 12px;">수집하는 개인정보 항목</h3>
<ul style="margin-left: 20px; margin-bottom: 16px;">
<li>이름</li>
<li>전화번호</li>
<li>이메일 주소</li>
</ul>
<h3 class="h3" style="margin-bottom: 12px;">개인정보의 수집 및 이용 목적</h3>
<ul style="margin-left: 20px; margin-bottom: 16px;">
<li>회원 가입 및 관리</li>
<li>서비스 제공 및 계약 이행</li>
<li>고객 문의 응대</li>
</ul>
<h3 class="h3" style="margin-bottom: 12px;">개인정보의 보유 및 이용 기간</h3>
<p style="margin-bottom: 16px;">
회원 탈퇴 시까지 보유하며, 관계 법령에 따라 일정 기간 보관할 수 있습니다.
</p>
<p class="body-s text-muted">
귀하는 개인정보 수집 및 이용에 동의하지 않을 권리가 있으나, 동의하지 않을 경우 서비스 이용이 제한될 수 있습니다.
</p>
</div>
`
);
};
console.log('회원가입 페이지 로드 완료');
})();
</script>
</body>
</html>

View File

@ -0,0 +1,755 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="KT AI 기반 소상공인 이벤트 자동 생성 서비스 - 매장정보등록">
<title>매장정보등록 - KT 이벤트 마케팅</title>
<!-- Styles -->
<link rel="stylesheet" href="css/common.css">
<!-- Fonts -->
<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>
<!-- Skip Link -->
<a href="#main-content" class="skip-link">본문으로 건너뛰기</a>
<!-- App Wrapper -->
<div class="app-wrapper">
<!-- Top App Bar -->
<header class="app-bar">
<button class="app-bar__back" aria-label="뒤로가기">
<span class="material-icons">arrow_back</span>
</button>
<h1 class="app-bar__title">매장정보등록</h1>
</header>
<!-- Main Content -->
<main id="main-content" class="app-content">
<div class="container" style="max-width: 600px; margin: 0 auto; padding-top: 24px;">
<!-- Progress Indicator -->
<div style="margin-bottom: 32px;">
<div class="body-s text-muted" style="margin-bottom: 8px;">진행상황</div>
<div style="display: flex; gap: 4px; align-items: center;">
<div style="flex: 1; height: 4px; background: var(--color-primary-main); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-primary-main); border-radius: 2px;"></div>
<span class="body-s" style="margin-left: 8px; font-weight: 600;">2/2</span>
</div>
</div>
<!-- Registration Form -->
<form id="storeForm" novalidate>
<!-- Required Information -->
<section style="margin-bottom: 32px;">
<h2 class="h3" style="margin-bottom: 20px;">필수 정보</h2>
<!-- Store Name -->
<div class="form-group">
<label for="storeName" class="form-label required">매장명</label>
<input
type="text"
id="storeName"
name="storeName"
class="form-input"
placeholder="왕갈비통닭 수원점"
required
minlength="2"
autocomplete="organization"
>
</div>
<!-- Business Type -->
<div class="form-group">
<label for="businessType" class="form-label required">업종</label>
<select
id="businessType"
name="businessType"
class="form-input"
required
>
<option value="">업종을 선택하세요</option>
<option value="restaurant">음식점</option>
<option value="cafe">카페/디저트</option>
<option value="retail">소매업</option>
<option value="beauty">미용/뷰티</option>
<option value="education">교육/학원</option>
<option value="service">서비스업</option>
<option value="healthcare">의료/건강</option>
<option value="other">기타</option>
</select>
</div>
<!-- Address Search -->
<div class="form-group">
<label for="address" class="form-label required">주소</label>
<div style="display: flex; gap: 8px;">
<input
type="text"
id="address"
name="address"
class="form-input"
placeholder="주소를 검색하세요"
required
readonly
style="flex: 1; cursor: pointer;"
>
<button
type="button"
class="btn btn-secondary"
onclick="searchAddress()"
aria-label="주소 검색"
>
<span class="material-icons" style="font-size: 20px;">search</span>
</button>
</div>
<input
type="text"
id="addressDetail"
name="addressDetail"
class="form-input"
placeholder="상세주소 (선택)"
style="margin-top: 8px;"
autocomplete="address-line2"
>
</div>
<!-- Business Hours -->
<div class="form-group">
<label class="form-label required">영업시간</label>
<div id="businessHours">
<!-- Business hours will be dynamically added here -->
</div>
<div class="form-check" style="margin-top: 12px;">
<input
type="checkbox"
id="sameHours"
name="sameHours"
checked
>
<label for="sameHours" style="flex: 1; cursor: pointer;">
<span class="body-m">모든 요일 동일하게 적용</span>
</label>
</div>
</div>
<!-- Business Number -->
<div class="form-group">
<label for="businessNumber" class="form-label required">사업자번호</label>
<div style="display: flex; gap: 8px; align-items: flex-start;">
<input
type="text"
id="businessNumber"
name="businessNumber"
class="form-input"
placeholder="123-45-67890"
required
maxlength="12"
style="flex: 1;"
autocomplete="off"
>
<button
type="button"
id="verifyBtn"
class="btn btn-secondary"
onclick="verifyBusinessNumber()"
disabled
>
검증하기
</button>
</div>
<div id="verifyResult" style="margin-top: 8px;"></div>
</div>
</section>
<!-- Optional Information -->
<section style="margin-bottom: 32px;">
<h2 class="h3" style="margin-bottom: 8px;">선택 정보</h2>
<p class="body-s text-muted" style="margin-bottom: 20px;">
이벤트 맞춤화를 위해 추가 정보를 입력해주세요
</p>
<!-- Menu Items -->
<div class="form-group">
<label class="form-label">메뉴/상품 (최대 10개)</label>
<div id="menuList" style="margin-bottom: 12px;">
<!-- Menu items will be dynamically added here -->
</div>
<button
type="button"
class="btn btn-text btn-sm"
onclick="addMenuItem()"
id="addMenuBtn"
>
<span class="material-icons" style="font-size: 18px; margin-right: 4px;">add</span>
메뉴 추가
</button>
</div>
<!-- Store Features -->
<div class="form-group">
<label for="storeFeatures" class="form-label">매장 특징</label>
<textarea
id="storeFeatures"
name="storeFeatures"
class="form-input"
placeholder="매장의 특별한 점이나 강점을 알려주세요&#10;예) 30년 전통, 직접 만든 소스, 주차 가능"
rows="4"
maxlength="200"
style="resize: vertical;"
></textarea>
<div class="body-s text-muted" style="margin-top: 4px; text-align: right;">
<span id="featureCount">0</span>/200
</div>
</div>
<!-- Store Images -->
<div class="form-group">
<label class="form-label">대표 이미지 (최대 3장)</label>
<div id="imagePreview" style="display: flex; gap: 12px; flex-wrap: wrap;">
<label class="image-upload-box" for="imageUpload1">
<input
type="file"
id="imageUpload1"
name="storeImages"
accept="image/*"
style="display: none;"
onchange="handleImageUpload(event, 1)"
>
<span class="material-icons" style="font-size: 32px; color: var(--color-gray-400);">add_photo_alternate</span>
</label>
<label class="image-upload-box" for="imageUpload2" style="display: none;">
<input
type="file"
id="imageUpload2"
name="storeImages"
accept="image/*"
style="display: none;"
onchange="handleImageUpload(event, 2)"
>
<span class="material-icons" style="font-size: 32px; color: var(--color-gray-400);">add_photo_alternate</span>
</label>
<label class="image-upload-box" for="imageUpload3" style="display: none;">
<input
type="file"
id="imageUpload3"
name="storeImages"
accept="image/*"
style="display: none;"
onchange="handleImageUpload(event, 3)"
>
<span class="material-icons" style="font-size: 32px; color: var(--color-gray-400);">add_photo_alternate</span>
</label>
</div>
</div>
</section>
<!-- Submit Button -->
<button
type="submit"
id="submitBtn"
class="btn btn-primary btn-lg btn-block"
style="margin-bottom: 32px;"
disabled
>
등록 완료하기
</button>
</form>
</div>
</main>
</div>
<!-- Scripts -->
<script src="js/common.js"></script>
<script>
(function() {
'use strict';
const form = document.getElementById('storeForm');
const submitBtn = document.getElementById('submitBtn');
const verifyBtn = document.getElementById('verifyBtn');
const businessNumberInput = document.getElementById('businessNumber');
const storeNameInput = document.getElementById('storeName');
const businessTypeSelect = document.getElementById('businessType');
const addressInput = document.getElementById('address');
const featureCountSpan = document.getElementById('featureCount');
const storeFeaturesTextarea = document.getElementById('storeFeatures');
let menuItems = [];
let uploadedImages = [];
let isBusinessNumberVerified = false;
// 영업시간 초기화
function initBusinessHours() {
const days = ['월', '화', '수', '목', '금', '토', '일'];
const hoursContainer = document.getElementById('businessHours');
days.forEach((day, index) => {
const hourRow = document.createElement('div');
hourRow.className = 'business-hour-row';
hourRow.style.cssText = 'display: flex; gap: 8px; align-items: center; margin-bottom: 8px;';
hourRow.innerHTML = `
<span class="body-m" style="min-width: 24px;">${day}:</span>
<input
type="time"
name="openTime_${index}"
value="09:00"
class="form-input"
style="flex: 1; min-width: 0;"
required
>
<span class="body-m">~</span>
<input
type="time"
name="closeTime_${index}"
value="21:00"
class="form-input"
style="flex: 1; min-width: 0;"
required
>
`;
hoursContainer.appendChild(hourRow);
});
}
// 영업시간 동일 적용
document.getElementById('sameHours').addEventListener('change', function() {
const rows = document.querySelectorAll('.business-hour-row');
if (this.checked && rows.length > 0) {
const firstOpen = rows[0].querySelector('input[type="time"]:first-of-type').value;
const firstClose = rows[0].querySelector('input[type="time"]:last-of-type').value;
rows.forEach((row, index) => {
if (index > 0) {
row.querySelector('input[type="time"]:first-of-type').value = firstOpen;
row.querySelector('input[type="time"]:last-of-type').value = firstClose;
}
});
}
});
// 주소 검색 (시뮬레이션)
window.searchAddress = function() {
Modal.show({
title: '주소 검색',
body: `
<div class="form-group" style="margin-bottom: 16px;">
<input
type="text"
id="addressSearchInput"
class="form-input"
placeholder="도로명 또는 지번 주소 입력"
onkeyup="if(event.key==='Enter') searchAddressQuery()"
>
</div>
<div id="addressResults" class="body-s" style="color: var(--color-gray-600);">
주소를 입력하고 Enter를 눌러주세요
</div>
`,
confirmText: '닫기',
showCancel: false
});
setTimeout(() => {
document.getElementById('addressSearchInput').focus();
}, 100);
};
window.searchAddressQuery = function() {
const query = document.getElementById('addressSearchInput').value;
if (!query) return;
const results = document.getElementById('addressResults');
results.innerHTML = '<div class="body-s text-muted">검색 중...</div>';
// 시뮬레이션: 실제로는 카카오 주소 API 사용
setTimeout(() => {
results.innerHTML = `
<div class="card" style="padding: 12px; margin-bottom: 8px; cursor: pointer;" onclick="selectAddress('경기도 수원시 팔달구 인계동 1055-1')">
<div class="body-m" style="margin-bottom: 4px;">경기도 수원시 팔달구 인계동 1055-1</div>
<div class="body-s text-muted">(우) 16495</div>
</div>
<div class="card" style="padding: 12px; margin-bottom: 8px; cursor: pointer;" onclick="selectAddress('경기도 수원시 팔달구 권광로 181')">
<div class="body-m" style="margin-bottom: 4px;">경기도 수원시 팔달구 권광로 181</div>
<div class="body-s text-muted">(우) 16495</div>
</div>
`;
}, 500);
};
window.selectAddress = function(address) {
addressInput.value = address;
document.querySelector('.modal__close').click();
checkFormValidity();
};
// 사업자번호 검증
businessNumberInput.addEventListener('input', function() {
isBusinessNumberVerified = false;
verifyBtn.disabled = !FormValidator.isValidBusinessNumber(this.value);
document.getElementById('verifyResult').innerHTML = '';
checkFormValidity();
});
window.verifyBusinessNumber = function() {
const businessNumber = businessNumberInput.value;
if (!FormValidator.isValidBusinessNumber(businessNumber)) {
Toast.error('올바른 사업자번호 형식을 입력하세요.');
return;
}
Loading.show('사업자번호 검증 중...');
verifyBtn.disabled = true;
// 시뮬레이션: 실제로는 국세청 API 연동
setTimeout(() => {
Loading.hide();
isBusinessNumberVerified = true;
document.getElementById('verifyResult').innerHTML = `
<div style="display: flex; align-items: center; gap: 8px; color: var(--color-success);">
<span class="material-icons" style="font-size: 20px;">check_circle</span>
<span class="body-s" style="font-weight: 600;">확인됨</span>
</div>
`;
Toast.success('사업자번호가 확인되었습니다.');
checkFormValidity();
}, 1500);
};
// 메뉴 추가
window.addMenuItem = function() {
if (menuItems.length >= 10) {
Toast.error('메뉴는 최대 10개까지 추가할 수 있습니다.');
return;
}
Modal.show({
title: '메뉴 추가',
body: `
<div class="form-group">
<label class="form-label required">메뉴명</label>
<input
type="text"
id="menuName"
class="form-input"
placeholder="예) 양념치킨"
required
>
</div>
<div class="form-group">
<label class="form-label required">가격</label>
<input
type="number"
id="menuPrice"
class="form-input"
placeholder="20000"
required
min="0"
>
</div>
<div class="form-group">
<label class="form-label">설명</label>
<textarea
id="menuDescription"
class="form-input"
placeholder="메뉴 설명 (선택)"
rows="2"
></textarea>
</div>
`,
confirmText: '추가',
onConfirm: function() {
const name = document.getElementById('menuName').value.trim();
const price = document.getElementById('menuPrice').value;
const description = document.getElementById('menuDescription').value.trim();
if (!name || !price) {
Toast.error('메뉴명과 가격은 필수입니다.');
return;
}
menuItems.push({
id: Utils.generateId(),
name,
price: parseInt(price),
description
});
renderMenuList();
Toast.success('메뉴가 추가되었습니다.');
}
});
setTimeout(() => {
document.getElementById('menuName').focus();
}, 100);
};
// 메뉴 목록 렌더링
function renderMenuList() {
const menuList = document.getElementById('menuList');
if (menuItems.length === 0) {
menuList.innerHTML = '';
document.getElementById('addMenuBtn').style.display = 'flex';
return;
}
menuList.innerHTML = menuItems.map((item, index) => `
<div class="card" style="padding: 12px; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center;">
<div style="flex: 1;">
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">${item.name}</div>
<div class="body-s text-muted">
${Utils.formatNumber(item.price)}원
${item.description ? ' · ' + item.description : ''}
</div>
</div>
<button
type="button"
class="btn btn-text btn-sm"
onclick="removeMenuItem('${item.id}')"
style="color: var(--color-error);"
>
<span class="material-icons" style="font-size: 18px;">delete</span>
</button>
</div>
`).join('');
document.getElementById('addMenuBtn').style.display = menuItems.length >= 10 ? 'none' : 'flex';
}
window.removeMenuItem = function(id) {
menuItems = menuItems.filter(item => item.id !== id);
renderMenuList();
Toast.info('메뉴가 삭제되었습니다.');
};
// 이미지 업로드
window.handleImageUpload = function(event, index) {
const file = event.target.files[0];
if (!file) return;
// 파일 크기 검증 (5MB)
if (file.size > 5 * 1024 * 1024) {
Toast.error('이미지 크기는 5MB 이하여야 합니다.');
event.target.value = '';
return;
}
Loading.show('이미지 업로드 중...');
// 이미지 미리보기
const reader = new FileReader();
reader.onload = function(e) {
setTimeout(() => {
Loading.hide();
const imageUrl = e.target.result;
uploadedImages.push({
id: Utils.generateId(),
url: imageUrl,
file: file
});
// 현재 업로드 박스를 이미지로 교체
const currentBox = document.querySelector(`label[for="imageUpload${index}"]`);
currentBox.style.backgroundImage = `url(${imageUrl})`;
currentBox.style.backgroundSize = 'cover';
currentBox.style.backgroundPosition = 'center';
currentBox.innerHTML = `
<button
type="button"
class="image-remove-btn"
onclick="removeImage(${index})"
style="position: absolute; top: 4px; right: 4px; background: rgba(0,0,0,0.6); color: white; border: none; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; cursor: pointer;"
>
<span class="material-icons" style="font-size: 16px;">close</span>
</button>
`;
// 다음 업로드 박스 표시
if (index < 3) {
const nextBox = document.querySelector(`label[for="imageUpload${index + 1}"]`);
if (nextBox) {
nextBox.style.display = 'flex';
}
}
Toast.success('이미지가 업로드되었습니다.');
}, 500);
};
reader.readAsDataURL(file);
};
window.removeImage = function(index) {
uploadedImages = uploadedImages.filter((_, i) => i !== index - 1);
// 이미지 박스 초기화
const imagePreview = document.getElementById('imagePreview');
imagePreview.innerHTML = `
<label class="image-upload-box" for="imageUpload1">
<input type="file" id="imageUpload1" name="storeImages" accept="image/*" style="display: none;" onchange="handleImageUpload(event, 1)">
<span class="material-icons" style="font-size: 32px; color: var(--color-gray-400);">add_photo_alternate</span>
</label>
<label class="image-upload-box" for="imageUpload2" style="display: none;">
<input type="file" id="imageUpload2" name="storeImages" accept="image/*" style="display: none;" onchange="handleImageUpload(event, 2)">
<span class="material-icons" style="font-size: 32px; color: var(--color-gray-400);">add_photo_alternate</span>
</label>
<label class="image-upload-box" for="imageUpload3" style="display: none;">
<input type="file" id="imageUpload3" name="storeImages" accept="image/*" style="display: none;" onchange="handleImageUpload(event, 3)">
<span class="material-icons" style="font-size: 32px; color: var(--color-gray-400);">add_photo_alternate</span>
</label>
`;
// 업로드된 이미지 다시 렌더링
uploadedImages.forEach((img, i) => {
const box = document.querySelector(`label[for="imageUpload${i + 1}"]`);
box.style.backgroundImage = `url(${img.url})`;
box.style.backgroundSize = 'cover';
box.style.backgroundPosition = 'center';
box.innerHTML = `
<button type="button" class="image-remove-btn" onclick="removeImage(${i + 1})" style="position: absolute; top: 4px; right: 4px; background: rgba(0,0,0,0.6); color: white; border: none; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; cursor: pointer;">
<span class="material-icons" style="font-size: 16px;">close</span>
</button>
`;
if (i < 2) {
const nextBox = document.querySelector(`label[for="imageUpload${i + 2}"]`);
if (nextBox) nextBox.style.display = 'flex';
}
});
Toast.info('이미지가 삭제되었습니다.');
};
// 매장 특징 글자수 카운트
storeFeaturesTextarea.addEventListener('input', function() {
featureCountSpan.textContent = this.value.length;
});
// 폼 검증 및 버튼 활성화
function checkFormValidity() {
const isStoreNameValid = storeNameInput.value.trim().length >= 2;
const isBusinessTypeValid = businessTypeSelect.value !== '';
const isAddressValid = addressInput.value.trim() !== '';
const isBusinessNumberValid = isBusinessNumberVerified;
const isFormValid = isStoreNameValid && isBusinessTypeValid && isAddressValid && isBusinessNumberValid;
submitBtn.disabled = !isFormValid;
}
// 입력 필드 이벤트 리스너
[storeNameInput, businessTypeSelect, addressInput].forEach(input => {
input.addEventListener('input', checkFormValidity);
input.addEventListener('change', checkFormValidity);
});
// 폼 제출
form.addEventListener('submit', function(e) {
e.preventDefault();
if (!isBusinessNumberVerified) {
Toast.error('사업자번호를 먼저 검증해주세요.');
return;
}
Loading.show('매장 정보 저장 중...');
setTimeout(() => {
Loading.hide();
// 영업시간 수집
const businessHours = [];
const days = ['월', '화', '수', '목', '금', '토', '일'];
days.forEach((day, index) => {
const openTime = document.querySelector(`input[name="openTime_${index}"]`).value;
const closeTime = document.querySelector(`input[name="closeTime_${index}"]`).value;
businessHours.push({ day, openTime, closeTime });
});
// 매장 정보 저장
const storeData = {
id: Utils.generateId(),
name: storeNameInput.value.trim(),
businessType: businessTypeSelect.value,
businessTypeName: businessTypeSelect.options[businessTypeSelect.selectedIndex].text,
address: addressInput.value.trim(),
addressDetail: document.getElementById('addressDetail').value.trim(),
businessNumber: businessNumberInput.value,
businessHours: businessHours,
menuItems: menuItems,
features: storeFeaturesTextarea.value.trim(),
images: uploadedImages,
createdAt: new Date().toISOString()
};
window.AppState.store = storeData;
window.AppState.save();
// 성공 메시지
Modal.show({
title: '🎉 매장 등록 완료!',
body: `
<div style="text-align: center;">
<p class="body-l" style="margin-bottom: 16px;">
<strong>${storeData.name}</strong> 매장이<br>
성공적으로 등록되었습니다.
</p>
<div class="card" style="padding: 16px; background: var(--color-primary-light); border: 1px solid var(--color-primary-main);">
<div class="body-m" style="font-weight: 600; margin-bottom: 8px;">
🎁 무료 체험 쿠폰 발급 완료
</div>
<div class="body-s text-muted">
첫 이벤트 생성 시 사용 가능
</div>
</div>
</div>
`,
confirmText: '대시보드로 이동',
showCancel: false,
onConfirm: function() {
// 실시간 대시보드로 이동 (아직 미구현이므로 임시 페이지로)
window.location.href = '21-실시간대시보드.html';
}
});
}, 1500);
});
// 초기화
initBusinessHours();
console.log('매장정보등록 페이지 로드 완료');
})();
</script>
<style>
.image-upload-box {
width: 100px;
height: 100px;
border: 2px dashed var(--color-gray-300);
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
background-color: var(--color-gray-50);
}
.image-upload-box:hover {
border-color: var(--color-primary-main);
background-color: var(--color-primary-light);
}
.image-upload-box:active {
transform: scale(0.98);
}
</style>
</body>
</html>

View File

@ -0,0 +1,494 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="KT AI 기반 소상공인 이벤트 자동 생성 서비스 - 이벤트목적선택">
<title>이벤트 목적 선택 - KT 이벤트 마케팅</title>
<!-- Styles -->
<link rel="stylesheet" href="css/common.css">
<!-- Fonts -->
<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>
<!-- Skip Link -->
<a href="#main-content" class="skip-link">본문으로 건너뛰기</a>
<!-- App Wrapper -->
<div class="app-wrapper">
<!-- Top App Bar -->
<header class="app-bar">
<button class="app-bar__back" aria-label="닫기" onclick="confirmClose()">
<span class="material-icons">close</span>
</button>
<h1 class="app-bar__title">새 이벤트 기획</h1>
</header>
<!-- Main Content -->
<main id="main-content" class="app-content">
<div class="container" style="max-width: 500px; margin: 0 auto; padding-top: 24px;">
<!-- Progress Section -->
<div style="margin-bottom: 32px;">
<div class="body-m" style="margin-bottom: 8px; font-weight: 600;">단계 1/6: 목적 선택</div>
<div style="display: flex; gap: 4px;">
<div style="flex: 1; height: 4px; background: var(--color-primary-main); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-gray-200); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-gray-200); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-gray-200); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-gray-200); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-gray-200); border-radius: 2px;"></div>
</div>
</div>
<!-- Instructions -->
<div style="text-align: center; margin-bottom: 32px;">
<h2 class="h2" style="margin-bottom: 8px;">이벤트 목적을 선택하세요</h2>
<p class="body-m text-muted">선택한 목적에 맞춰 AI가 최적의 이벤트를 기획합니다</p>
</div>
<!-- Purpose Options -->
<div id="purposeOptions" style="margin-bottom: 32px;">
<!-- New Customer Acquisition -->
<label class="purpose-card" for="purpose1" data-purpose="new_customer">
<input
type="radio"
name="purpose"
id="purpose1"
value="new_customer"
class="purpose-radio"
>
<div class="purpose-card__content">
<div class="purpose-card__icon">👤</div>
<div class="purpose-card__title">신규고객 유치</div>
<div class="purpose-card__description">새로운 고객 확보</div>
<div class="purpose-card__effect">예상효과: +30%</div>
<div class="purpose-card__check">
<span class="material-icons">check_circle</span>
</div>
</div>
</label>
<!-- Repeat Visit -->
<label class="purpose-card" for="purpose2" data-purpose="repeat_visit">
<input
type="radio"
name="purpose"
id="purpose2"
value="repeat_visit"
class="purpose-radio"
>
<div class="purpose-card__content">
<div class="purpose-card__icon">🔄</div>
<div class="purpose-card__title">재방문 유도</div>
<div class="purpose-card__description">기존 고객 재방문</div>
<div class="purpose-card__effect">예상효과: +25%</div>
<div class="purpose-card__check">
<span class="material-icons">check_circle</span>
</div>
</div>
</label>
<!-- Sales Increase -->
<label class="purpose-card" for="purpose3" data-purpose="sales_increase">
<input
type="radio"
name="purpose"
id="purpose3"
value="sales_increase"
class="purpose-radio"
>
<div class="purpose-card__content">
<div class="purpose-card__icon">💰</div>
<div class="purpose-card__title">매출 증대</div>
<div class="purpose-card__description">단기 매출 향상</div>
<div class="purpose-card__effect">예상효과: +40%</div>
<div class="purpose-card__check">
<span class="material-icons">check_circle</span>
</div>
</div>
</label>
<!-- Brand Awareness -->
<label class="purpose-card" for="purpose4" data-purpose="brand_awareness">
<input
type="radio"
name="purpose"
id="purpose4"
value="brand_awareness"
class="purpose-radio"
>
<div class="purpose-card__content">
<div class="purpose-card__icon">📢</div>
<div class="purpose-card__title">인지도 향상</div>
<div class="purpose-card__description">브랜드 인지도 제고</div>
<div class="purpose-card__effect">예상효과: +50%</div>
<div class="purpose-card__check">
<span class="material-icons">check_circle</span>
</div>
</div>
</label>
</div>
<!-- Next Button -->
<button
id="nextBtn"
class="btn btn-primary btn-lg btn-block"
style="margin-bottom: 32px;"
disabled
>
다음 (AI분석)
</button>
</div>
</main>
</div>
<!-- Scripts -->
<script src="js/common.js"></script>
<script>
(function() {
'use strict';
const nextBtn = document.getElementById('nextBtn');
const purposeCards = document.querySelectorAll('.purpose-card');
const purposeRadios = document.querySelectorAll('.purpose-radio');
let selectedPurpose = null;
// Purpose 상세 설명 데이터
const purposeDetails = {
new_customer: {
title: '신규고객 유치',
icon: '👤',
description: '새로운 고객을 끌어들이고 고객 기반을 확장합니다.',
benefits: [
'새로운 잠재고객 발굴',
'고객 기반 확대',
'시장 점유율 증가'
],
strategies: [
'할인 쿠폰 제공',
'SNS 바이럴 마케팅',
'신규 고객 전용 특전'
],
expectedEffect: '신규 고객 +30%',
successRate: '85%'
},
repeat_visit: {
title: '재방문 유도',
icon: '🔄',
description: '기존 고객의 재방문을 유도하여 충성도를 높입니다.',
benefits: [
'고객 충성도 향상',
'안정적 매출 확보',
'단골 고객 증가'
],
strategies: [
'재방문 쿠폰 발급',
'포인트 적립 이벤트',
'멤버십 혜택 강화'
],
expectedEffect: '재방문율 +25%',
successRate: '90%'
},
sales_increase: {
title: '매출 증대',
icon: '💰',
description: '단기간에 매출을 극대화하는 이벤트를 진행합니다.',
benefits: [
'즉각적인 매출 증가',
'재고 소진 가속화',
'현금 흐름 개선'
],
strategies: [
'시즌 특가 행사',
'묶음 할인 판매',
'한정 수량 프로모션'
],
expectedEffect: '매출 +40%',
successRate: '80%'
},
brand_awareness: {
title: '인지도 향상',
icon: '📢',
description: '브랜드 인지도를 높여 장기적인 성장 기반을 마련합니다.',
benefits: [
'브랜드 인지도 상승',
'입소문 효과',
'미디어 노출 증가'
],
strategies: [
'SNS 해시태그 챌린지',
'이벤트 사진 공유',
'인플루언서 협업'
],
expectedEffect: '인지도 +50%',
successRate: '75%'
}
};
// 카드 선택 이벤트
purposeCards.forEach(card => {
// 카드 클릭
card.addEventListener('click', function() {
const radio = this.querySelector('.purpose-radio');
if (radio) {
radio.checked = true;
handlePurposeSelection(radio.value);
}
});
// 롱프레스/호버 시 상세 툴팁
let pressTimer;
card.addEventListener('mouseenter', function() {
const purpose = this.dataset.purpose;
if (purpose) {
pressTimer = setTimeout(() => {
showTooltip(purpose, this);
}, 500);
}
});
card.addEventListener('mouseleave', function() {
clearTimeout(pressTimer);
});
// 터치 이벤트 (모바일)
card.addEventListener('touchstart', function(e) {
const purpose = this.dataset.purpose;
if (purpose) {
pressTimer = setTimeout(() => {
showTooltip(purpose, this);
}, 800);
}
});
card.addEventListener('touchend', function() {
clearTimeout(pressTimer);
});
});
// 라디오 버튼 변경 이벤트
purposeRadios.forEach(radio => {
radio.addEventListener('change', function() {
if (this.checked) {
handlePurposeSelection(this.value);
}
});
});
// 목적 선택 처리
function handlePurposeSelection(value) {
selectedPurpose = value;
// 모든 카드에서 selected 클래스 제거
purposeCards.forEach(card => {
card.classList.remove('selected');
});
// 선택된 카드에 selected 클래스 추가
const selectedCard = document.querySelector(`[data-purpose="${value}"]`);
if (selectedCard) {
selectedCard.classList.add('selected');
}
// 다음 버튼 활성화
nextBtn.disabled = false;
// 선택 완료 피드백
Toast.success(`${purposeDetails[value].title}이(가) 선택되었습니다.`);
// 다음 단계 프리페치 시뮬레이션
console.log('다음 단계 데이터 프리페치 중...', value);
}
// 상세 툴팁 표시
function showTooltip(purpose, element) {
const details = purposeDetails[purpose];
if (!details) return;
const rect = element.getBoundingClientRect();
BottomSheet.show(`
<div style="padding: 8px 0;">
<div style="text-align: center; margin-bottom: 20px;">
<div style="font-size: 48px; margin-bottom: 8px;">${details.icon}</div>
<h3 class="h3">${details.title}</h3>
<p class="body-m text-muted" style="margin-top: 8px;">${details.description}</p>
</div>
<div style="margin-bottom: 20px;">
<h4 class="body-m" style="font-weight: 600; margin-bottom: 12px;">주요 혜택</h4>
<ul style="margin-left: 20px;">
${details.benefits.map(b => `<li class="body-m" style="margin-bottom: 8px;">${b}</li>`).join('')}
</ul>
</div>
<div style="margin-bottom: 20px;">
<h4 class="body-m" style="font-weight: 600; margin-bottom: 12px;">추천 전략</h4>
<ul style="margin-left: 20px;">
${details.strategies.map(s => `<li class="body-m" style="margin-bottom: 8px;">${s}</li>`).join('')}
</ul>
</div>
<div class="card" style="padding: 16px; background: var(--color-secondary-light);">
<div class="body-m" style="font-weight: 600; margin-bottom: 8px;">
${details.expectedEffect}
</div>
<div class="body-s text-muted">
성공률: ${details.successRate}
</div>
</div>
</div>
`);
}
// 다음 버튼 클릭
nextBtn.addEventListener('click', function() {
if (!selectedPurpose) {
Toast.error('이벤트 목적을 선택해주세요.');
return;
}
// 이벤트 정보 저장
const eventData = {
id: Utils.generateId(),
purpose: selectedPurpose,
purposeDetails: purposeDetails[selectedPurpose],
createdAt: new Date().toISOString(),
status: 'planning'
};
window.AppState.currentEvent = eventData;
window.AppState.save();
// AI 분석 화면으로 이동
Loading.show('AI 트렌드 분석 준비 중...');
setTimeout(() => {
window.location.href = '04-AI트렌드분석결과.html';
}, 800);
});
// 닫기 확인
window.confirmClose = function() {
Modal.confirm(
'이벤트 기획 종료',
'현재까지 입력한 내용이 저장되지 않습니다. 정말 종료하시겠습니까?',
function() {
window.location.href = '21-실시간대시보드.html';
}
);
};
console.log('이벤트목적선택 페이지 로드 완료');
})();
</script>
<style>
/* Purpose Card Styles */
.purpose-card {
display: block;
margin-bottom: 16px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.purpose-radio {
position: absolute;
opacity: 0;
pointer-events: none;
}
.purpose-card__content {
padding: 20px;
border: 2px solid var(--color-gray-200);
border-radius: var(--radius-lg);
background: var(--color-white);
transition: all 0.3s ease;
position: relative;
}
.purpose-card:hover .purpose-card__content {
border-color: var(--color-primary-main);
background: var(--color-primary-light);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.purpose-card.selected .purpose-card__content {
border-color: var(--color-primary-main);
border-width: 3px;
background: var(--color-primary-light);
box-shadow: 0 0 0 4px rgba(227, 30, 36, 0.1);
}
.purpose-card__icon {
font-size: 40px;
margin-bottom: 12px;
text-align: center;
}
.purpose-card__title {
font-size: 18px;
font-weight: 700;
color: var(--color-gray-900);
margin-bottom: 4px;
text-align: center;
}
.purpose-card__description {
font-size: 14px;
color: var(--color-gray-600);
margin-bottom: 8px;
text-align: center;
}
.purpose-card__effect {
font-size: 14px;
font-weight: 600;
color: var(--color-secondary-main);
text-align: center;
}
.purpose-card__check {
position: absolute;
top: 16px;
right: 16px;
opacity: 0;
transition: opacity 0.3s ease;
}
.purpose-card__check .material-icons {
color: var(--color-primary-main);
font-size: 28px;
}
.purpose-card.selected .purpose-card__check {
opacity: 1;
}
/* Active state for touch devices */
.purpose-card:active .purpose-card__content {
transform: scale(0.98);
}
/* Accessibility */
@media (prefers-reduced-motion: reduce) {
.purpose-card,
.purpose-card__content,
.purpose-card__check {
transition: none;
}
}
/* Focus state for keyboard navigation */
.purpose-radio:focus + .purpose-card__content {
outline: 2px solid var(--color-secondary-main);
outline-offset: 2px;
}
</style>
</body>
</html>

View File

@ -0,0 +1,413 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="KT AI 기반 소상공인 이벤트 자동 생성 서비스 - AI트렌드분석결과">
<title>AI 트렌드 분석 - KT 이벤트 마케팅</title>
<!-- Styles -->
<link rel="stylesheet" href="css/common.css">
<!-- Fonts -->
<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>
<!-- Skip Link -->
<a href="#main-content" class="skip-link">본문으로 건너뛰기</a>
<!-- App Wrapper -->
<div class="app-wrapper">
<!-- Top App Bar -->
<header class="app-bar">
<button class="app-bar__back" aria-label="뒤로가기">
<span class="material-icons">arrow_back</span>
</button>
<h1 class="app-bar__title">AI 트렌드 분석</h1>
</header>
<!-- Main Content -->
<main id="main-content" class="app-content">
<div class="container" style="max-width: 500px; margin: 0 auto; padding-top: 24px;">
<!-- Progress Section -->
<div style="margin-bottom: 32px;">
<div class="body-m" style="margin-bottom: 8px; font-weight: 600;">단계 2/6: 트렌드 분석</div>
<div style="display: flex; gap: 4px;">
<div style="flex: 1; height: 4px; background: var(--color-primary-main); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-primary-main); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-gray-200); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-gray-200); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-gray-200); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-gray-200); border-radius: 2px;"></div>
</div>
</div>
<!-- AI Analysis Loading -->
<div id="loadingSection" style="text-align: center; padding: 40px 0;">
<div class="spinner" style="margin: 0 auto 24px;"></div>
<h2 class="h3" style="margin-bottom: 8px;">🤖 AI가 분석중입니다...</h2>
<p class="body-m text-muted">업종, 지역, 시즌을 고려한 트렌드를 분석하고 있습니다</p>
<div id="progressBar" style="width: 100%; height: 8px; background: var(--color-gray-200); border-radius: 4px; margin: 24px 0; overflow: hidden;">
<div id="progressFill" style="height: 100%; background: var(--color-primary-main); width: 0%; transition: width 0.3s ease;"></div>
</div>
<div class="body-s text-muted" id="progressText">분석 시작 중...</div>
</div>
<!-- Analysis Results (Hidden initially) -->
<div id="resultsSection" style="display: none; opacity: 0; transition: opacity 0.5s ease;">
<!-- Store Info -->
<div style="margin-bottom: 32px;">
<h2 class="h3" style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span>📊</span>
<span>분석 결과</span>
</h2>
<div class="card" style="padding: 16px;">
<div style="display: grid; grid-template-columns: auto 1fr; gap: 12px 16px;">
<div class="body-m text-muted">매장</div>
<div class="body-m" style="font-weight: 600;" id="storeName">-</div>
<div class="body-m text-muted">업종</div>
<div class="body-m" style="font-weight: 600;" id="businessType">-</div>
<div class="body-m text-muted">지역</div>
<div class="body-m" style="font-weight: 600;" id="location">-</div>
<div class="body-m text-muted">시즌</div>
<div class="body-m" style="font-weight: 600;" id="season">-</div>
</div>
</div>
</div>
<!-- Trends -->
<div style="margin-bottom: 32px;">
<h3 class="body-l" style="font-weight: 700; margin-bottom: 16px;">주요 트렌드</h3>
<div id="trendsList">
<!-- Trends will be dynamically added here -->
</div>
</div>
<!-- Success Rate -->
<div style="margin-bottom: 32px;">
<div class="card" style="padding: 20px; text-align: center; background: linear-gradient(135deg, var(--color-secondary-light) 0%, var(--color-white) 100%);">
<div class="h2" style="color: var(--color-secondary-main); margin-bottom: 8px;" id="successRate">
78% 🎯
</div>
<div class="body-m text-muted">예상 성공률 (동일 업종 평균 대비)</div>
</div>
</div>
<!-- Retry Button (Hidden unless error) -->
<button
id="retryBtn"
class="btn btn-secondary btn-md btn-block"
style="display: none; margin-bottom: 16px;"
onclick="retryAnalysis()"
>
<span class="material-icons" style="font-size: 20px; margin-right: 8px;">refresh</span>
다시 분석하기
</button>
<!-- Next Button -->
<button
id="nextBtn"
class="btn btn-primary btn-lg btn-block"
style="margin-bottom: 32px;"
>
다음 (경품추천)
</button>
</div>
</div>
</main>
</div>
<!-- Scripts -->
<script src="js/common.js"></script>
<script>
(function() {
'use strict';
const loadingSection = document.getElementById('loadingSection');
const resultsSection = document.getElementById('resultsSection');
const progressFill = document.getElementById('progressFill');
const progressText = document.getElementById('progressText');
const nextBtn = document.getElementById('nextBtn');
const retryBtn = document.getElementById('retryBtn');
// 상태 로드
window.AppState.load();
const store = window.AppState.store;
const event = window.AppState.currentEvent;
// 매장 정보가 없으면 이전 화면으로
if (!store) {
Toast.error('매장 정보를 먼저 등록해주세요.');
setTimeout(() => {
window.location.href = '02-매장정보등록.html';
}, 1000);
return;
}
// AI 분석 시뮬레이션
let progress = 0;
const analysisSteps = [
{ progress: 20, text: '업종 데이터 수집 중...' },
{ progress: 40, text: '지역 트렌드 분석 중...' },
{ progress: 60, text: '시즌 패턴 학습 중...' },
{ progress: 80, text: '최적 전략 계산 중...' },
{ progress: 100, text: '분석 완료!' }
];
let currentStep = 0;
function updateProgress() {
if (currentStep < analysisSteps.length) {
const step = analysisSteps[currentStep];
progressFill.style.width = step.progress + '%';
progressText.textContent = step.text;
currentStep++;
// 랜덤한 시간 간격으로 진행 (0.5~1초)
const delay = 500 + Math.random() * 500;
setTimeout(updateProgress, delay);
} else {
// 분석 완료
setTimeout(showResults, 500);
}
}
// 분석 결과 표시
function showResults() {
// 로딩 숨기기
loadingSection.style.display = 'none';
// 매장 정보 표시
document.getElementById('storeName').textContent = store.name || '-';
document.getElementById('businessType').textContent = store.businessTypeName || '-';
const addressParts = store.address ? store.address.split(' ') : [];
const locationText = addressParts.slice(0, 3).join(' ') || '-';
document.getElementById('location').textContent = locationText;
// 현재 시즌 계산
const currentMonth = new Date().getMonth() + 1;
let season = '';
if (currentMonth >= 3 && currentMonth <= 5) season = '봄';
else if (currentMonth >= 6 && currentMonth <= 8) season = '여름';
else if (currentMonth >= 9 && currentMonth <= 11) season = '가을';
else season = '겨울';
document.getElementById('season').textContent = `${season} (${currentMonth}월)`;
// 트렌드 데이터 생성 (업종별 트렌드 시뮬레이션)
const trends = generateTrends(store.businessType, season, event.purpose);
// 트렌드 렌더링
const trendsList = document.getElementById('trendsList');
trendsList.innerHTML = trends.map((trend, index) => `
<div class="trend-card" onclick="showTrendDetail(${index})" style="margin-bottom: 12px; cursor: pointer;">
<div class="card" style="padding: 16px; transition: all 0.3s ease;">
<div style="display: flex; gap: 12px;">
<div class="trend-number">${trend.emoji}</div>
<div style="flex: 1;">
<div class="body-m" style="font-weight: 700; margin-bottom: 4px;">
${trend.title}
</div>
<div class="body-s" style="color: var(--color-secondary-main); margin-bottom: 8px;">
→ ${trend.recommendation}
</div>
<div class="body-s text-muted">${trend.description}</div>
</div>
</div>
</div>
</div>
`).join('');
// 성공률 계산
const successRate = calculateSuccessRate(store.businessType, event.purpose, trends);
document.getElementById('successRate').textContent = `${successRate}% 🎯`;
// 분석 결과 저장
event.trendAnalysis = {
trends: trends,
successRate: successRate,
analyzedAt: new Date().toISOString()
};
window.AppState.save();
// 결과 섹션 표시 (페이드인)
resultsSection.style.display = 'block';
setTimeout(() => {
resultsSection.style.opacity = '1';
}, 50);
Toast.success('AI 트렌드 분석이 완료되었습니다!');
}
// 업종/시즌/목적별 트렌드 생성
function generateTrends(businessType, season, purpose) {
const trendData = {
restaurant: {
winter: [
{ emoji: '1⃣', title: '연말 모임 증가', recommendation: '단체 할인 이벤트 효과적', description: '회식, 송년회 수요 급증으로 단체 예약 고객 공략 필요' },
{ emoji: '2⃣', title: '배달 주문 급증', recommendation: '배달 경품 추천', description: '추운 날씨로 배달 주문 40% 증가, 배달 전용 혜택 제공' },
{ emoji: '3⃣', title: 'SNS 공유 활발', recommendation: '바이럴 참여방법 권장', description: '연말 분위기로 SNS 공유율 높음, 해시태그 이벤트 효과적' }
],
spring: [
{ emoji: '1⃣', title: '야외 활동 증가', recommendation: '테이크아웃 이벤트', description: '봄 나들이 시즌, 테이크아웃 주문 35% 증가' },
{ emoji: '2⃣', title: '가족 단위 방문', recommendation: '패밀리 세트 경품', description: '주말 가족 방문객 증가, 어린이 메뉴 할인 효과적' },
{ emoji: '3⃣', title: '건강식 선호', recommendation: '건강 메뉴 홍보', description: '봄철 건강관리 트렌드, 저칼로리 메뉴 각광' }
]
},
cafe: {
winter: [
{ emoji: '1⃣', title: '따뜻한 음료 선호', recommendation: '핫 음료 할인 이벤트', description: '겨울철 따뜻한 음료 판매량 60% 증가' },
{ emoji: '2⃣', title: '실내 체류 시간 증가', recommendation: '재방문 쿠폰 제공', description: '추운 날씨로 카페 체류시간 2배 증가' },
{ emoji: '3⃣', title: '디저트 수요 증가', recommendation: '음료+디저트 세트', description: '연말 분위기로 디저트 주문 45% 상승' }
]
}
};
// 기본 트렌드 (업종/시즌이 매칭되지 않을 경우)
const defaultTrends = [
{ emoji: '1⃣', title: '할인 이벤트 선호', recommendation: '가격 할인 경품 추천', description: '소비자 가격 민감도 증가, 할인 이벤트 참여율 높음' },
{ emoji: '2⃣', title: '모바일 접근성 중요', recommendation: 'QR코드 참여방식', description: '모바일 결제 증가, 간편한 참여 방법 선호' },
{ emoji: '3⃣', title: '후기 공유 활발', recommendation: '리뷰 이벤트 연계', description: 'SNS 리뷰 작성 유도로 바이럴 효과 기대' }
];
// 업종과 시즌에 맞는 트렌드 반환
const seasonKey = season === '봄' ? 'spring' : season === '여름' ? 'summer' : season === '가을' ? 'fall' : 'winter';
if (trendData[businessType] && trendData[businessType][seasonKey]) {
return trendData[businessType][seasonKey];
} else if (trendData[businessType]) {
// 해당 시즌 데이터가 없으면 겨울 데이터 사용
return trendData[businessType].winter || defaultTrends;
} else {
return defaultTrends;
}
}
// 성공률 계산
function calculateSuccessRate(businessType, purpose, trends) {
let baseRate = 70;
// 업종별 기본 성공률
const businessRates = {
restaurant: 75,
cafe: 78,
retail: 72,
beauty: 80,
service: 70
};
if (businessRates[businessType]) {
baseRate = businessRates[businessType];
}
// 목적별 가중치
const purposeBonus = {
new_customer: 3,
repeat_visit: 5,
sales_increase: 0,
brand_awareness: -2
};
if (purposeBonus[purpose] !== undefined) {
baseRate += purposeBonus[purpose];
}
// 트렌드 개수에 따른 보너스
baseRate += trends.length;
return Math.min(95, Math.max(65, baseRate));
}
// 트렌드 상세 정보 표시
window.showTrendDetail = function(index) {
const trends = event.trendAnalysis.trends;
if (!trends || !trends[index]) return;
const trend = trends[index];
BottomSheet.show(`
<div style="padding: 8px 0;">
<div style="text-align: center; margin-bottom: 20px;">
<div style="font-size: 48px; margin-bottom: 8px;">${trend.emoji}</div>
<h3 class="h3">${trend.title}</h3>
</div>
<div style="margin-bottom: 20px;">
<h4 class="body-m" style="font-weight: 600; margin-bottom: 8px;">트렌드 설명</h4>
<p class="body-m">${trend.description}</p>
</div>
<div class="card" style="padding: 16px; background: var(--color-secondary-light);">
<h4 class="body-m" style="font-weight: 600; margin-bottom: 8px;">AI 추천</h4>
<p class="body-m" style="color: var(--color-secondary-main);">${trend.recommendation}</p>
</div>
</div>
`);
};
// 재시도
window.retryAnalysis = function() {
// 결과 숨기기
resultsSection.style.display = 'none';
resultsSection.style.opacity = '0';
retryBtn.style.display = 'none';
// 로딩 다시 표시
loadingSection.style.display = 'block';
currentStep = 0;
progress = 0;
progressFill.style.width = '0%';
progressText.textContent = '분석 시작 중...';
// 분석 재시작
setTimeout(updateProgress, 500);
};
// 다음 버튼
nextBtn.addEventListener('click', function() {
Loading.show('경품 추천 준비 중...');
setTimeout(() => {
window.location.href = '05-AI경품추천.html';
}, 800);
});
// 페이지 로드 시 분석 시작
setTimeout(updateProgress, 1000);
console.log('AI트렌드분석결과 페이지 로드 완료');
})();
</script>
<style>
.trend-card .card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
border-color: var(--color-primary-main);
}
.trend-number {
font-size: 24px;
min-width: 32px;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
#progressText {
animation: pulse 2s ease-in-out infinite;
}
</style>
</body>
</html>

View File

@ -0,0 +1,563 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="KT AI 기반 소상공인 이벤트 자동 생성 서비스 - AI경품추천">
<title>AI 경품 추천 - KT 이벤트 마케팅</title>
<!-- Styles -->
<link rel="stylesheet" href="css/common.css">
<!-- Fonts -->
<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>
<!-- Skip Link -->
<a href="#main-content" class="skip-link">본문으로 건너뛰기</a>
<!-- App Wrapper -->
<div class="app-wrapper">
<!-- Top App Bar -->
<header class="app-bar">
<button class="app-bar__back" aria-label="뒤로가기">
<span class="material-icons">arrow_back</span>
</button>
<h1 class="app-bar__title">AI 경품 추천</h1>
</header>
<!-- Main Content -->
<main id="main-content" class="app-content">
<div class="container" style="max-width: 500px; margin: 0 auto; padding-top: 24px;">
<!-- Progress Section -->
<div style="margin-bottom: 32px;">
<div class="body-m" style="margin-bottom: 8px; font-weight: 600;">단계 3/6: 경품 선택</div>
<div style="display: flex; gap: 4px;">
<div style="flex: 1; height: 4px; background: var(--color-primary-main); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-primary-main); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-primary-main); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-gray-200); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-gray-200); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-gray-200); border-radius: 2px;"></div>
</div>
</div>
<!-- Budget Setting -->
<section style="margin-bottom: 32px;">
<h2 class="h3" style="margin-bottom: 16px;">예산 설정</h2>
<div class="card" style="padding: 20px;">
<div style="text-align: center; margin-bottom: 20px;">
<div class="h2" style="color: var(--color-primary-main);" id="budgetDisplay">
100,000원
</div>
</div>
<input
type="range"
id="budgetSlider"
min="10000"
max="5000000"
step="10000"
value="100000"
style="width: 100%; margin-bottom: 12px;"
>
<div style="display: flex; justify-content: space-between;">
<span class="body-s text-muted">1만원</span>
<span class="body-s text-muted">500만원</span>
</div>
</div>
</section>
<!-- AI Recommendations -->
<section style="margin-bottom: 24px;">
<h2 class="h3" style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span>🤖</span>
<span>AI 추천 경품 (Top 5)</span>
</h2>
<div id="prizesList">
<!-- Prizes will be dynamically added here -->
</div>
</section>
<!-- Add Custom Prize -->
<button
class="btn btn-text btn-md"
style="width: 100%; margin-bottom: 24px;"
onclick="addCustomPrize()"
>
<span class="material-icons" style="margin-right: 8px;">add</span>
직접 입력하기
</button>
<!-- Budget Warning -->
<div id="budgetWarning" style="display: none; margin-bottom: 24px;">
<div class="card" style="padding: 16px; background: var(--color-error-light); border: 1px solid var(--color-error);">
<div style="display: flex; align-items: center; gap: 12px;">
<span class="material-icons" style="color: var(--color-error); font-size: 24px;">warning</span>
<div>
<div class="body-m" style="font-weight: 600; color: var(--color-error); margin-bottom: 4px;">
예산 초과
</div>
<div class="body-s text-muted">
선택한 경품의 총 비용이 예산을 초과합니다.
</div>
</div>
</div>
</div>
</div>
<!-- Next Button -->
<button
id="nextBtn"
class="btn btn-primary btn-lg btn-block"
style="margin-bottom: 32px;"
disabled
>
다음 (참여방법)
</button>
</div>
</main>
</div>
<!-- Scripts -->
<script src="js/common.js"></script>
<script>
(function() {
'use strict';
const budgetSlider = document.getElementById('budgetSlider');
const budgetDisplay = document.getElementById('budgetDisplay');
const prizesList = document.getElementById('prizesList');
const budgetWarning = document.getElementById('budgetWarning');
const nextBtn = document.getElementById('nextBtn');
let currentBudget = 100000;
let recommendedPrizes = [];
let selectedPrize = null;
// 상태 로드
window.AppState.load();
const store = window.AppState.store;
const event = window.AppState.currentEvent;
// 예산 슬라이더 변경
budgetSlider.addEventListener('input', function() {
currentBudget = parseInt(this.value);
budgetDisplay.textContent = Utils.formatNumber(currentBudget) + '원';
});
budgetSlider.addEventListener('change', function() {
// 예산 변경 시 경품 재추천
Loading.show('예산에 맞는 경품 재추천 중...');
setTimeout(() => {
generatePrizeRecommendations();
Loading.hide();
Toast.success('경품이 재추천되었습니다.');
}, 1000);
});
// AI 경품 추천 생성
function generatePrizeRecommendations() {
const businessType = store.businessType;
const purpose = event.purpose;
// 업종/목적별 경품 데이터
const prizesDatabase = {
restaurant: [
{ name: '치킨세트 무료교환', basePrice: 20000, attraction: 5, participationRate: 45, category: 'product' },
{ name: '5천원 할인쿠폰', basePrice: 5000, attraction: 4, participationRate: 38, category: 'discount' },
{ name: '사이드메뉴 무료 제공', basePrice: 8000, attraction: 4, participationRate: 40, category: 'product' },
{ name: '음료 무료 업그레이드', basePrice: 3000, attraction: 3, participationRate: 35, category: 'upgrade' },
{ name: '10% 할인 쿠폰', basePrice: 10000, attraction: 4, participationRate: 36, category: 'discount' }
],
cafe: [
{ name: '음료 1+1 쿠폰', basePrice: 5000, attraction: 5, participationRate: 42, category: 'product' },
{ name: '디저트 무료 제공', basePrice: 6000, attraction: 4, participationRate: 38, category: 'product' },
{ name: '3천원 할인쿠폰', basePrice: 3000, attraction: 4, participationRate: 40, category: 'discount' },
{ name: '사이즈 업그레이드', basePrice: 2000, attraction: 3, participationRate: 33, category: 'upgrade' },
{ name: '스탬프 2배 적립', basePrice: 0, attraction: 3, participationRate: 30, category: 'point' }
],
retail: [
{ name: '20% 할인쿠폰', basePrice: 10000, attraction: 5, participationRate: 44, category: 'discount' },
{ name: '무료 배송권', basePrice: 3000, attraction: 4, participationRate: 36, category: 'service' },
{ name: '사은품 증정', basePrice: 8000, attraction: 4, participationRate: 38, category: 'product' },
{ name: '5천원 상품권', basePrice: 5000, attraction: 4, participationRate: 40, category: 'voucher' },
{ name: 'VIP 멤버십 1개월', basePrice: 0, attraction: 3, participationRate: 28, category: 'membership' }
]
};
// 기본 경품 (업종이 매칭되지 않을 경우)
const defaultPrizes = [
{ name: 'KT 멤버십 포인트 5천점', basePrice: 5000, attraction: 3, participationRate: 32, category: 'point' },
{ name: '5천원 모바일 상품권', basePrice: 5000, attraction: 4, participationRate: 38, category: 'voucher' },
{ name: '3천원 할인쿠폰', basePrice: 3000, attraction: 4, participationRate: 36, category: 'discount' },
{ name: '경품 추첨권', basePrice: 0, attraction: 2, participationRate: 25, category: 'raffle' },
{ name: '다음 방문 시 10% 할인', basePrice: 0, attraction: 3, participationRate: 30, category: 'discount' }
];
let prizes = prizesDatabase[businessType] || defaultPrizes;
// 예산에 맞춰 가격 조정 및 추천
const budgetPerPrize = currentBudget * 0.2; // 경품 1개당 예산의 20%
recommendedPrizes = prizes.map((prize, index) => {
// 예산에 따라 수량 조정
const maxQuantity = Math.floor(currentBudget / prize.basePrice) || 100;
const recommendedQuantity = Math.min(maxQuantity, Math.ceil(50 + Math.random() * 50));
return {
id: Utils.generateId(),
rank: index + 1,
name: prize.name,
price: prize.basePrice,
quantity: recommendedQuantity,
attraction: prize.attraction,
participationRate: prize.participationRate,
category: prize.category,
totalCost: prize.basePrice * recommendedQuantity
};
}).slice(0, 5);
renderPrizesList();
}
// 경품 목록 렌더링
function renderPrizesList() {
prizesList.innerHTML = recommendedPrizes.map(prize => `
<div class="prize-card ${selectedPrize && selectedPrize.id === prize.id ? 'selected' : ''}" data-prize-id="${prize.id}">
<div class="card" style="padding: 16px; margin-bottom: 12px; cursor: pointer; transition: all 0.3s ease; position: relative;">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px;">
<div style="flex: 1;">
<div class="body-m" style="font-weight: 700; margin-bottom: 4px;">
${prize.rank}. ${prize.name}
</div>
<div class="body-s text-muted">
${Utils.formatNumber(prize.price)}원 × ${prize.quantity}개
</div>
</div>
${selectedPrize && selectedPrize.id === prize.id ? `
<span class="material-icons" style="color: var(--color-primary-main); font-size: 28px;">
check_circle
</span>
` : ''}
</div>
<div style="margin-bottom: 12px;">
<div class="body-s" style="margin-bottom: 4px;">
매력도: ${'⭐'.repeat(prize.attraction)}${'☆'.repeat(5 - prize.attraction)}
</div>
<div class="body-s" style="color: var(--color-secondary-main);">
예상참여율: ${prize.participationRate}%
</div>
</div>
<div style="display: flex; gap: 8px;">
<button
class="btn btn-secondary btn-sm"
style="flex: 1;"
onclick="selectPrize('${prize.id}')"
>
선택
</button>
<button
class="btn btn-text btn-sm"
style="flex: 1;"
onclick="editPrize('${prize.id}')"
>
수정
</button>
</div>
</div>
</div>
`).join('');
}
// 경품 선택
window.selectPrize = function(prizeId) {
const prize = recommendedPrizes.find(p => p.id === prizeId);
if (!prize) return;
selectedPrize = prize;
renderPrizesList();
checkBudget();
nextBtn.disabled = false;
Toast.success(`${prize.name}이(가) 선택되었습니다.`);
// 예상 효과 재계산 애니메이션
const card = document.querySelector(`[data-prize-id="${prizeId}"]`);
if (card) {
card.style.transform = 'scale(1.02)';
setTimeout(() => {
card.style.transform = '';
}, 200);
}
};
// 경품 수정
window.editPrize = function(prizeId) {
const prize = recommendedPrizes.find(p => p.id === prizeId);
if (!prize) return;
Modal.show({
title: '경품 수정',
body: `
<div class="form-group">
<label class="form-label required">경품명</label>
<input
type="text"
id="editPrizeName"
class="form-input"
value="${prize.name}"
required
>
</div>
<div class="form-group">
<label class="form-label required">개당 가격 (원)</label>
<input
type="number"
id="editPrizePrice"
class="form-input"
value="${prize.price}"
required
min="0"
>
</div>
<div class="form-group">
<label class="form-label required">수량 (개)</label>
<input
type="number"
id="editPrizeQuantity"
class="form-input"
value="${prize.quantity}"
required
min="1"
>
</div>
<div class="body-s text-muted" style="margin-top: 8px;">
총 비용: <span id="editTotalCost">${Utils.formatNumber(prize.totalCost)}</span>
</div>
`,
confirmText: '저장',
onConfirm: function() {
const newName = document.getElementById('editPrizeName').value.trim();
const newPrice = parseInt(document.getElementById('editPrizePrice').value);
const newQuantity = parseInt(document.getElementById('editPrizeQuantity').value);
if (!newName || !newPrice || !newQuantity) {
Toast.error('모든 항목을 입력해주세요.');
return;
}
prize.name = newName;
prize.price = newPrice;
prize.quantity = newQuantity;
prize.totalCost = newPrice * newQuantity;
// 예상참여율 재계산 (가격이 낮을수록 참여율 높음)
const priceRatio = newPrice / 10000;
prize.participationRate = Math.min(50, Math.max(20, Math.round(40 - priceRatio * 5)));
renderPrizesList();
checkBudget();
Toast.success('경품이 수정되었습니다.');
}
});
// 실시간 총 비용 계산
setTimeout(() => {
const priceInput = document.getElementById('editPrizePrice');
const quantityInput = document.getElementById('editPrizeQuantity');
const totalCostSpan = document.getElementById('editTotalCost');
function updateTotalCost() {
const price = parseInt(priceInput.value) || 0;
const quantity = parseInt(quantityInput.value) || 0;
totalCostSpan.textContent = Utils.formatNumber(price * quantity);
}
priceInput.addEventListener('input', updateTotalCost);
quantityInput.addEventListener('input', updateTotalCost);
}, 100);
};
// 직접 경품 추가
window.addCustomPrize = function() {
Modal.show({
title: '경품 직접 입력',
body: `
<div class="form-group">
<label class="form-label required">경품명</label>
<input
type="text"
id="customPrizeName"
class="form-input"
placeholder="예) 스타벅스 아메리카노"
required
>
</div>
<div class="form-group">
<label class="form-label required">개당 가격 (원)</label>
<input
type="number"
id="customPrizePrice"
class="form-input"
placeholder="5000"
required
min="0"
>
</div>
<div class="form-group">
<label class="form-label required">수량 (개)</label>
<input
type="number"
id="customPrizeQuantity"
class="form-input"
placeholder="50"
required
min="1"
>
</div>
<div class="body-s text-muted" style="margin-top: 8px;">
총 비용: <span id="customTotalCost">0</span>
</div>
`,
confirmText: '추가',
onConfirm: function() {
const name = document.getElementById('customPrizeName').value.trim();
const price = parseInt(document.getElementById('customPrizePrice').value);
const quantity = parseInt(document.getElementById('customPrizeQuantity').value);
if (!name || !price || !quantity) {
Toast.error('모든 항목을 입력해주세요.');
return;
}
const newPrize = {
id: Utils.generateId(),
rank: recommendedPrizes.length + 1,
name: name,
price: price,
quantity: quantity,
attraction: 3,
participationRate: 30,
category: 'custom',
totalCost: price * quantity
};
recommendedPrizes.push(newPrize);
renderPrizesList();
Toast.success('경품이 추가되었습니다.');
}
});
// 실시간 총 비용 계산
setTimeout(() => {
const priceInput = document.getElementById('customPrizePrice');
const quantityInput = document.getElementById('customPrizeQuantity');
const totalCostSpan = document.getElementById('customTotalCost');
function updateTotalCost() {
const price = parseInt(priceInput.value) || 0;
const quantity = parseInt(quantityInput.value) || 0;
totalCostSpan.textContent = Utils.formatNumber(price * quantity);
}
priceInput.addEventListener('input', updateTotalCost);
quantityInput.addEventListener('input', updateTotalCost);
}, 100);
};
// 예산 초과 체크
function checkBudget() {
if (!selectedPrize) {
budgetWarning.style.display = 'none';
return;
}
if (selectedPrize.totalCost > currentBudget) {
budgetWarning.style.display = 'block';
nextBtn.disabled = true;
} else {
budgetWarning.style.display = 'none';
nextBtn.disabled = false;
}
}
// 다음 버튼
nextBtn.addEventListener('click', function() {
if (!selectedPrize) {
Toast.error('경품을 선택해주세요.');
return;
}
if (selectedPrize.totalCost > currentBudget) {
Toast.error('예산을 초과합니다. 경품을 수정하거나 예산을 늘려주세요.');
return;
}
// 경품 정보 저장
event.prize = selectedPrize;
event.budget = currentBudget;
window.AppState.save();
Loading.show('참여방법 준비 중...');
setTimeout(() => {
window.location.href = '06-AI참여방법설계.html';
}, 800);
});
// 초기화
generatePrizeRecommendations();
console.log('AI경품추천 페이지 로드 완료');
})();
</script>
<style>
input[type="range"] {
-webkit-appearance: none;
appearance: none;
height: 6px;
background: var(--color-gray-200);
border-radius: 3px;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
background: var(--color-primary-main);
border-radius: 50%;
cursor: pointer;
box-shadow: var(--shadow-sm);
}
input[type="range"]::-moz-range-thumb {
width: 20px;
height: 20px;
background: var(--color-primary-main);
border-radius: 50%;
cursor: pointer;
border: none;
box-shadow: var(--shadow-sm);
}
.prize-card.selected .card {
border: 3px solid var(--color-primary-main);
background: var(--color-primary-light);
box-shadow: 0 0 0 4px rgba(227, 30, 36, 0.1);
}
.prize-card .card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
</style>
</body>
</html>

View File

@ -0,0 +1,507 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="KT AI 기반 소상공인 이벤트 자동 생성 서비스 - AI참여방법설계">
<title>AI 참여방법 설계 - KT 이벤트 마케팅</title>
<!-- Styles -->
<link rel="stylesheet" href="css/common.css">
<!-- Fonts -->
<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>
<!-- Skip Link -->
<a href="#main-content" class="skip-link">본문으로 건너뛰기</a>
<!-- App Wrapper -->
<div class="app-wrapper">
<!-- Top App Bar -->
<header class="app-bar">
<button class="app-bar__back" aria-label="뒤로가기">
<span class="material-icons">arrow_back</span>
</button>
<h1 class="app-bar__title">AI 참여방법 설계</h1>
</header>
<!-- Main Content -->
<main id="main-content" class="app-content">
<div class="container" style="max-width: 500px; margin: 0 auto; padding-top: 24px;">
<!-- Progress Section -->
<div style="margin-bottom: 32px;">
<div class="body-m" style="margin-bottom: 8px; font-weight: 600;">단계 4/6: 참여방법 선택</div>
<div style="display: flex; gap: 4px;">
<div style="flex: 1; height: 4px; background: var(--color-primary-main); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-primary-main); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-primary-main); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-primary-main); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-gray-200); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-gray-200); border-radius: 2px;"></div>
</div>
</div>
<!-- Instructions -->
<div style="text-align: center; margin-bottom: 32px;">
<h2 class="h2" style="margin-bottom: 8px; display: flex; align-items: center; justify-content: center; gap: 8px;">
<span>🤖</span>
<span>AI 추천 참여방법</span>
</h2>
<p class="body-m text-muted">이벤트 목적에 맞는 최적의 참여 방법을 선택하세요</p>
</div>
<!-- Participation Methods -->
<div id="methodOptions" style="margin-bottom: 24px;">
<!-- Option 1: Simple -->
<div class="method-card" data-method="simple">
<div class="card" style="padding: 20px; margin-bottom: 16px; cursor: pointer; transition: all 0.3s ease;">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 16px;">
<div>
<div class="body-l" style="font-weight: 700; margin-bottom: 4px;">옵션 1: 간편형</div>
<div class="h3" style="margin-bottom: 8px;">📱 QR 코드 스캔</div>
</div>
<div class="method-check" style="opacity: 0; transition: opacity 0.3s ease;">
<span class="material-icons" style="color: var(--color-primary-main); font-size: 32px;">
check_circle
</span>
</div>
</div>
<div style="margin-bottom: 16px;">
<div class="body-m" style="margin-bottom: 8px;">
<span class="text-muted">난이도:</span> ⭐ (쉬움)
</div>
<div class="body-m" style="margin-bottom: 8px;">
<span class="text-muted">예상참여율:</span>
<span style="color: var(--color-secondary-main); font-weight: 600;">60%</span>
</div>
<div class="body-m">
<span class="text-muted">재방문율:</span>
<span style="color: var(--color-success); font-weight: 600;">20%</span>
</div>
</div>
<div style="margin-bottom: 16px;">
<div class="body-s" style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
<span class="material-icons" style="font-size: 16px; color: var(--color-success);">check</span>
<span>빠른 참여</span>
</div>
<div class="body-s" style="display: flex; align-items: center; gap: 8px;">
<span class="material-icons" style="font-size: 16px; color: var(--color-success);">check</span>
<span>매장 방문 불필요</span>
</div>
</div>
<button
class="btn btn-secondary btn-md btn-block"
onclick="selectMethod('simple')"
>
선택하기
</button>
</div>
</div>
<!-- Option 2: Revisit -->
<div class="method-card" data-method="revisit">
<div class="card" style="padding: 20px; margin-bottom: 16px; cursor: pointer; transition: all 0.3s ease;">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 16px;">
<div>
<div class="body-l" style="font-weight: 700; margin-bottom: 4px;">옵션 2: 재방문 유도형</div>
<div class="h3" style="margin-bottom: 8px;">🏪 매장 방문 + 리뷰</div>
</div>
<div class="method-check" style="opacity: 0; transition: opacity 0.3s ease;">
<span class="material-icons" style="color: var(--color-primary-main); font-size: 32px;">
check_circle
</span>
</div>
</div>
<div style="margin-bottom: 16px;">
<div class="body-m" style="margin-bottom: 8px;">
<span class="text-muted">난이도:</span> ⭐⭐ (보통)
</div>
<div class="body-m" style="margin-bottom: 8px;">
<span class="text-muted">예상참여율:</span>
<span style="color: var(--color-secondary-main); font-weight: 600;">35%</span>
</div>
<div class="body-m">
<span class="text-muted">재방문율:</span>
<span style="color: var(--color-success); font-weight: 600;">80%</span>
</div>
</div>
<div style="margin-bottom: 16px;">
<div class="body-s" style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
<span class="material-icons" style="font-size: 16px; color: var(--color-success);">check</span>
<span>높은 재방문율</span>
</div>
<div class="body-s" style="display: flex; align-items: center; gap: 8px;">
<span class="material-icons" style="font-size: 16px; color: var(--color-success);">check</span>
<span>리뷰 축적</span>
</div>
</div>
<button
class="btn btn-secondary btn-md btn-block"
onclick="selectMethod('revisit')"
>
선택하기
</button>
</div>
</div>
<!-- Option 3: Viral -->
<div class="method-card" data-method="viral">
<div class="card" style="padding: 20px; margin-bottom: 16px; cursor: pointer; transition: all 0.3s ease;">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 16px;">
<div>
<div class="body-l" style="font-weight: 700; margin-bottom: 4px;">옵션 3: 바이럴형</div>
<div class="h3" style="margin-bottom: 8px;">📢 SNS 공유 + 태그</div>
</div>
<div class="method-check" style="opacity: 0; transition: opacity 0.3s ease;">
<span class="material-icons" style="color: var(--color-primary-main); font-size: 32px;">
check_circle
</span>
</div>
</div>
<div style="margin-bottom: 16px;">
<div class="body-m" style="margin-bottom: 8px;">
<span class="text-muted">난이도:</span> ⭐⭐⭐ (어려움)
</div>
<div class="body-m" style="margin-bottom: 8px;">
<span class="text-muted">예상참여율:</span>
<span style="color: var(--color-secondary-main); font-weight: 600;">25%</span>
</div>
<div class="body-m">
<span class="text-muted">바이럴 확산:</span>
<span style="color: var(--color-success); font-weight: 600;">150%</span>
</div>
</div>
<div style="margin-bottom: 16px;">
<div class="body-s" style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
<span class="material-icons" style="font-size: 16px; color: var(--color-success);">check</span>
<span>입소문 효과</span>
</div>
<div class="body-s" style="display: flex; align-items: center; gap: 8px;">
<span class="material-icons" style="font-size: 16px; color: var(--color-success);">check</span>
<span>신규고객 유입</span>
</div>
</div>
<button
class="btn btn-secondary btn-md btn-block"
onclick="selectMethod('viral')"
>
선택하기
</button>
</div>
</div>
</div>
<!-- Compare Button -->
<button
class="btn btn-text btn-md btn-block"
style="margin-bottom: 24px;"
onclick="showCompareView()"
>
<span class="material-icons" style="margin-right: 8px;">compare_arrows</span>
옵션 비교하기
</button>
<!-- Next Button -->
<button
id="nextBtn"
class="btn btn-primary btn-lg btn-block"
style="margin-bottom: 32px;"
disabled
>
다음 (홍보문구)
</button>
</div>
</main>
</div>
<!-- Scripts -->
<script src="js/common.js"></script>
<script>
(function() {
'use strict';
const methodCards = document.querySelectorAll('.method-card');
const nextBtn = document.getElementById('nextBtn');
let selectedMethod = null;
// 참여 방법 상세 데이터
const methodDetails = {
simple: {
id: 'simple',
name: '간편형',
icon: '📱',
title: 'QR 코드 스캔',
difficulty: 1,
participationRate: 60,
revisitRate: 20,
viralEffect: 0,
benefits: ['빠른 참여', '매장 방문 불필요', '즉시 경품 확인'],
process: [
'1. QR 코드 스캔',
'2. 이벤트 페이지 자동 이동',
'3. 간단한 정보 입력',
'4. 즉시 경품 당첨 확인'
],
bestFor: ['신규고객 유치', '빠른 확산'],
tips: '매장 입구, 포스터, 전단지에 QR 코드 부착'
},
revisit: {
id: 'revisit',
name: '재방문 유도형',
icon: '🏪',
title: '매장 방문 + 리뷰',
difficulty: 2,
participationRate: 35,
revisitRate: 80,
viralEffect: 30,
benefits: ['높은 재방문율', '리뷰 축적', '충성고객 확보'],
process: [
'1. 매장 방문 인증 (QR 스캔 또는 GPS)',
'2. 제품 구매 또는 서비스 이용',
'3. 리뷰 작성 (별점 + 후기)',
'4. 경품 즉시 지급 또는 추첨'
],
bestFor: ['재방문 유도', '매출 증대'],
tips: '리뷰 작성 시 추가 혜택 제공으로 참여율 향상'
},
viral: {
id: 'viral',
name: '바이럴형',
icon: '📢',
title: 'SNS 공유 + 태그',
difficulty: 3,
participationRate: 25,
revisitRate: 15,
viralEffect: 150,
benefits: ['입소문 효과', '신규고객 유입', '브랜드 인지도 향상'],
process: [
'1. 이벤트 페이지 방문',
'2. SNS(인스타그램, 페이스북) 공유',
'3. 해시태그 및 매장 태그 필수',
'4. 공유 인증 후 경품 추첨'
],
bestFor: ['인지도 향상', '바이럴 마케팅'],
tips: '매력적인 이미지와 해시태그가 성공의 핵심'
}
};
// 상태 로드
window.AppState.load();
const event = window.AppState.currentEvent;
// 카드 클릭 이벤트
methodCards.forEach(card => {
card.addEventListener('click', function(e) {
// 버튼 클릭이 아닐 때만 상세 정보 표시
if (!e.target.closest('button')) {
const method = this.dataset.method;
showMethodDetail(method);
}
});
});
// 참여 방법 선택
window.selectMethod = function(method) {
selectedMethod = method;
// 모든 카드에서 selected 클래스 제거
methodCards.forEach(card => {
card.classList.remove('selected');
const check = card.querySelector('.method-check');
if (check) check.style.opacity = '0';
});
// 선택된 카드에 selected 클래스 추가
const selectedCard = document.querySelector(`[data-method="${method}"]`);
if (selectedCard) {
selectedCard.classList.add('selected');
const check = selectedCard.querySelector('.method-check');
if (check) check.style.opacity = '1';
}
// 다음 버튼 활성화
nextBtn.disabled = false;
// 선택 피드백
const details = methodDetails[method];
Toast.success(`${details.name}이(가) 선택되었습니다.`);
// 카드 애니메이션
if (selectedCard) {
selectedCard.style.transform = 'scale(1.02)';
setTimeout(() => {
selectedCard.style.transform = '';
}, 200);
}
};
// 참여 방법 상세 정보
function showMethodDetail(method) {
const details = methodDetails[method];
if (!details) return;
BottomSheet.show(`
<div style="padding: 8px 0;">
<div style="text-align: center; margin-bottom: 20px;">
<div style="font-size: 48px; margin-bottom: 8px;">${details.icon}</div>
<h3 class="h3">${details.name}</h3>
<p class="body-l" style="margin-top: 8px; color: var(--color-gray-700);">${details.title}</p>
</div>
<div style="margin-bottom: 20px;">
<h4 class="body-m" style="font-weight: 600; margin-bottom: 12px;">참여 과정</h4>
<div style="margin-left: 8px;">
${details.process.map(p => `
<div class="body-m" style="margin-bottom: 8px; padding-left: 8px; border-left: 3px solid var(--color-secondary-main);">
${p}
</div>
`).join('')}
</div>
</div>
<div style="margin-bottom: 20px;">
<h4 class="body-m" style="font-weight: 600; margin-bottom: 12px;">주요 혜택</h4>
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
${details.benefits.map(b => `
<span class="body-s" style="padding: 6px 12px; background: var(--color-secondary-light); border-radius: 16px; color: var(--color-secondary-main);">
${b}
</span>
`).join('')}
</div>
</div>
<div style="margin-bottom: 20px;">
<h4 class="body-m" style="font-weight: 600; margin-bottom: 12px;">추천 상황</h4>
<div class="body-m text-muted">
${details.bestFor.join(', ')}
</div>
</div>
<div class="card" style="padding: 16px; background: var(--color-primary-light);">
<div class="body-m" style="font-weight: 600; margin-bottom: 4px; color: var(--color-primary-main);">
💡 팁
</div>
<div class="body-s">${details.tips}</div>
</div>
</div>
`);
}
// 옵션 비교 뷰
window.showCompareView = function() {
const methods = Object.values(methodDetails);
Modal.show({
title: '참여방법 비교',
body: `
<div style="overflow-x: auto;">
<table style="width: 100%; border-collapse: collapse; font-size: 14px;">
<thead>
<tr style="background: var(--color-gray-100);">
<th style="padding: 12px; text-align: left; border-bottom: 2px solid var(--color-gray-300);">항목</th>
<th style="padding: 12px; text-align: center; border-bottom: 2px solid var(--color-gray-300);">간편형</th>
<th style="padding: 12px; text-align: center; border-bottom: 2px solid var(--color-gray-300);">재방문형</th>
<th style="padding: 12px; text-align: center; border-bottom: 2px solid var(--color-gray-300);">바이럴형</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 12px; border-bottom: 1px solid var(--color-gray-200);">난이도</td>
<td style="padding: 12px; text-align: center; border-bottom: 1px solid var(--color-gray-200);"></td>
<td style="padding: 12px; text-align: center; border-bottom: 1px solid var(--color-gray-200);">⭐⭐</td>
<td style="padding: 12px; text-align: center; border-bottom: 1px solid var(--color-gray-200);">⭐⭐⭐</td>
</tr>
<tr>
<td style="padding: 12px; border-bottom: 1px solid var(--color-gray-200);">참여율</td>
<td style="padding: 12px; text-align: center; border-bottom: 1px solid var(--color-gray-200); font-weight: 700; color: var(--color-success);">60%</td>
<td style="padding: 12px; text-align: center; border-bottom: 1px solid var(--color-gray-200);">35%</td>
<td style="padding: 12px; text-align: center; border-bottom: 1px solid var(--color-gray-200);">25%</td>
</tr>
<tr>
<td style="padding: 12px; border-bottom: 1px solid var(--color-gray-200);">재방문율</td>
<td style="padding: 12px; text-align: center; border-bottom: 1px solid var(--color-gray-200);">20%</td>
<td style="padding: 12px; text-align: center; border-bottom: 1px solid var(--color-gray-200); font-weight: 700; color: var(--color-success);">80%</td>
<td style="padding: 12px; text-align: center; border-bottom: 1px solid var(--color-gray-200);">15%</td>
</tr>
<tr>
<td style="padding: 12px;">바이럴 효과</td>
<td style="padding: 12px; text-align: center;">-</td>
<td style="padding: 12px; text-align: center;">30%</td>
<td style="padding: 12px; text-align: center; font-weight: 700; color: var(--color-success);">150%</td>
</tr>
</tbody>
</table>
</div>
`,
confirmText: '확인',
showCancel: false
});
};
// 다음 버튼
nextBtn.addEventListener('click', function() {
if (!selectedMethod) {
Toast.error('참여 방법을 선택해주세요.');
return;
}
// 참여 방법 정보 저장
event.participationMethod = methodDetails[selectedMethod];
window.AppState.save();
Loading.show('홍보 문구 생성 준비 중...');
setTimeout(() => {
window.location.href = '07-AI홍보문구생성.html';
}, 800);
});
console.log('AI참여방법설계 페이지 로드 완료');
})();
</script>
<style>
.method-card {
transition: all 0.3s ease;
}
.method-card .card {
border: 2px solid var(--color-gray-200);
}
.method-card.selected .card {
border: 3px solid var(--color-primary-main);
background: var(--color-primary-light);
box-shadow: 0 0 0 4px rgba(227, 30, 36, 0.1);
}
.method-card .card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
border-color: var(--color-secondary-main);
}
.method-card:active {
transform: scale(0.98);
}
@media (prefers-reduced-motion: reduce) {
.method-card,
.method-card .card,
.method-check {
transition: none;
}
}
</style>
</body>
</html>

View File

@ -0,0 +1,501 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="KT AI 기반 소상공인 이벤트 자동 생성 서비스 - AI홍보문구생성">
<title>AI 홍보문구 생성 - KT 이벤트 마케팅</title>
<!-- Styles -->
<link rel="stylesheet" href="css/common.css">
<!-- Fonts -->
<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>
<!-- Skip Link -->
<a href="#main-content" class="skip-link">본문으로 건너뛰기</a>
<!-- App Wrapper -->
<div class="app-wrapper">
<!-- Top App Bar -->
<header class="app-bar">
<button class="app-bar__back" aria-label="뒤로가기">
<span class="material-icons">arrow_back</span>
</button>
<h1 class="app-bar__title">AI 홍보문구 생성</h1>
</header>
<!-- Main Content -->
<main id="main-content" class="app-content">
<div class="container" style="max-width: 500px; margin: 0 auto; padding-top: 24px;">
<!-- Progress Section -->
<div style="margin-bottom: 32px;">
<div class="body-m" style="margin-bottom: 8px; font-weight: 600;">단계 5/6: 홍보문구 선택</div>
<div style="display: flex; gap: 4px;">
<div style="flex: 1; height: 4px; background: var(--color-primary-main); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-primary-main); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-primary-main); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-primary-main); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-primary-main); border-radius: 2px;"></div>
<div style="flex: 1; height: 4px; background: var(--color-gray-200); border-radius: 2px;"></div>
</div>
</div>
<!-- AI Generation Loading -->
<div id="loadingSection" style="text-align: center; padding: 40px 0;">
<div class="spinner" style="margin: 0 auto 24px;"></div>
<h2 class="h3" style="margin-bottom: 8px;">🤖 AI가 생성중입니다...</h2>
<p class="body-m text-muted">매장과 이벤트에 맞는 홍보 문구를 작성하고 있습니다</p>
<div id="progressBar" style="width: 100%; height: 8px; background: var(--color-gray-200); border-radius: 4px; margin: 24px 0; overflow: hidden;">
<div id="progressFill" style="height: 100%; background: var(--color-primary-main); width: 0%; transition: width 0.3s ease;"></div>
</div>
<div class="body-s text-muted" id="progressText">문구 생성 준비 중...</div>
</div>
<!-- Generated Messages (Hidden initially) -->
<div id="resultsSection" style="display: none; opacity: 0; transition: opacity 0.5s ease;">
<h2 class="h3" style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
<span>📝</span>
<span>생성된 홍보 문구</span>
</h2>
<div id="messagesList">
<!-- Messages will be dynamically added here -->
</div>
<!-- Regenerate Button -->
<button
class="btn btn-secondary btn-md btn-block"
style="margin-bottom: 24px;"
onclick="regenerateMessages()"
>
<span class="material-icons" style="margin-right: 8px;">refresh</span>
다시 생성하기
</button>
<!-- Next Button -->
<button
id="nextBtn"
class="btn btn-primary btn-lg btn-block"
style="margin-bottom: 32px;"
disabled
>
다음 (최종확인)
</button>
</div>
</div>
</main>
</div>
<!-- Scripts -->
<script src="js/common.js"></script>
<script>
(function() {
'use strict';
const loadingSection = document.getElementById('loadingSection');
const resultsSection = document.getElementById('resultsSection');
const progressFill = document.getElementById('progressFill');
const progressText = document.getElementById('progressText');
const messagesList = document.getElementById('messagesList');
const nextBtn = document.getElementById('nextBtn');
let generatedMessages = [];
let selectedMessage = null;
// 상태 로드
window.AppState.load();
const store = window.AppState.store;
const event = window.AppState.currentEvent;
// AI 생성 시뮬레이션
const generationSteps = [
{ progress: 20, text: '매장 정보 분석 중...' },
{ progress: 40, text: '이벤트 컨셉 도출 중...' },
{ progress: 60, text: '문구 패턴 학습 중...' },
{ progress: 80, text: '해시태그 생성 중...' },
{ progress: 100, text: '최종 문구 완성 중...' }
];
let currentStep = 0;
function updateProgress() {
if (currentStep < generationSteps.length) {
const step = generationSteps[currentStep];
progressFill.style.width = step.progress + '%';
progressText.textContent = step.text;
currentStep++;
const delay = 1000 + Math.random() * 1500; // 1~2.5초
setTimeout(updateProgress, delay);
} else {
setTimeout(showResults, 500);
}
}
// 홍보 문구 생성
function generateMessages() {
const storeName = store.name || '우리 가게';
const businessType = store.businessTypeName || '가게';
const prizeName = event.prize ? event.prize.name : '경품';
const purpose = event.purposeDetails ? event.purposeDetails.title : '이벤트';
// 5가지 톤앤매너 문구 생성
generatedMessages = [
{
id: Utils.generateId(),
version: 1,
tone: '친근한 톤',
message: `🎉 연말 대박 이벤트!\n${storeName}에서\n${prizeName}을(를) 공짜로!\n지금 바로 참여하세요 😊`,
hashtags: ['#' + store.address.split(' ')[1] + '맛집', '#이벤트', '#연말특가', '#' + businessType],
platform: 'Instagram',
style: 'friendly'
},
{
id: Utils.generateId(),
version: 2,
tone: '공식적인 톤',
message: `${storeName} 12월 프로모션을 시작합니다.\n선착순 한정으로\n${prizeName} 무료 증정 이벤트를 진행합니다.\n많은 관심과 참여 부탁드립니다.`,
hashtags: ['#' + storeName, '#프로모션', '#이벤트안내'],
platform: 'Blog',
style: 'formal'
},
{
id: Utils.generateId(),
version: 3,
tone: '긴급한 톤',
message: `⚡ 놓치면 후회!\n지금 ${storeName}에서\n${prizeName} 이벤트 진행 중!\n서두르세요! 선착순 마감 임박! 🔥`,
hashtags: ['#긴급', '#선착순', '#한정수량', '#' + businessType + '이벤트'],
platform: 'Instagram',
style: 'urgent'
},
{
id: Utils.generateId(),
version: 4,
tone: '감성적인 톤',
message: `올 한 해 수고 많으셨습니다 💙\n소중한 고객님께 감사의 마음을 담아\n${storeName}에서\n${prizeName} 이벤트를 준비했습니다.\n여러분의 행복한 연말을 응원합니다.`,
hashtags: ['#감사이벤트', '#연말감사', '#고객사랑', '#' + storeName],
platform: 'Kakao',
style: 'emotional'
},
{
id: Utils.generateId(),
version: 5,
tone: '유머러스한 톤',
message: `${businessType} 먹고 싶은데 돈은 아까운 당신!\n${storeName}이 해결해드립니다 😎\n${prizeName} 이벤트로\n지갑 걱정 끝! 배는 두둑!\n지금 바로 참여하GO! 🎁`,
hashtags: ['#' + businessType + '맛집', '#가성비', '#이벤트중', '#지금참여'],
platform: 'Instagram',
style: 'humorous'
}
];
return generatedMessages;
}
// 결과 표시
function showResults() {
loadingSection.style.display = 'none';
generateMessages();
renderMessages();
resultsSection.style.display = 'block';
setTimeout(() => {
resultsSection.style.opacity = '1';
}, 50);
Toast.success('홍보 문구가 생성되었습니다!');
}
// 메시지 렌더링
function renderMessages() {
messagesList.innerHTML = generatedMessages.map(msg => `
<div class="message-card ${selectedMessage && selectedMessage.id === msg.id ? 'selected' : ''}" data-message-id="${msg.id}">
<div class="card" style="padding: 16px; margin-bottom: 16px; cursor: pointer; transition: all 0.3s ease;">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px;">
<div>
<div class="body-m" style="font-weight: 700; margin-bottom: 4px;">
버전 ${msg.version}: ${msg.tone}
</div>
<div class="body-s text-muted">
추천 플랫폼: ${msg.platform}
</div>
</div>
${selectedMessage && selectedMessage.id === msg.id ? `
<span class="material-icons" style="color: var(--color-primary-main); font-size: 28px;">
check_circle
</span>
` : ''}
</div>
<div class="message-preview" style="padding: 12px; background: var(--color-gray-50); border-radius: var(--radius-md); margin-bottom: 12px;">
<div class="body-m" style="white-space: pre-line; line-height: 1.6;">
${msg.message}
</div>
</div>
<div style="margin-bottom: 12px;">
<div class="body-s text-muted" style="margin-bottom: 4px;">해시태그:</div>
<div style="display: flex; flex-wrap: wrap; gap: 6px;">
${msg.hashtags.map(tag => `
<span class="body-s" style="padding: 4px 10px; background: var(--color-secondary-light); border-radius: 12px; color: var(--color-secondary-main);">
${tag}
</span>
`).join('')}
</div>
</div>
<div style="display: flex; gap: 8px;">
<button
class="btn btn-secondary btn-sm"
style="flex: 1;"
onclick="selectMessage('${msg.id}')"
>
선택
</button>
<button
class="btn btn-text btn-sm"
style="flex: 1;"
onclick="editMessage('${msg.id}')"
>
편집
</button>
<button
class="btn btn-text btn-sm"
onclick="previewMessage('${msg.id}')"
>
<span class="material-icons" style="font-size: 18px;">visibility</span>
</button>
</div>
</div>
</div>
`).join('');
}
// 메시지 선택
window.selectMessage = function(messageId) {
const message = generatedMessages.find(m => m.id === messageId);
if (!message) return;
selectedMessage = message;
renderMessages();
nextBtn.disabled = false;
Toast.success(`버전 ${message.version}이(가) 선택되었습니다.`);
// 카드 애니메이션
const card = document.querySelector(`[data-message-id="${messageId}"]`);
if (card) {
card.style.transform = 'scale(1.02)';
setTimeout(() => {
card.style.transform = '';
}, 200);
}
};
// 메시지 편집
window.editMessage = function(messageId) {
const message = generatedMessages.find(m => m.id === messageId);
if (!message) return;
Modal.show({
title: '홍보문구 편집',
body: `
<div class="form-group">
<label class="form-label">문구</label>
<textarea
id="editMessageText"
class="form-input"
rows="6"
style="resize: vertical;"
>${message.message}</textarea>
</div>
<div class="form-group">
<label class="form-label">해시태그 (쉼표로 구분)</label>
<input
type="text"
id="editHashtags"
class="form-input"
value="${message.hashtags.join(', ')}"
>
</div>
<div class="body-s text-muted">
💡 팁: 해시태그는 3~5개가 적당하며, 지역명과 업종을 포함하세요.
</div>
`,
confirmText: '저장',
onConfirm: function() {
const newText = document.getElementById('editMessageText').value.trim();
const hashtagsText = document.getElementById('editHashtags').value.trim();
if (!newText) {
Toast.error('문구를 입력해주세요.');
return;
}
message.message = newText;
message.hashtags = hashtagsText.split(',').map(tag => tag.trim()).filter(tag => tag);
renderMessages();
Toast.success('문구가 수정되었습니다.');
}
});
};
// 메시지 미리보기
window.previewMessage = function(messageId) {
const message = generatedMessages.find(m => m.id === messageId);
if (!message) return;
const previewHTML = getPlatformPreview(message);
BottomSheet.show(`
<div style="padding: 8px 0;">
<h3 class="h3" style="margin-bottom: 16px; text-align: center;">
${message.platform} 미리보기
</h3>
${previewHTML}
<div class="body-s text-muted" style="margin-top: 16px; text-align: center;">
* 실제 플랫폼과 다소 차이가 있을 수 있습니다
</div>
</div>
`);
};
// 플랫폼별 미리보기 생성
function getPlatformPreview(message) {
const storeName = store.name || '우리 가게';
if (message.platform === 'Instagram') {
return `
<div style="max-width: 360px; margin: 0 auto; border: 1px solid var(--color-gray-300); border-radius: var(--radius-md); overflow: hidden;">
<div style="padding: 12px; display: flex; align-items: center; gap: 12px; border-bottom: 1px solid var(--color-gray-200);">
<div style="width: 32px; height: 32px; background: var(--color-primary-main); border-radius: 50%;"></div>
<div class="body-m" style="font-weight: 600;">${storeName}</div>
</div>
<div style="aspect-ratio: 1; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);"></div>
<div style="padding: 12px;">
<div class="body-m" style="white-space: pre-line; line-height: 1.6; margin-bottom: 8px;">
${message.message}
</div>
<div class="body-s" style="color: var(--color-secondary-main);">
${message.hashtags.join(' ')}
</div>
</div>
</div>
`;
} else if (message.platform === 'Blog') {
return `
<div style="max-width: 400px; margin: 0 auto; border: 1px solid var(--color-gray-300); border-radius: var(--radius-md); padding: 20px; background: white;">
<h4 class="h3" style="margin-bottom: 16px;">이벤트 안내</h4>
<div class="body-m" style="white-space: pre-line; line-height: 1.8;">
${message.message}
</div>
</div>
`;
} else { // Kakao
return `
<div style="max-width: 360px; margin: 0 auto; background: #FEE500; border-radius: var(--radius-md); padding: 16px;">
<div class="body-m" style="font-weight: 700; margin-bottom: 12px;">${storeName}</div>
<div class="body-m" style="white-space: pre-line; line-height: 1.6; margin-bottom: 12px;">
${message.message}
</div>
<div class="body-s" style="color: var(--color-gray-700);">
${message.hashtags.join(' ')}
</div>
</div>
`;
}
}
// 다시 생성하기
window.regenerateMessages = function() {
Modal.confirm(
'문구 다시 생성',
'새로운 톤앤매너로 홍보 문구를 다시 생성하시겠습니까?',
function() {
resultsSection.style.display = 'none';
resultsSection.style.opacity = '0';
loadingSection.style.display = 'block';
currentStep = 0;
progressFill.style.width = '0%';
progressText.textContent = '문구 생성 준비 중...';
selectedMessage = null;
nextBtn.disabled = true;
setTimeout(updateProgress, 500);
}
);
};
// 다음 버튼
nextBtn.addEventListener('click', function() {
if (!selectedMessage) {
Toast.error('홍보 문구를 선택해주세요.');
return;
}
// 홍보 문구 저장
event.promotionMessage = selectedMessage;
window.AppState.save();
Loading.show('최종 기획안 준비 중...');
setTimeout(() => {
window.location.href = '08-이벤트기획안승인.html';
}, 800);
});
// 페이지 로드 시 생성 시작
setTimeout(updateProgress, 1000);
console.log('AI홍보문구생성 페이지 로드 완료');
})();
</script>
<style>
.message-card {
transition: all 0.3s ease;
}
.message-card .card {
border: 2px solid var(--color-gray-200);
}
.message-card.selected .card {
border: 3px solid var(--color-primary-main);
background: var(--color-primary-light);
box-shadow: 0 0 0 4px rgba(227, 30, 36, 0.1);
}
.message-card .card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.message-preview {
transition: all 0.2s ease;
}
.message-card:hover .message-preview {
background: var(--color-gray-100);
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
#progressText {
animation: pulse 2s ease-in-out infinite;
}
</style>
</body>
</html>

View File

@ -0,0 +1,614 @@
<!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="css/common.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Pretendard:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<!-- Skip Navigation -->
<a href="#main-content" class="skip-link">본문으로 건너뛰기</a>
<!-- Header -->
<header class="header" role="banner">
<div class="header-content">
<button type="button"
class="btn-icon"
onclick="window.history.back()"
aria-label="이전 페이지로 돌아가기">
<span class="material-icons">arrow_back</span>
</button>
<h1 class="header-title">이벤트 기획안 승인</h1>
<div style="width: 40px;"></div>
</div>
</header>
<!-- Progress Bar -->
<div class="progress-container" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar" style="width: 100%;"></div>
<div class="progress-label">6/6 단계</div>
</div>
<!-- Main Content -->
<main id="main-content" class="container" role="main">
<!-- Time Elapsed -->
<div class="alert alert-success" role="status" style="margin-bottom: var(--spacing-l);">
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
<span class="material-icons" style="font-size: 20px;">schedule</span>
<span class="body-m" style="font-weight: 600;">
소요 시간: <span id="timeElapsed">0초</span>
</span>
</div>
<p class="body-s" style="margin: var(--spacing-xs) 0 0 28px; color: var(--color-success-dark);">
목표 시간(10초) 대비 <span id="timeComparison" style="font-weight: 600;"></span>
</p>
</div>
<!-- Plan Summary Section -->
<section class="section" aria-labelledby="summary-title">
<h2 id="summary-title" class="h3">기획안 요약</h2>
<!-- Store Info -->
<div class="card">
<div class="card-body">
<div style="display: flex; align-items: center; gap: var(--spacing-s); margin-bottom: var(--spacing-m);">
<span class="material-icons" style="color: var(--color-primary-main);">store</span>
<h3 class="h4" id="storeName">매장명</h3>
</div>
<div class="info-grid">
<div class="info-item">
<span class="info-label">업종</span>
<span class="info-value" id="businessType">음식점</span>
</div>
<div class="info-item">
<span class="info-label">위치</span>
<span class="info-value" id="location">서울 강남구</span>
</div>
</div>
</div>
</div>
<!-- Event Purpose -->
<div class="card">
<div class="card-body">
<div style="display: flex; align-items: center; gap: var(--spacing-s); margin-bottom: var(--spacing-m);">
<span class="material-icons" style="color: var(--color-secondary-main);">flag</span>
<h3 class="h4">이벤트 목적</h3>
</div>
<div class="tag tag-secondary" id="purposeTag">신규 고객 유치</div>
</div>
</div>
<!-- Prize Info -->
<div class="card">
<div class="card-body">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--spacing-m);">
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
<span class="material-icons" style="color: var(--color-warning);">card_giftcard</span>
<h3 class="h4">경품 정보</h3>
</div>
<button type="button" class="btn-text" onclick="goToStep('05-AI경품추천.html')" aria-label="경품 정보 수정하기">
<span class="material-icons">edit</span>
</button>
</div>
<div id="prizeList"></div>
</div>
</div>
<!-- Participation Method -->
<div class="card">
<div class="card-body">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--spacing-m);">
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
<span class="material-icons" style="color: var(--color-success);">how_to_reg</span>
<h3 class="h4">참여 방법</h3>
</div>
<button type="button" class="btn-text" onclick="goToStep('06-AI참여방법설계.html')" aria-label="참여 방법 수정하기">
<span class="material-icons">edit</span>
</button>
</div>
<div style="padding: var(--spacing-m); background: var(--color-gray-50); border-radius: var(--radius-md);">
<div class="body-m" style="font-weight: 600; margin-bottom: var(--spacing-xs);" id="methodName">간편 QR 참여</div>
<div class="body-s" style="color: var(--color-gray-600);" id="methodDesc">매장 방문 시 QR 코드 스캔으로 즉시 참여</div>
<div style="display: flex; gap: var(--spacing-m); margin-top: var(--spacing-m);">
<div>
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">예상 참여율</div>
<div class="body-m" style="font-weight: 600; color: var(--color-primary-main);" id="methodParticipation">60%</div>
</div>
<div>
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">재방문율</div>
<div class="body-m" style="font-weight: 600; color: var(--color-success);" id="methodRevisit">20%</div>
</div>
</div>
</div>
</div>
</div>
<!-- Promotion Message -->
<div class="card">
<div class="card-body">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--spacing-m);">
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
<span class="material-icons" style="color: var(--color-info);">campaign</span>
<h3 class="h4">홍보 문구</h3>
</div>
<button type="button" class="btn-text" onclick="goToStep('07-AI홍보문구생성.html')" aria-label="홍보 문구 수정하기">
<span class="material-icons">edit</span>
</button>
</div>
<div style="padding: var(--spacing-m); background: var(--color-gray-50); border-radius: var(--radius-md);">
<div class="body-s" style="white-space: pre-line;" id="promotionMessage">홍보 문구가 여기에 표시됩니다.</div>
<div style="margin-top: var(--spacing-s); display: flex; flex-wrap: wrap; gap: var(--spacing-xs);" id="hashtags"></div>
</div>
</div>
</div>
</section>
<!-- Cost Summary -->
<section class="section" aria-labelledby="cost-title">
<h2 id="cost-title" class="h3">예상 비용</h2>
<div class="card">
<div class="card-body">
<div class="info-grid">
<div class="info-item">
<span class="info-label">경품 총 비용</span>
<span class="info-value" style="color: var(--color-primary-main); font-weight: 600;" id="prizeCost">0원</span>
</div>
<div class="info-item">
<span class="info-label">플랫폼 이용료</span>
<span class="info-value" style="color: var(--color-gray-600);">무료</span>
</div>
</div>
<div style="border-top: 1px solid var(--color-gray-300); margin-top: var(--spacing-m); padding-top: var(--spacing-m);">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span class="body-l" style="font-weight: 600;">총 예상 비용</span>
<span class="h3" style="color: var(--color-primary-main);" id="totalCost">0원</span>
</div>
</div>
</div>
</div>
</section>
<!-- Expected Effects -->
<section class="section" aria-labelledby="effects-title">
<h2 id="effects-title" class="h3">예상 효과</h2>
<div class="card">
<div class="card-body">
<div class="metric-grid">
<div class="metric-item">
<div class="metric-icon" style="background: var(--color-primary-lightest);">
<span class="material-icons" style="color: var(--color-primary-main);">people</span>
</div>
<div>
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">예상 참여자</div>
<div class="h4" id="expectedParticipants">0명</div>
</div>
</div>
<div class="metric-item">
<div class="metric-icon" style="background: var(--color-success-lightest);">
<span class="material-icons" style="color: var(--color-success);">trending_up</span>
</div>
<div>
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">예상 ROI</div>
<div class="h4" style="color: var(--color-success);" id="expectedROI">0%</div>
</div>
</div>
<div class="metric-item">
<div class="metric-icon" style="background: var(--color-warning-lightest);">
<span class="material-icons" style="color: var(--color-warning);">monetization_on</span>
</div>
<div>
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">예상 매출 증대</div>
<div class="h4" style="color: var(--color-warning);" id="expectedRevenue">0원</div>
</div>
</div>
<div class="metric-item">
<div class="metric-icon" style="background: var(--color-info-lightest);">
<span class="material-icons" style="color: var(--color-info);">repeat</span>
</div>
<div>
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">재방문 고객</div>
<div class="h4" style="color: var(--color-info);" id="expectedRevisits">0명</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- AI Insights -->
<section class="section" aria-labelledby="insights-title">
<h2 id="insights-title" class="h3">AI 분석 및 조언</h2>
<div class="alert alert-info" role="status">
<div style="display: flex; gap: var(--spacing-s);">
<span class="material-icons" style="font-size: 20px;">lightbulb</span>
<div style="flex: 1;">
<div class="body-m" style="font-weight: 600; margin-bottom: var(--spacing-xs);">최적화 제안</div>
<ul class="body-s" style="margin: 0; padding-left: var(--spacing-l); color: var(--color-info-dark);" id="aiInsights">
<li>현재 기획안은 목적에 부합하는 최적의 조합입니다.</li>
</ul>
</div>
</div>
</div>
</section>
</main>
<!-- Fixed Bottom Button -->
<div class="fixed-bottom-button">
<button type="button"
class="btn btn-outline btn-lg"
onclick="window.history.back()"
style="flex: 1;">
이전
</button>
<button type="button"
class="btn btn-primary btn-lg"
onclick="approvePlan()"
style="flex: 2;">
승인 및 콘텐츠 제작
</button>
</div>
<script src="js/common.js"></script>
<script>
(function() {
'use strict';
// Load saved state
const eventData = AppState.currentEvent || {};
const storeData = AppState.store || {};
const userData = AppState.user || {};
// Calculate time elapsed (simulated)
const startTime = eventData.startTime || Date.now();
const elapsedSeconds = Math.floor((Date.now() - startTime) / 1000);
// Initialize page
document.addEventListener('DOMContentLoaded', function() {
loadPlanSummary();
calculateEffects();
generateAIInsights();
});
/**
* Load plan summary from AppState
*/
function loadPlanSummary() {
// Store info
document.getElementById('storeName').textContent = storeData.name || '매장명';
document.getElementById('businessType').textContent = getBusinessTypeLabel(storeData.businessType) || '음식점';
document.getElementById('location').textContent = storeData.location || '서울';
// Event purpose
const purposeMap = {
new_customer: '신규 고객 유치',
revisit: '재방문 유도',
sales: '매출 증대',
brand: '브랜드 인지도 향상'
};
document.getElementById('purposeTag').textContent = purposeMap[eventData.purpose] || '신규 고객 유치';
// Prize list
loadPrizeList();
// Participation method
loadParticipationMethod();
// Promotion message
loadPromotionMessage();
// Time elapsed
updateTimeElapsed();
}
/**
* Load prize list
*/
function loadPrizeList() {
const prizes = eventData.prizes || [];
const prizeListEl = document.getElementById('prizeList');
if (prizes.length === 0) {
prizeListEl.innerHTML = '<p class="body-s" style="color: var(--color-gray-600);">등록된 경품이 없습니다.</p>';
return;
}
let totalCost = 0;
let html = '<div style="display: flex; flex-direction: column; gap: var(--spacing-s);">';
prizes.forEach(function(prize, index) {
const prizeCost = prize.price * prize.quantity;
totalCost += prizeCost;
html += `
<div style="display: flex; justify-content: space-between; align-items: center; padding: var(--spacing-s); background: var(--color-gray-50); border-radius: var(--radius-sm);">
<div style="flex: 1;">
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">${prize.name}</div>
<div class="caption" style="color: var(--color-gray-600);">
${prize.quantity}개 × ${formatCurrency(prize.price)}
</div>
</div>
<div class="body-m" style="font-weight: 600; color: var(--color-primary-main);">
${formatCurrency(prizeCost)}
</div>
</div>
`;
});
html += '</div>';
prizeListEl.innerHTML = html;
// Update total cost
document.getElementById('prizeCost').textContent = formatCurrency(totalCost);
document.getElementById('totalCost').textContent = formatCurrency(totalCost);
}
/**
* Load participation method
*/
function loadParticipationMethod() {
const method = eventData.participationMethod || {};
const methodMap = {
simple: { name: '간편 QR 참여', desc: '매장 방문 시 QR 코드 스캔으로 즉시 참여' },
revisit: { name: '재방문+리뷰 참여', desc: '매장 재방문 후 리뷰 작성으로 참여' },
viral: { name: '바이럴 SNS 참여', desc: 'SNS 공유 및 친구 태그로 참여' }
};
const selectedMethod = methodMap[method.type] || methodMap.simple;
document.getElementById('methodName').textContent = selectedMethod.name;
document.getElementById('methodDesc').textContent = selectedMethod.desc;
document.getElementById('methodParticipation').textContent = (method.participationRate || 60) + '%';
document.getElementById('methodRevisit').textContent = (method.revisitRate || 20) + '%';
}
/**
* Load promotion message
*/
function loadPromotionMessage() {
const message = eventData.promotionMessage || {};
const messageEl = document.getElementById('promotionMessage');
const hashtagsEl = document.getElementById('hashtags');
if (message.text) {
messageEl.textContent = message.text;
} else {
messageEl.textContent = '홍보 문구가 아직 생성되지 않았습니다.';
}
if (message.hashtags && message.hashtags.length > 0) {
hashtagsEl.innerHTML = message.hashtags.map(function(tag) {
return `<span class="tag tag-sm" style="background: var(--color-secondary-lightest); color: var(--color-secondary-main);">${tag}</span>`;
}).join('');
} else {
hashtagsEl.innerHTML = '';
}
}
/**
* Update time elapsed
*/
function updateTimeElapsed() {
const elapsed = elapsedSeconds;
const target = 10;
document.getElementById('timeElapsed').textContent = elapsed + '초';
if (elapsed <= target) {
document.getElementById('timeComparison').textContent = '목표 달성! 🎉';
document.getElementById('timeComparison').style.color = 'var(--color-success)';
} else {
const diff = elapsed - target;
document.getElementById('timeComparison').textContent = `${diff}초 초과`;
document.getElementById('timeComparison').style.color = 'var(--color-warning)';
}
}
/**
* Calculate expected effects
*/
function calculateEffects() {
const prizes = eventData.prizes || [];
const method = eventData.participationMethod || {};
const budget = eventData.budget || 0;
// Calculate total prize quantity
let totalPrizes = 0;
prizes.forEach(function(prize) {
totalPrizes += prize.quantity;
});
// Expected participants based on participation rate
const participationRate = method.participationRate || 60;
const expectedParticipants = Math.round(totalPrizes * (100 / participationRate) * 1.2);
// Expected revenue (average customer value × participants × conversion rate)
const avgCustomerValue = getAvgCustomerValue(storeData.businessType);
const conversionRate = getConversionRate(eventData.purpose);
const expectedRevenue = Math.round(expectedParticipants * avgCustomerValue * conversionRate);
// Expected ROI
const totalCost = prizes.reduce(function(sum, prize) {
return sum + (prize.price * prize.quantity);
}, 0);
const roi = totalCost > 0 ? Math.round((expectedRevenue - totalCost) / totalCost * 100) : 0;
// Expected revisits
const revisitRate = method.revisitRate || 20;
const expectedRevisits = Math.round(expectedParticipants * revisitRate / 100);
// Update display
document.getElementById('expectedParticipants').textContent = formatNumber(expectedParticipants) + '명';
document.getElementById('expectedROI').textContent = roi + '%';
document.getElementById('expectedRevenue').textContent = formatCurrency(expectedRevenue);
document.getElementById('expectedRevisits').textContent = formatNumber(expectedRevisits) + '명';
}
/**
* Generate AI insights
*/
function generateAIInsights() {
const method = eventData.participationMethod || {};
const purpose = eventData.purpose || 'new_customer';
const budget = eventData.budget || 0;
const prizes = eventData.prizes || [];
const insights = [];
// Budget optimization
if (budget < 100000) {
insights.push('예산 규모가 작습니다. SNS 바이럴 참여 방식으로 변경하면 비용 대비 효과를 높일 수 있습니다.');
} else if (budget > 1000000) {
insights.push('예산이 충분합니다. 경품 종류를 다양화하면 더 많은 고객층을 유치할 수 있습니다.');
}
// Method optimization
if (purpose === 'revisit' && method.type !== 'revisit') {
insights.push('재방문 유도가 목적이라면 "재방문+리뷰" 참여 방식이 더 효과적입니다.');
}
if (purpose === 'brand' && method.type !== 'viral') {
insights.push('브랜드 인지도 향상이 목적이라면 "바이럴 SNS" 참여 방식이 더 효과적입니다.');
}
// Prize optimization
if (prizes.length === 1) {
insights.push('경품을 1등급, 2등급으로 나누면 참여율을 높일 수 있습니다.');
}
// Seasonal insights
const month = new Date().getMonth() + 1;
if (month === 12 || month === 1) {
insights.push('연말연시 시즌입니다. "따뜻한 겨울" 테마를 추가하면 공감도가 높아집니다.');
} else if (month >= 6 && month <= 8) {
insights.push('여름 시즌입니다. "시원한 여름" 테마를 추가하면 관심도가 높아집니다.');
}
// Default message
if (insights.length === 0) {
insights.push('현재 기획안은 목적과 예산에 부합하는 최적의 조합입니다.');
insights.push('콘텐츠 제작 단계로 진행하시면 이벤트 홍보물을 생성할 수 있습니다.');
}
// Update display
const insightsEl = document.getElementById('aiInsights');
insightsEl.innerHTML = insights.map(function(insight) {
return '<li>' + insight + '</li>';
}).join('');
}
/**
* Get average customer value by business type
*/
function getAvgCustomerValue(businessType) {
const values = {
restaurant: 25000,
cafe: 8000,
retail: 50000,
beauty: 80000,
fitness: 150000,
education: 200000
};
return values[businessType] || 30000;
}
/**
* Get conversion rate by purpose
*/
function getConversionRate(purpose) {
const rates = {
new_customer: 0.15,
revisit: 0.3,
sales: 0.25,
brand: 0.1
};
return rates[purpose] || 0.2;
}
/**
* Get business type label
*/
function getBusinessTypeLabel(type) {
const labels = {
restaurant: '음식점',
cafe: '카페',
retail: '소매점',
beauty: '미용/뷰티',
fitness: '피트니스',
education: '교육'
};
return labels[type] || '기타';
}
/**
* Navigate to specific step for editing
*/
window.goToStep = function(filename) {
if (confirm('해당 단계로 이동하시겠습니까? 현재 내용은 저장됩니다.')) {
AppState.save();
window.location.href = filename;
}
};
/**
* Approve plan and proceed to content creation
*/
window.approvePlan = function() {
// Validate plan completeness
if (!eventData.purpose) {
Toast.error('이벤트 목적이 설정되지 않았습니다.');
return;
}
if (!eventData.prizes || eventData.prizes.length === 0) {
Toast.error('경품이 설정되지 않았습니다.');
return;
}
if (!eventData.participationMethod) {
Toast.error('참여 방법이 설정되지 않았습니다.');
return;
}
if (!eventData.promotionMessage) {
Toast.error('홍보 문구가 생성되지 않았습니다.');
return;
}
// Show confirmation
if (confirm('이벤트 기획안을 승인하고 콘텐츠 제작을 시작하시겠습니까?')) {
// Update event status
eventData.status = 'approved';
eventData.approvedAt = new Date().toISOString();
AppState.currentEvent = eventData;
AppState.save();
// Show success message
Toast.success('기획안이 승인되었습니다!');
// Navigate to content creation (AI image generation)
setTimeout(function() {
window.location.href = '10-AI영상제작.html';
}, 1000);
}
};
/**
* Format number with commas
*/
function formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
/**
* Format currency
*/
function formatCurrency(amount) {
return formatNumber(amount) + '원';
}
})();
</script>
</body>
</html>

View File

@ -0,0 +1,648 @@
<!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="css/common.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Pretendard:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<!-- Skip Navigation -->
<a href="#main-content" class="skip-link">본문으로 건너뛰기</a>
<!-- Header -->
<header class="header" role="banner">
<div class="header-content">
<button type="button"
class="btn-icon"
onclick="window.history.back()"
aria-label="이전 페이지로 돌아가기">
<span class="material-icons">arrow_back</span>
</button>
<h1 class="header-title">AI 영상 제작</h1>
<div style="width: 40px;"></div>
</div>
</header>
<!-- Progress Bar -->
<div class="progress-container" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar" style="width: 40%;"></div>
<div class="progress-label">콘텐츠 2/5: 영상</div>
</div>
<!-- Main Content -->
<main id="main-content" class="container" role="main">
<!-- Settings Section -->
<section class="section" aria-labelledby="settings-title" id="settingsSection">
<h2 id="settings-title" class="h3">영상 설정</h2>
<!-- Background Music -->
<div class="card">
<div class="card-body">
<label class="form-label" for="musicSelect">배경 음악</label>
<div style="display: flex; flex-direction: column; gap: var(--spacing-s);">
<button type="button"
class="music-option"
data-music="upbeat1"
onclick="selectMusic('upbeat1')"
aria-pressed="true">
<span class="material-icons" style="color: var(--color-primary-main);">music_note</span>
<span style="flex: 1; text-align: left;">경쾌한 음악 1</span>
<span class="material-icons play-icon" onclick="playMusicPreview('upbeat1', event)">play_circle</span>
</button>
<button type="button"
class="music-option"
data-music="upbeat2"
onclick="selectMusic('upbeat2')"
aria-pressed="false">
<span class="material-icons" style="color: var(--color-primary-main);">music_note</span>
<span style="flex: 1; text-align: left;">신나는 음악 2</span>
<span class="material-icons play-icon" onclick="playMusicPreview('upbeat2', event)">play_circle</span>
</button>
<button type="button"
class="music-option"
data-music="calm1"
onclick="selectMusic('calm1')"
aria-pressed="false">
<span class="material-icons" style="color: var(--color-primary-main);">music_note</span>
<span style="flex: 1; text-align: left;">차분한 음악 3</span>
<span class="material-icons play-icon" onclick="playMusicPreview('calm1', event)">play_circle</span>
</button>
</div>
</div>
</div>
<!-- Text Overlay -->
<div class="card">
<div class="card-body">
<label class="form-label">텍스트 오버레이</label>
<div style="padding: var(--spacing-m); background: var(--color-gray-50); border-radius: var(--radius-md); margin-bottom: var(--spacing-s);">
<p class="body-m" id="overlayText" style="white-space: pre-line; min-height: 60px;">
🎉 연말 대박 이벤트!
</p>
</div>
<button type="button"
class="btn btn-outline btn-sm"
onclick="editOverlayText()">
<span class="material-icons" style="font-size: 18px;">edit</span>
<span>편집하기</span>
</button>
</div>
</div>
<!-- Resolution Selection -->
<div class="card">
<div class="card-body">
<label class="form-label">해상도 선택</label>
<p class="body-s" style="color: var(--color-gray-600); margin-bottom: var(--spacing-m);">
선택한 채널별로 영상이 생성됩니다.
</p>
<div style="display: flex; flex-direction: column; gap: var(--spacing-s);">
<label class="checkbox-card">
<input type="checkbox"
id="snsResolution"
checked
onchange="toggleResolution(this)">
<div class="checkbox-card-content">
<div>
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">SNS용 (정사각형)</div>
<div class="caption" style="color: var(--color-gray-600);">1080x1080 • Instagram, Facebook</div>
</div>
</div>
</label>
<label class="checkbox-card">
<input type="checkbox"
id="tvResolution"
checked
onchange="toggleResolution(this)">
<div class="checkbox-card-content">
<div>
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">우리동네TV용 (16:9)</div>
<div class="caption" style="color: var(--color-gray-600);">1920x1080 • Full HD</div>
</div>
</div>
</label>
</div>
</div>
</div>
<!-- Start Button -->
<button type="button"
class="btn btn-primary btn-lg"
onclick="startVideoCreation()"
style="width: 100%;">
<span class="material-icons">smart_display</span>
<span>AI 영상 제작 시작</span>
</button>
</section>
<!-- Progress Section (Hidden initially) -->
<section class="section" id="progressSection" style="display: none;" aria-live="polite">
<div class="card">
<div class="card-body" style="text-align: center; padding: var(--spacing-xl);">
<div class="loading-animation" style="margin-bottom: var(--spacing-l);">
<span class="material-icons" style="font-size: 64px; color: var(--color-primary-main); animation: pulse 1.5s ease-in-out infinite;">
movie_creation
</span>
</div>
<h3 class="h4" style="margin-bottom: var(--spacing-s);">🎬 AI가 영상 제작중...</h3>
<p class="body-s" style="color: var(--color-gray-600); margin-bottom: var(--spacing-l);" id="progressMessage">
이미지를 분석하고 있습니다...
</p>
<div class="progress-bar-container" style="margin-bottom: var(--spacing-m);">
<div class="progress-bar-fill" id="videoProgressBar" style="width: 0%;"></div>
</div>
<div class="body-m" style="font-weight: 600; color: var(--color-primary-main); margin-bottom: var(--spacing-xs);">
<span id="progressPercent">0</span>%
</div>
<p class="caption" style="color: var(--color-gray-600);">
예상 소요: <span id="remainingTime">3분</span>
</p>
</div>
</div>
</section>
<!-- Result Section (Hidden initially) -->
<section class="section" id="resultSection" style="display: none;" aria-labelledby="result-title">
<h2 id="result-title" class="h3">생성 완료</h2>
<!-- Video Preview -->
<div class="card">
<div class="card-body">
<div style="aspect-ratio: 16/9; background: var(--color-gray-900); border-radius: var(--radius-md); margin-bottom: var(--spacing-m); display: flex; align-items: center; justify-content: center; position: relative; overflow: hidden;">
<button type="button"
class="video-play-button"
onclick="playVideoPreview()"
aria-label="영상 미리보기 재생">
<span class="material-icons" style="font-size: 48px; color: white;">play_circle</span>
</button>
<div class="video-thumbnail" id="videoThumbnail" style="position: absolute; width: 100%; height: 100%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center;">
<span class="material-icons" style="font-size: 64px; color: white; opacity: 0.5;">movie</span>
</div>
</div>
<div class="body-s" style="color: var(--color-gray-600); text-align: center; margin-bottom: var(--spacing-m);">
15초 홍보 영상
</div>
<div style="padding: var(--spacing-m); background: var(--color-gray-50); border-radius: var(--radius-md);">
<div class="body-s" style="color: var(--color-gray-700);">
<div style="display: flex; align-items: center; gap: var(--spacing-xs); margin-bottom: var(--spacing-xs);">
<span class="material-icons" style="font-size: 16px; color: var(--color-success);">check_circle</span>
<span id="videoResolutions">SNS용, 우리동네TV용 영상 생성완료</span>
</div>
<div style="display: flex; align-items: center; gap: var(--spacing-xs);">
<span class="material-icons" style="font-size: 16px; color: var(--color-success);">check_circle</span>
<span>배경 음악 및 텍스트 오버레이 적용</span>
</div>
</div>
</div>
</div>
</div>
<!-- Action Buttons -->
<div style="display: flex; gap: var(--spacing-s);">
<button type="button"
class="btn btn-outline btn-lg"
onclick="downloadVideo()"
style="flex: 1;">
<span class="material-icons">download</span>
<span>다운로드</span>
</button>
<button type="button"
class="btn btn-outline btn-lg"
onclick="recreateVideo()"
style="flex: 1;">
<span class="material-icons">refresh</span>
<span>다시제작</span>
</button>
</div>
</section>
</main>
<!-- Fixed Bottom Button (for next step) -->
<div class="fixed-bottom-button" id="nextButton" style="display: none;">
<button type="button"
class="btn btn-primary btn-lg"
onclick="goToNext()">
다음 (SNS콘텐츠)
</button>
</div>
<!-- Text Edit Modal -->
<div id="textEditModal" class="modal" role="dialog" aria-labelledby="modal-title" aria-modal="true" style="display: none;">
<div class="modal-content">
<div class="modal-header">
<h3 id="modal-title" class="h4">텍스트 편집</h3>
<button type="button"
class="btn-icon"
onclick="closeTextEditModal()"
aria-label="닫기">
<span class="material-icons">close</span>
</button>
</div>
<div class="modal-body">
<label class="form-label" for="textInput">영상에 표시될 텍스트</label>
<textarea id="textInput"
class="form-input"
rows="4"
maxlength="100"
placeholder="영상에 표시될 텍스트를 입력하세요 (최대 100자)"
style="resize: vertical; min-height: 100px;"></textarea>
<div class="caption" style="color: var(--color-gray-600); margin-top: var(--spacing-xs); text-align: right;">
<span id="textCount">0</span>/100자
</div>
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-outline"
onclick="closeTextEditModal()">
취소
</button>
<button type="button"
class="btn btn-primary"
onclick="saveOverlayText()">
저장
</button>
</div>
</div>
</div>
<script src="js/common.js"></script>
<script>
(function() {
'use strict';
let selectedMusic = 'upbeat1';
let overlayTextContent = '🎉 연말 대박 이벤트!';
let snsResolutionEnabled = true;
let tvResolutionEnabled = true;
// Load saved state
const eventData = AppState.currentEvent || {};
const storeData = AppState.store || {};
// Initialize overlay text from promotion message
if (eventData.promotionMessage && eventData.promotionMessage.text) {
overlayTextContent = eventData.promotionMessage.text.split('\n').slice(0, 2).join('\n');
}
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('overlayText').textContent = overlayTextContent;
});
/**
* Select background music
*/
window.selectMusic = function(musicId) {
selectedMusic = musicId;
// Update UI
document.querySelectorAll('.music-option').forEach(function(btn) {
if (btn.dataset.music === musicId) {
btn.setAttribute('aria-pressed', 'true');
btn.style.background = 'var(--color-primary-lightest)';
btn.style.borderColor = 'var(--color-primary-main)';
} else {
btn.setAttribute('aria-pressed', 'false');
btn.style.background = 'white';
btn.style.borderColor = 'var(--color-gray-300)';
}
});
};
/**
* Play music preview
*/
window.playMusicPreview = function(musicId, event) {
event.stopPropagation();
const icon = event.target;
// Simulate playing
icon.textContent = 'pause_circle';
Toast.show('🎵 ' + getMusicName(musicId) + ' 미리듣기');
setTimeout(function() {
icon.textContent = 'play_circle';
}, 3000);
};
/**
* Get music name
*/
function getMusicName(musicId) {
const names = {
upbeat1: '경쾌한 음악 1',
upbeat2: '신나는 음악 2',
calm1: '차분한 음악 3'
};
return names[musicId] || '음악';
}
/**
* Edit overlay text
*/
window.editOverlayText = function() {
const modal = document.getElementById('textEditModal');
const input = document.getElementById('textInput');
const counter = document.getElementById('textCount');
input.value = overlayTextContent;
counter.textContent = overlayTextContent.length;
// Add input listener for character count
input.addEventListener('input', function() {
counter.textContent = this.value.length;
});
Modal.open(modal);
};
/**
* Close text edit modal
*/
window.closeTextEditModal = function() {
const modal = document.getElementById('textEditModal');
Modal.close(modal);
};
/**
* Save overlay text
*/
window.saveOverlayText = function() {
const input = document.getElementById('textInput');
overlayTextContent = input.value.trim();
if (!overlayTextContent) {
Toast.error('텍스트를 입력해주세요.');
return;
}
document.getElementById('overlayText').textContent = overlayTextContent;
closeTextEditModal();
Toast.success('텍스트가 저장되었습니다.');
};
/**
* Toggle resolution
*/
window.toggleResolution = function(checkbox) {
if (checkbox.id === 'snsResolution') {
snsResolutionEnabled = checkbox.checked;
} else if (checkbox.id === 'tvResolution') {
tvResolutionEnabled = checkbox.checked;
}
// At least one must be selected
if (!snsResolutionEnabled && !tvResolutionEnabled) {
checkbox.checked = true;
if (checkbox.id === 'snsResolution') {
snsResolutionEnabled = true;
} else {
tvResolutionEnabled = true;
}
Toast.show('최소 1개 이상 선택해야 합니다.');
}
};
/**
* Start video creation
*/
window.startVideoCreation = function() {
// Hide settings
document.getElementById('settingsSection').style.display = 'none';
// Show progress
document.getElementById('progressSection').style.display = 'block';
// Simulate video creation process
simulateVideoCreation();
};
/**
* Simulate video creation with progress
*/
function simulateVideoCreation() {
const progressBar = document.getElementById('videoProgressBar');
const progressPercent = document.getElementById('progressPercent');
const progressMessage = document.getElementById('progressMessage');
const remainingTime = document.getElementById('remainingTime');
const steps = [
{ percent: 0, message: '이미지를 분석하고 있습니다...', time: '3분' },
{ percent: 15, message: '배경 음악을 처리하고 있습니다...', time: '2분 30초' },
{ percent: 30, message: '텍스트 오버레이를 추가하고 있습니다...', time: '2분' },
{ percent: 45, message: 'SNS용 영상을 렌더링하고 있습니다...', time: '1분 30초' },
{ percent: 60, message: '우리동네TV용 영상을 렌더링하고 있습니다...', time: '1분' },
{ percent: 75, message: '영상 품질을 최적화하고 있습니다...', time: '40초' },
{ percent: 90, message: '최종 검토 중...', time: '20초' },
{ percent: 100, message: '완료!', time: '0초' }
];
let currentStep = 0;
function updateProgress() {
if (currentStep >= steps.length) {
// Show result
setTimeout(function() {
showResult();
}, 500);
return;
}
const step = steps[currentStep];
progressBar.style.width = step.percent + '%';
progressPercent.textContent = step.percent;
progressMessage.textContent = step.message;
remainingTime.textContent = step.time;
currentStep++;
setTimeout(updateProgress, 1500);
}
updateProgress();
}
/**
* Show result section
*/
function showResult() {
document.getElementById('progressSection').style.display = 'none';
document.getElementById('resultSection').style.display = 'block';
document.getElementById('nextButton').style.display = 'block';
// Update resolution text
const resolutions = [];
if (snsResolutionEnabled) resolutions.push('SNS용');
if (tvResolutionEnabled) resolutions.push('우리동네TV용');
document.getElementById('videoResolutions').textContent = resolutions.join(', ') + ' 영상 생성완료';
// Save to AppState
eventData.video = {
music: selectedMusic,
overlayText: overlayTextContent,
snsResolution: snsResolutionEnabled,
tvResolution: tvResolutionEnabled,
createdAt: new Date().toISOString()
};
AppState.currentEvent = eventData;
AppState.save();
Toast.success('영상이 성공적으로 생성되었습니다!');
}
/**
* Play video preview
*/
window.playVideoPreview = function() {
Toast.show('🎬 영상 미리보기 재생 (시뮬레이션)');
// In real implementation, would play actual video
};
/**
* Download video
*/
window.downloadVideo = function() {
Loading.show('영상 다운로드 준비 중...');
setTimeout(function() {
Loading.hide();
Toast.success('영상이 다운로드되었습니다!');
// In real implementation, would trigger actual download
}, 1500);
};
/**
* Recreate video
*/
window.recreateVideo = function() {
if (confirm('영상을 다시 제작하시겠습니까? 현재 설정으로 새로 생성됩니다.')) {
// Reset to settings
document.getElementById('resultSection').style.display = 'none';
document.getElementById('nextButton').style.display = 'none';
document.getElementById('settingsSection').style.display = 'block';
Toast.show('설정을 확인하고 다시 제작해주세요.');
}
};
/**
* Go to next step
*/
window.goToNext = function() {
window.location.href = '11-SNS콘텐츠생성.html';
};
})();
</script>
<style>
.music-option {
width: 100%;
padding: var(--spacing-m);
background: white;
border: 2px solid var(--color-gray-300);
border-radius: var(--radius-md);
display: flex;
align-items: center;
gap: var(--spacing-s);
cursor: pointer;
transition: all 0.2s ease;
font-size: 14px;
font-weight: 500;
}
.music-option:hover {
border-color: var(--color-primary-main);
}
.music-option[aria-pressed="true"] {
background: var(--color-primary-lightest);
border-color: var(--color-primary-main);
}
.play-icon {
cursor: pointer;
color: var(--color-primary-main);
transition: transform 0.2s ease;
}
.play-icon:hover {
transform: scale(1.1);
}
.checkbox-card {
display: block;
padding: var(--spacing-m);
background: white;
border: 2px solid var(--color-gray-300);
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.2s ease;
}
.checkbox-card:hover {
border-color: var(--color-primary-main);
}
.checkbox-card input[type="checkbox"] {
margin-right: var(--spacing-s);
}
.checkbox-card input[type="checkbox"]:checked ~ .checkbox-card-content {
color: var(--color-primary-main);
}
.checkbox-card-content {
display: flex;
align-items: center;
gap: var(--spacing-s);
}
.progress-bar-container {
width: 100%;
height: 8px;
background: var(--color-gray-200);
border-radius: var(--radius-full);
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
background: linear-gradient(90deg, var(--color-primary-main), var(--color-secondary-main));
border-radius: var(--radius-full);
transition: width 0.5s ease;
}
.video-play-button {
position: absolute;
z-index: 2;
background: rgba(0, 0, 0, 0.3);
border: none;
border-radius: 50%;
width: 64px;
height: 64px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
.video-play-button:hover {
background: rgba(0, 0, 0, 0.5);
transform: scale(1.1);
}
@keyframes pulse {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.7;
transform: scale(1.05);
}
}
</style>
</body>
</html>

View File

@ -0,0 +1,483 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SNS 콘텐츠 생성 - KT AI 이벤트</title>
<link rel="stylesheet" href="css/common.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Pretendard:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<!-- Skip Navigation -->
<a href="#main-content" class="skip-link">본문으로 건너뛰기</a>
<!-- Header -->
<header class="header" role="banner">
<div class="header-content">
<button type="button"
class="btn-icon"
onclick="window.history.back()"
aria-label="이전 페이지로 돌아가기">
<span class="material-icons">arrow_back</span>
</button>
<h1 class="header-title">SNS 콘텐츠 생성</h1>
<div style="width: 40px;"></div>
</div>
</header>
<!-- Progress Bar -->
<div class="progress-container" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar" style="width: 60%;"></div>
<div class="progress-label">콘텐츠 3/5: SNS</div>
</div>
<!-- Main Content -->
<main id="main-content" class="container" role="main">
<!-- Platform Selection Section -->
<section class="section" aria-labelledby="platform-title" id="selectionSection">
<h2 id="platform-title" class="h3">플랫폼 선택</h2>
<p class="body-s" style="color: var(--color-gray-600); margin-bottom: var(--spacing-l);">
각 플랫폼에 최적화된 콘텐츠가 생성됩니다.
</p>
<!-- Instagram (Required) -->
<div class="card">
<div class="card-body">
<label class="platform-card">
<input type="checkbox"
id="platformInstagram"
checked
disabled
onchange="togglePlatform(this)">
<div class="platform-card-content">
<div style="display: flex; align-items: center; gap: var(--spacing-s); flex: 1;">
<div class="platform-icon" style="background: linear-gradient(45deg, #f09433 0%,#e6683c 25%,#dc2743 50%,#cc2366 75%,#bc1888 100%);">
<span class="material-icons" style="color: white;">photo_camera</span>
</div>
<div style="flex: 1;">
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">
Instagram
<span class="tag tag-sm" style="background: var(--color-primary-main); color: white; margin-left: var(--spacing-xs);">필수</span>
</div>
<div class="caption" style="color: var(--color-gray-600);">1080x1080 (정사각형)</div>
</div>
</div>
<button type="button"
class="btn-text btn-sm"
onclick="previewPlatform('instagram', event)"
aria-label="Instagram 미리보기">
미리보기
</button>
</div>
</label>
</div>
</div>
<!-- Naver Blog (Optional) -->
<div class="card">
<div class="card-body">
<label class="platform-card">
<input type="checkbox"
id="platformBlog"
onchange="togglePlatform(this)">
<div class="platform-card-content">
<div style="display: flex; align-items: center; gap: var(--spacing-s); flex: 1;">
<div class="platform-icon" style="background: #03C75A;">
<span class="material-icons" style="color: white;">article</span>
</div>
<div style="flex: 1;">
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">Naver Blog</div>
<div class="caption" style="color: var(--color-gray-600);">800x600 (가로형)</div>
</div>
</div>
<button type="button"
class="btn-text btn-sm"
onclick="previewPlatform('blog', event)"
aria-label="Naver Blog 미리보기">
미리보기
</button>
</div>
</label>
</div>
</div>
<!-- Kakao Channel (Optional) -->
<div class="card">
<div class="card-body">
<label class="platform-card">
<input type="checkbox"
id="platformKakao"
onchange="togglePlatform(this)">
<div class="platform-card-content">
<div style="display: flex; align-items: center; gap: var(--spacing-s); flex: 1;">
<div class="platform-icon" style="background: #FEE500;">
<span class="material-icons" style="color: #3C1E1E;">chat</span>
</div>
<div style="flex: 1;">
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">Kakao Channel</div>
<div class="caption" style="color: var(--color-gray-600);">800x800 (정사각형)</div>
</div>
</div>
<button type="button"
class="btn-text btn-sm"
onclick="previewPlatform('kakao', event)"
aria-label="Kakao Channel 미리보기">
미리보기
</button>
</div>
</label>
</div>
</div>
<!-- Generate Button -->
<button type="button"
class="btn btn-primary btn-lg"
onclick="generateContent()"
style="width: 100%; margin-top: var(--spacing-m);">
<span class="material-icons">auto_awesome</span>
<span>콘텐츠 생성하기</span>
</button>
</section>
<!-- Result Section (Hidden initially) -->
<section class="section" id="resultSection" style="display: none;" aria-labelledby="result-title">
<h2 id="result-title" class="h3">생성된 콘텐츠</h2>
<!-- Content Preview -->
<div id="contentPreview"></div>
<!-- Post Text -->
<div class="card">
<div class="card-body">
<label class="form-label">게시 텍스트</label>
<div style="padding: var(--spacing-m); background: var(--color-gray-50); border-radius: var(--radius-md); margin-bottom: var(--spacing-s);">
<p class="body-s" id="postText" style="white-space: pre-line;"></p>
</div>
<div id="hashtagsContainer"></div>
</div>
</div>
<!-- Action Buttons -->
<div style="display: flex; gap: var(--spacing-s); margin-top: var(--spacing-l);">
<button type="button"
class="btn btn-outline btn-lg"
onclick="downloadAll()"
style="flex: 1;">
<span class="material-icons">download</span>
<span>일괄 다운로드</span>
</button>
<button type="button"
class="btn btn-outline btn-lg"
onclick="regenerate()"
style="flex: 1;">
<span class="material-icons">refresh</span>
<span>다시생성</span>
</button>
</div>
</section>
</main>
<!-- Fixed Bottom Button (for next step) -->
<div class="fixed-bottom-button" id="nextButton" style="display: none;">
<button type="button"
class="btn btn-primary btn-lg"
onclick="goToNext()">
다음 (QR포스터)
</button>
</div>
<!-- Preview Modal -->
<div id="previewModal" class="modal" role="dialog" aria-labelledby="preview-title" aria-modal="true" style="display: none;">
<div class="modal-content" style="max-width: 400px;">
<div class="modal-header">
<h3 id="preview-title" class="h4">플랫폼 미리보기</h3>
<button type="button"
class="btn-icon"
onclick="closePreview()"
aria-label="닫기">
<span class="material-icons">close</span>
</button>
</div>
<div class="modal-body" id="previewContent" style="padding: 0;">
<!-- Platform-specific preview will be inserted here -->
</div>
</div>
</div>
<script src="js/common.js"></script>
<script>
(function() {
'use strict';
const platforms = {
instagram: true, // Required
blog: false,
kakao: false
};
// Load saved state
const eventData = AppState.currentEvent || {};
const storeData = AppState.store || {};
/**
* Toggle platform selection
*/
window.togglePlatform = function(checkbox) {
const platformId = checkbox.id.replace('platform', '').toLowerCase();
platforms[platformId] = checkbox.checked;
};
/**
* Preview platform
*/
window.previewPlatform = function(platform, event) {
event.stopPropagation();
event.preventDefault();
const modal = document.getElementById('previewModal');
const content = document.getElementById('previewContent');
const title = document.getElementById('preview-title');
const storeName = storeData.name || '우리가게';
const promotionText = eventData.promotionMessage?.text || '🎉 연말 대박 이벤트!';
let previewHTML = '';
if (platform === 'instagram') {
title.textContent = 'Instagram 미리보기';
previewHTML = `
<div style="background: white; border: 1px solid var(--color-gray-300);">
<div style="padding: 12px; display: flex; align-items: center; gap: 8px; border-bottom: 1px solid var(--color-gray-300);">
<div style="width: 32px; height: 32px; background: var(--color-primary-main); border-radius: 50%;"></div>
<div class="body-m" style="font-weight: 600;">${storeName}</div>
</div>
<div style="aspect-ratio: 1; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white; padding: var(--spacing-l); text-align: center;">
<div>
<div class="h3" style="color: white; margin-bottom: var(--spacing-s);">🎁</div>
<div class="body-l" style="color: white; font-weight: 600;">${promotionText.split('\n')[0]}</div>
</div>
</div>
<div style="padding: 12px;">
<div class="body-s" style="margin-bottom: 8px;">${promotionText}</div>
<div class="caption" style="color: var(--color-secondary-main);">#이벤트 #경품 #당첨</div>
</div>
</div>
`;
} else if (platform === 'blog') {
title.textContent = 'Naver Blog 미리보기';
previewHTML = `
<div style="background: white; padding: var(--spacing-l);">
<div class="h4" style="margin-bottom: var(--spacing-m); padding-bottom: var(--spacing-m); border-bottom: 2px solid var(--color-primary-main);">
${storeName} 이벤트 소식
</div>
<div style="aspect-ratio: 4/3; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center; margin-bottom: var(--spacing-m);">
<div style="text-align: center; color: white; padding: var(--spacing-l);">
<div class="h2" style="color: white;">🎉</div>
<div class="h4" style="color: white; margin-top: var(--spacing-s);">${promotionText.split('\n')[0]}</div>
</div>
</div>
<div class="body-m" style="line-height: 1.6; white-space: pre-line;">${promotionText}</div>
</div>
`;
} else if (platform === 'kakao') {
title.textContent = 'Kakao Channel 미리보기';
previewHTML = `
<div style="background: #F7F7F7;">
<div style="background: white; border-radius: 12px; overflow: hidden; margin: 16px;">
<div style="aspect-ratio: 1; background: linear-gradient(135deg, #FFE17D 0%, #FFBB54 100%); display: flex; align-items: center; justify-content: center;">
<div style="text-align: center; padding: var(--spacing-l);">
<div class="h2">🎁</div>
<div class="h4" style="margin-top: var(--spacing-s);">${promotionText.split('\n')[0]}</div>
</div>
</div>
<div style="padding: 16px; background: white;">
<div class="body-m" style="font-weight: 600; margin-bottom: 8px;">${storeName}</div>
<div class="body-s" style="color: var(--color-gray-600);">${promotionText}</div>
</div>
</div>
</div>
`;
}
content.innerHTML = previewHTML;
Modal.open(modal);
};
/**
* Close preview modal
*/
window.closePreview = function() {
const modal = document.getElementById('previewModal');
Modal.close(modal);
};
/**
* Generate content
*/
window.generateContent = function() {
const selectedPlatforms = Object.keys(platforms).filter(p => platforms[p]);
if (selectedPlatforms.length === 0) {
Toast.error('최소 1개 플랫폼을 선택해주세요.');
return;
}
Loading.show('SNS 콘텐츠 생성 중...');
setTimeout(function() {
Loading.hide();
showResult(selectedPlatforms);
}, 2000);
};
/**
* Show result
*/
function showResult(selectedPlatforms) {
document.getElementById('selectionSection').style.display = 'none';
document.getElementById('resultSection').style.display = 'block';
document.getElementById('nextButton').style.display = 'block';
const storeName = storeData.name || '우리가게';
const promotionMessage = eventData.promotionMessage || {};
const promotionText = promotionMessage.text || '🎉 연말 대박 이벤트!';
const hashtags = promotionMessage.hashtags || ['#이벤트', '#경품', '#당첨'];
// Generate content previews
let previewHTML = '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--spacing-m); margin-bottom: var(--spacing-l);">';
selectedPlatforms.forEach(function(platform) {
const platformInfo = getPlatformInfo(platform);
previewHTML += `
<div class="card">
<div class="card-body" style="padding: var(--spacing-s);">
<div style="aspect-ratio: 1; background: ${platformInfo.gradient}; border-radius: var(--radius-sm); margin-bottom: var(--spacing-s); display: flex; align-items: center; justify-content: center;">
<span class="material-icons" style="font-size: 32px; color: white;">${platformInfo.icon}</span>
</div>
<div class="caption" style="text-align: center; font-weight: 600;">${platformInfo.name}</div>
<div class="caption" style="text-align: center; color: var(--color-gray-600); font-size: 11px;">${platformInfo.size}</div>
</div>
</div>
`;
});
previewHTML += '</div>';
document.getElementById('contentPreview').innerHTML = previewHTML;
// Set post text
document.getElementById('postText').textContent = promotionText;
// Set hashtags
const hashtagHTML = '<div style="display: flex; flex-wrap: wrap; gap: var(--spacing-xs); margin-top: var(--spacing-s);">' +
hashtags.map(tag => `<span class="tag tag-sm" style="background: var(--color-secondary-lightest); color: var(--color-secondary-main);">${tag}</span>`).join('') +
'</div>';
document.getElementById('hashtagsContainer').innerHTML = hashtagHTML;
// Save to AppState
eventData.snsContent = {
platforms: selectedPlatforms,
text: promotionText,
hashtags: hashtags,
createdAt: new Date().toISOString()
};
AppState.currentEvent = eventData;
AppState.save();
Toast.success('SNS 콘텐츠가 생성되었습니다!');
}
/**
* Get platform info
*/
function getPlatformInfo(platform) {
const info = {
instagram: {
name: 'Instagram',
size: '1080x1080',
gradient: 'linear-gradient(45deg, #f09433 0%,#e6683c 25%,#dc2743 50%,#cc2366 75%,#bc1888 100%)',
icon: 'photo_camera'
},
blog: {
name: 'Naver Blog',
size: '800x600',
gradient: 'linear-gradient(135deg, #03C75A 0%, #02A648 100%)',
icon: 'article'
},
kakao: {
name: 'Kakao Channel',
size: '800x800',
gradient: 'linear-gradient(135deg, #FFE17D 0%, #FFBB54 100%)',
icon: 'chat'
}
};
return info[platform] || info.instagram;
}
/**
* Download all content
*/
window.downloadAll = function() {
Loading.show('콘텐츠 다운로드 준비 중...');
setTimeout(function() {
Loading.hide();
Toast.success('📦 모든 콘텐츠가 다운로드되었습니다!');
// In real implementation, would create and download ZIP file
}, 1500);
};
/**
* Regenerate content
*/
window.regenerate = function() {
if (confirm('콘텐츠를 다시 생성하시겠습니까?')) {
document.getElementById('resultSection').style.display = 'none';
document.getElementById('nextButton').style.display = 'none';
document.getElementById('selectionSection').style.display = 'block';
Toast.show('플랫폼을 선택하고 다시 생성해주세요.');
}
};
/**
* Go to next step
*/
window.goToNext = function() {
window.location.href = '12-QR포스터생성.html';
};
})();
</script>
<style>
.platform-card {
display: block;
cursor: pointer;
}
.platform-card input[type="checkbox"] {
margin-right: var(--spacing-s);
}
.platform-card-content {
display: flex;
align-items: center;
gap: var(--spacing-m);
}
.platform-icon {
width: 48px;
height: 48px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.platform-card input[type="checkbox"]:disabled {
cursor: not-allowed;
}
</style>
</body>
</html>

View File

@ -0,0 +1,240 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>QR 포스터 생성 - KT AI 이벤트</title>
<link rel="stylesheet" href="css/common.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Pretendard:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<a href="#main-content" class="skip-link">본문으로 건너뛰기</a>
<header class="header" role="banner">
<div class="header-content">
<button type="button" class="btn-icon" onclick="window.history.back()" aria-label="이전 페이지로 돌아가기">
<span class="material-icons">arrow_back</span>
</button>
<h1 class="header-title">QR 포스터 생성</h1>
<div style="width: 40px;"></div>
</div>
</header>
<div class="progress-container" role="progressbar" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar" style="width: 80%;"></div>
<div class="progress-label">콘텐츠 4/5: QR 포스터</div>
</div>
<main id="main-content" class="container" role="main">
<section class="section" aria-labelledby="qr-title" id="settingsSection">
<h2 id="qr-title" class="h3">QR 코드 설정</h2>
<div class="card">
<div class="card-body">
<label class="form-label">연결 URL (자동생성)</label>
<div style="display: flex; gap: var(--spacing-s); align-items: center;">
<div class="form-input" id="eventUrl" style="flex: 1; font-family: monospace; background: var(--color-gray-50);">
ktevnt.co/abc123
</div>
<button type="button" class="btn btn-outline btn-sm" onclick="copyUrl()" aria-label="URL 복사">
<span class="material-icons" style="font-size: 18px;">content_copy</span>
</button>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<label class="form-label">QR 코드 미리보기</label>
<div style="padding: var(--spacing-xl); background: var(--color-gray-50); border-radius: var(--radius-md); display: flex; justify-content: center;">
<div id="qrCode" style="width: 200px; height: 200px; background: white; border: 2px solid var(--color-gray-300); border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center;">
<div style="width: 180px; height: 180px; background: linear-gradient(45deg, #000 25%, transparent 25%, transparent 75%, #000 75%, #000), linear-gradient(45deg, #000 25%, transparent 25%, transparent 75%, #000 75%, #000); background-size: 20px 20px; background-position: 0 0, 10px 10px;"></div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<label class="form-label">포스터 크기</label>
<div style="display: flex; flex-direction: column; gap: var(--spacing-s);">
<label class="radio-card">
<input type="radio" name="posterSize" value="A4" checked onchange="selectSize(this)">
<div class="radio-card-content">
<div>
<div class="body-m" style="font-weight: 600;">A4</div>
<div class="caption" style="color: var(--color-gray-600);">210 × 297mm (일반 인쇄용)</div>
</div>
</div>
</label>
<label class="radio-card">
<input type="radio" name="posterSize" value="A3" onchange="selectSize(this)">
<div class="radio-card-content">
<div>
<div class="body-m" style="font-weight: 600;">A3</div>
<div class="caption" style="color: var(--color-gray-600);">297 × 420mm (대형 포스터용)</div>
</div>
</div>
</label>
</div>
</div>
</div>
<button type="button" class="btn btn-primary btn-lg" onclick="generatePoster()" style="width: 100%;">
<span class="material-icons">description</span>
<span>포스터 생성하기</span>
</button>
</section>
<section class="section" id="resultSection" style="display: none;" aria-labelledby="result-title">
<h2 id="result-title" class="h3">생성 완료</h2>
<div class="card">
<div class="card-body">
<div style="aspect-ratio: 210/297; background: white; border: 2px solid var(--color-gray-300); border-radius: var(--radius-md); margin-bottom: var(--spacing-m); display: flex; align-items: center; justify-content: center; position: relative; overflow: hidden;">
<div style="padding: var(--spacing-l); text-align: center; width: 100%;">
<div class="h3" style="color: var(--color-primary-main); margin-bottom: var(--spacing-m);">🎉</div>
<div class="h4" style="margin-bottom: var(--spacing-l);" id="posterTitle">이벤트 참여하세요!</div>
<div style="width: 120px; height: 120px; margin: 0 auto var(--spacing-m); background: white; border: 2px solid var(--color-gray-300);">
<div style="width: 100%; height: 100%; background: linear-gradient(45deg, #000 25%, transparent 25%, transparent 75%, #000 75%, #000), linear-gradient(45deg, #000 25%, transparent 25%, transparent 75%, #000 75%, #000); background-size: 15px 15px; background-position: 0 0, 7.5px 7.5px;"></div>
</div>
<div class="body-s" style="color: var(--color-gray-600);">QR 코드를 스캔하세요</div>
</div>
</div>
<div class="info-grid">
<div class="info-item">
<span class="info-label">PDF 파일</span>
<span class="info-value" id="pdfSize">2.1 MB</span>
</div>
<div class="info-item">
<span class="info-label">QR 이미지</span>
<span class="info-value">150 KB</span>
</div>
</div>
</div>
</div>
<div style="display: flex; gap: var(--spacing-s);">
<button type="button" class="btn btn-outline btn-lg" onclick="downloadPoster()" style="flex: 1;">
<span class="material-icons">download</span>
<span>다운로드</span>
</button>
<button type="button" class="btn btn-outline btn-lg" onclick="printPoster()" style="flex: 1;">
<span class="material-icons">print</span>
<span>인쇄하기</span>
</button>
</div>
</section>
</main>
<div class="fixed-bottom-button" id="nextButton" style="display: none;">
<button type="button" class="btn btn-primary btn-lg" onclick="goToNext()">
다음 (콘텐츠편집)
</button>
</div>
<script src="js/common.js"></script>
<script>
(function() {
'use strict';
let selectedSize = 'A4';
const eventData = AppState.currentEvent || {};
const storeData = AppState.store || {};
// Generate random event URL
const eventUrl = 'ktevnt.co/' + Math.random().toString(36).substring(2, 9);
document.getElementById('eventUrl').textContent = eventUrl;
window.copyUrl = function() {
navigator.clipboard.writeText(eventUrl).then(function() {
Toast.success('URL이 복사되었습니다!');
}).catch(function() {
Toast.error('복사에 실패했습니다.');
});
};
window.selectSize = function(radio) {
selectedSize = radio.value;
};
window.generatePoster = function() {
Loading.show('포스터 생성 중...');
setTimeout(function() {
Loading.hide();
showResult();
}, 2000);
};
function showResult() {
document.getElementById('settingsSection').style.display = 'none';
document.getElementById('resultSection').style.display = 'block';
document.getElementById('nextButton').style.display = 'block';
const storeName = storeData.name || '우리가게';
document.getElementById('posterTitle').textContent = storeName + ' 이벤트 참여하세요!';
document.getElementById('pdfSize').textContent = selectedSize === 'A4' ? '2.1 MB' : '4.8 MB';
eventData.qrPoster = {
url: eventUrl,
size: selectedSize,
createdAt: new Date().toISOString()
};
AppState.currentEvent = eventData;
AppState.save();
Toast.success('QR 포스터가 생성되었습니다!');
}
window.downloadPoster = function() {
Loading.show('다운로드 준비 중...');
setTimeout(function() {
Loading.hide();
Toast.success('포스터가 다운로드되었습니다!');
}, 1000);
};
window.printPoster = function() {
Toast.show('인쇄 다이얼로그를 엽니다...');
// window.print();
};
window.goToNext = function() {
window.location.href = '13-콘텐츠편집.html';
};
})();
</script>
<style>
.radio-card {
display: block;
padding: var(--spacing-m);
background: white;
border: 2px solid var(--color-gray-300);
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.2s ease;
}
.radio-card:hover {
border-color: var(--color-primary-main);
}
.radio-card input[type="radio"] {
margin-right: var(--spacing-s);
}
.radio-card input[type="radio"]:checked ~ .radio-card-content {
color: var(--color-primary-main);
}
.radio-card-content {
display: flex;
align-items: center;
}
</style>
</body>
</html>

View File

@ -0,0 +1,325 @@
<!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="css/common.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Pretendard:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<a href="#main-content" class="skip-link">본문으로 건너뛰기</a>
<header class="header" role="banner">
<div class="header-content">
<button type="button" class="btn-icon" onclick="window.history.back()" aria-label="이전 페이지로 돌아가기">
<span class="material-icons">arrow_back</span>
</button>
<h1 class="header-title">콘텐츠 편집</h1>
<div style="width: 40px;"></div>
</div>
</header>
<main id="main-content" class="container" role="main" style="padding-bottom: 100px;">
<section class="section" aria-labelledby="select-title">
<h2 id="select-title" class="h3">편집할 콘텐츠 선택</h2>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--spacing-m);">
<button type="button" class="content-card" onclick="editContent('image')" data-type="image">
<div class="content-card-icon" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
<span class="material-icons" style="color: white; font-size: 32px;">image</span>
</div>
<div class="body-s" style="font-weight: 600; text-align: center;">이미지</div>
</button>
<button type="button" class="content-card" onclick="editContent('video')" data-type="video">
<div class="content-card-icon" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
<span class="material-icons" style="color: white; font-size: 32px;">videocam</span>
</div>
<div class="body-s" style="font-weight: 600; text-align: center;">영상</div>
</button>
<button type="button" class="content-card" onclick="editContent('sns')" data-type="sns">
<div class="content-card-icon" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
<span class="material-icons" style="color: white; font-size: 32px;">share</span>
</div>
<div class="body-s" style="font-weight: 600; text-align: center;">SNS</div>
</button>
</div>
</section>
<section class="section" id="editorSection" style="display: none;" aria-labelledby="editor-title">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-m);">
<h2 id="editor-title" class="h3">편집 중</h2>
<button type="button" class="btn-text" onclick="closeEditor()">
<span class="material-icons">close</span>
</button>
</div>
<div class="card">
<div class="card-body">
<div id="previewArea" style="aspect-ratio: 1; background: var(--color-gray-100); border-radius: var(--radius-md); margin-bottom: var(--spacing-m); display: flex; align-items: center; justify-content: center; position: relative; overflow: hidden;">
<div id="previewContent" style="padding: var(--spacing-l); text-align: center; width: 100%;">
<div class="h4" id="previewText">🎉 연말 대박 이벤트!</div>
</div>
</div>
<div style="display: flex; gap: var(--spacing-xs); margin-bottom: var(--spacing-m);">
<button type="button" class="btn btn-outline btn-sm" onclick="editText()" style="flex: 1;">
<span class="material-icons" style="font-size: 18px;">text_fields</span>
<span>텍스트</span>
</button>
<button type="button" class="btn btn-outline btn-sm" onclick="editColor()" style="flex: 1;">
<span class="material-icons" style="font-size: 18px;">palette</span>
<span>색상</span>
</button>
<button type="button" class="btn btn-outline btn-sm" onclick="editSize()" style="flex: 1;">
<span class="material-icons" style="font-size: 18px;">photo_size_select_small</span>
<span>크기</span>
</button>
</div>
<div>
<label class="form-label">편집 이력</label>
<div style="padding: var(--spacing-m); background: var(--color-gray-50); border-radius: var(--radius-md);">
<div style="display: flex; flex-direction: column; gap: var(--spacing-xs);">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span class="body-s">• 원본</span>
<button type="button" class="btn-text btn-sm" onclick="revertToOriginal()">되돌리기</button>
</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span class="body-s" style="font-weight: 600;">• 버전 1 (현재)</span>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="section" id="skipSection">
<button type="button" class="btn btn-outline btn-lg" onclick="skipEdit()" style="width: 100%;">
편집 건너뛰기
</button>
</section>
</main>
<div class="fixed-bottom-button" id="saveButton" style="display: none;">
<button type="button" class="btn btn-outline btn-lg" onclick="cancelEdit()" style="flex: 1;">
취소
</button>
<button type="button" class="btn btn-primary btn-lg" onclick="saveEdit()" style="flex: 2;">
저장하기
</button>
</div>
<div id="textEditModal" class="modal" role="dialog" aria-modal="true" style="display: none;">
<div class="modal-content">
<div class="modal-header">
<h3 class="h4">텍스트 편집</h3>
<button type="button" class="btn-icon" onclick="closeTextModal()">
<span class="material-icons">close</span>
</button>
</div>
<div class="modal-body">
<label class="form-label" for="editTextInput">텍스트 내용</label>
<textarea id="editTextInput" class="form-input" rows="4" placeholder="텍스트를 입력하세요" style="resize: vertical;"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline" onclick="closeTextModal()">취소</button>
<button type="button" class="btn btn-primary" onclick="applyText()">적용</button>
</div>
</div>
</div>
<div id="colorEditModal" class="modal" role="dialog" aria-modal="true" style="display: none;">
<div class="modal-content">
<div class="modal-header">
<h3 class="h4">색상 선택</h3>
<button type="button" class="btn-icon" onclick="closeColorModal()">
<span class="material-icons">close</span>
</button>
</div>
<div class="modal-body">
<label class="form-label">배경 색상</label>
<div style="display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--spacing-s);">
<button type="button" class="color-option" data-color="#667eea" onclick="selectColor(this)" style="background: #667eea;"></button>
<button type="button" class="color-option" data-color="#f093fb" onclick="selectColor(this)" style="background: #f093fb;"></button>
<button type="button" class="color-option" data-color="#4facfe" onclick="selectColor(this)" style="background: #4facfe;"></button>
<button type="button" class="color-option" data-color="#43e97b" onclick="selectColor(this)" style="background: #43e97b;"></button>
<button type="button" class="color-option" data-color="#fa709a" onclick="selectColor(this)" style="background: #fa709a;"></button>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline" onclick="closeColorModal()">취소</button>
<button type="button" class="btn btn-primary" onclick="applyColor()">적용</button>
</div>
</div>
</div>
<script src="js/common.js"></script>
<script>
(function() {
'use strict';
let currentContent = null;
let selectedColor = '#667eea';
const eventData = AppState.currentEvent || {};
window.editContent = function(type) {
currentContent = type;
document.getElementById('editorSection').style.display = 'block';
document.getElementById('skipSection').style.display = 'none';
document.getElementById('saveButton').style.display = 'flex';
const typeNames = {
image: '이미지',
video: '영상',
sns: 'SNS'
};
document.getElementById('editor-title').textContent = typeNames[type] + ' 편집';
// Load content preview
if (type === 'video' && eventData.video) {
document.getElementById('previewText').textContent = eventData.video.overlayText || '🎉 연말 대박 이벤트!';
} else if (eventData.promotionMessage) {
document.getElementById('previewText').textContent = eventData.promotionMessage.text || '🎉 연말 대박 이벤트!';
}
};
window.closeEditor = function() {
document.getElementById('editorSection').style.display = 'none';
document.getElementById('skipSection').style.display = 'block';
document.getElementById('saveButton').style.display = 'none';
currentContent = null;
};
window.editText = function() {
const modal = document.getElementById('textEditModal');
const input = document.getElementById('editTextInput');
input.value = document.getElementById('previewText').textContent;
Modal.open(modal);
};
window.closeTextModal = function() {
Modal.close(document.getElementById('textEditModal'));
};
window.applyText = function() {
const text = document.getElementById('editTextInput').value.trim();
if (text) {
document.getElementById('previewText').textContent = text;
Toast.success('텍스트가 변경되었습니다.');
}
closeTextModal();
};
window.editColor = function() {
Modal.open(document.getElementById('colorEditModal'));
};
window.closeColorModal = function() {
Modal.close(document.getElementById('colorEditModal'));
};
window.selectColor = function(btn) {
selectedColor = btn.dataset.color;
document.querySelectorAll('.color-option').forEach(el => {
el.style.border = '2px solid transparent';
});
btn.style.border = '2px solid var(--color-gray-900)';
};
window.applyColor = function() {
document.getElementById('previewArea').style.background = 'linear-gradient(135deg, ' + selectedColor + ' 0%, ' + adjustColor(selectedColor) + ' 100%)';
Toast.success('색상이 변경되었습니다.');
closeColorModal();
};
function adjustColor(hex) {
const r = parseInt(hex.slice(1,3), 16);
const g = parseInt(hex.slice(3,5), 16);
const b = parseInt(hex.slice(5,7), 16);
return '#' + ((r-40).toString(16).padStart(2,'0')) + ((g-40).toString(16).padStart(2,'0')) + ((b-40).toString(16).padStart(2,'0'));
}
window.editSize = function() {
const current = document.getElementById('previewText');
const currentSize = parseInt(window.getComputedStyle(current).fontSize);
const newSize = currentSize === 24 ? 32 : 24;
current.style.fontSize = newSize + 'px';
Toast.success('크기가 변경되었습니다.');
};
window.revertToOriginal = function() {
if (confirm('원본으로 되돌리시겠습니까?')) {
document.getElementById('previewText').textContent = '🎉 연말 대박 이벤트!';
document.getElementById('previewArea').style.background = 'var(--color-gray-100)';
Toast.success('원본으로 되돌렸습니다.');
}
};
window.cancelEdit = function() {
if (confirm('편집을 취소하시겠습니까?')) {
closeEditor();
}
};
window.saveEdit = function() {
Loading.show('저장 중...');
setTimeout(function() {
Loading.hide();
Toast.success('편집 내용이 저장되었습니다!');
closeEditor();
}, 1000);
};
window.skipEdit = function() {
window.location.href = '14-콘텐츠최종승인.html';
};
})();
</script>
<style>
.content-card {
background: white;
border: 2px solid var(--color-gray-300);
border-radius: var(--radius-md);
padding: var(--spacing-m);
display: flex;
flex-direction: column;
align-items: center;
gap: var(--spacing-s);
cursor: pointer;
transition: all 0.2s ease;
}
.content-card:hover {
border-color: var(--color-primary-main);
transform: translateY(-2px);
}
.content-card-icon {
width: 64px;
height: 64px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
}
.color-option {
aspect-ratio: 1;
border-radius: var(--radius-sm);
border: 2px solid transparent;
cursor: pointer;
transition: transform 0.2s ease;
}
.color-option:hover {
transform: scale(1.1);
}
</style>
</body>
</html>

View File

@ -0,0 +1,279 @@
<!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="css/common.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Pretendard:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<a href="#main-content" class="skip-link">본문으로 건너뛰기</a>
<header class="header" role="banner">
<div class="header-content">
<button type="button" class="btn-icon" onclick="window.history.back()" aria-label="이전 페이지로 돌아가기">
<span class="material-icons">arrow_back</span>
</button>
<h1 class="header-title">콘텐츠 최종 확인</h1>
<div style="width: 40px;"></div>
</div>
</header>
<div class="progress-container" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar" style="width: 100%;"></div>
<div class="progress-label">콘텐츠 5/5: 최종 승인</div>
</div>
<main id="main-content" class="container" role="main" style="padding-bottom: 100px;">
<section class="section">
<div class="alert alert-success" role="status">
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
<span class="material-icons" style="font-size: 24px;">check_circle</span>
<span class="body-l" style="font-weight: 600;">콘텐츠 생성 완료!</span>
</div>
</div>
</section>
<section class="section" aria-labelledby="contents-title">
<h2 id="contents-title" class="h3">생성된 콘텐츠 (갤러리)</h2>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--spacing-s); margin-bottom: var(--spacing-l);">
<button type="button" class="gallery-item" onclick="viewContent('image', 1)">
<div style="aspect-ratio: 1; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center;">
<span class="material-icons" style="color: white; font-size: 32px;">image</span>
</div>
<div class="caption" style="margin-top: 4px; text-align: center;">이미지1</div>
</button>
<button type="button" class="gallery-item" onclick="viewContent('image', 2)">
<div style="aspect-ratio: 1; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center;">
<span class="material-icons" style="color: white; font-size: 32px;">image</span>
</div>
<div class="caption" style="margin-top: 4px; text-align: center;">이미지2</div>
</button>
<button type="button" class="gallery-item" onclick="viewContent('image', 3)">
<div style="aspect-ratio: 1; background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center;">
<span class="material-icons" style="color: white; font-size: 32px;">image</span>
</div>
<div class="caption" style="margin-top: 4px; text-align: center;">이미지3</div>
</button>
<button type="button" class="gallery-item" onclick="viewContent('video', 1)">
<div style="aspect-ratio: 1; background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center;">
<span class="material-icons" style="color: white; font-size: 32px;">play_circle</span>
</div>
<div class="caption" style="margin-top: 4px; text-align: center;">15초영상</div>
</button>
<button type="button" class="gallery-item" onclick="viewContent('sns', 1)">
<div style="aspect-ratio: 1; background: linear-gradient(135deg, #30cfd0 0%, #330867 100%); border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center;">
<span class="material-icons" style="color: white; font-size: 32px;">share</span>
</div>
<div class="caption" style="margin-top: 4px; text-align: center;">SNS세트</div>
</button>
<button type="button" class="gallery-item" onclick="viewContent('qr', 1)">
<div style="aspect-ratio: 1; background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center;">
<span class="material-icons" style="color: #333; font-size: 32px;">qr_code</span>
</div>
<div class="caption" style="margin-top: 4px; text-align: center;">QR포스터</div>
</button>
</div>
<button type="button" class="btn btn-outline btn-lg" onclick="downloadAll()" style="width: 100%;">
<span class="material-icons">download</span>
<span>전체 다운로드</span>
</button>
</section>
<section class="section">
<div class="card">
<div class="card-body">
<div style="display: flex; align-items: center; gap: var(--spacing-s); margin-bottom: var(--spacing-s);">
<span class="material-icons" style="color: var(--color-info);">schedule</span>
<span class="body-m" style="font-weight: 600;">콘텐츠 생성 시간</span>
</div>
<div style="text-align: center; padding: var(--spacing-l);">
<div class="h2" style="color: var(--color-primary-main); margin-bottom: var(--spacing-xs);" id="elapsedTime">7분</div>
<div class="body-s" style="color: var(--color-gray-600);">목표: 5-8분 이내</div>
<div class="body-s" style="color: var(--color-success); font-weight: 600; margin-top: var(--spacing-xs);" id="timeStatus">✓ 목표 달성!</div>
</div>
</div>
</div>
</section>
</main>
<div class="fixed-bottom-button">
<button type="button" class="btn btn-outline btn-lg" onclick="goBack()" style="flex: 1;">
<span class="material-icons">arrow_back</span>
<span>수정</span>
</button>
<button type="button" class="btn btn-primary btn-lg" onclick="approve()" style="flex: 2;">
<span class="material-icons">check</span>
<span>승인하기</span>
</button>
</div>
<div id="viewModal" class="modal" role="dialog" aria-modal="true" style="display: none;">
<div class="modal-content" style="max-width: 90vw; max-height: 90vh;">
<div class="modal-header">
<h3 class="h4" id="viewTitle">콘텐츠 미리보기</h3>
<button type="button" class="btn-icon" onclick="closeView()">
<span class="material-icons">close</span>
</button>
</div>
<div class="modal-body" id="viewContent" style="padding: 0; max-height: 70vh; overflow: auto;">
<!-- Content preview will be inserted here -->
</div>
</div>
</div>
<script src="js/common.js"></script>
<script>
(function() {
'use strict';
const eventData = AppState.currentEvent || {};
const storeData = AppState.store || {};
// Calculate elapsed time
const startTime = eventData.startTime || Date.now();
const elapsedMinutes = Math.floor((Date.now() - startTime) / 60000);
const displayTime = elapsedMinutes > 0 ? elapsedMinutes + '분' : '30초';
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('elapsedTime').textContent = displayTime;
const targetMin = 5;
const targetMax = 8;
const statusEl = document.getElementById('timeStatus');
if (elapsedMinutes <= targetMax) {
statusEl.textContent = '✓ 목표 달성!';
statusEl.style.color = 'var(--color-success)';
} else {
statusEl.textContent = '⚠ 목표 초과';
statusEl.style.color = 'var(--color-warning)';
}
});
window.viewContent = function(type, index) {
const modal = document.getElementById('viewModal');
const title = document.getElementById('viewTitle');
const content = document.getElementById('viewContent');
const typeNames = {
image: '이미지',
video: '영상',
sns: 'SNS',
qr: 'QR 포스터'
};
title.textContent = typeNames[type] + ' ' + (index || '') + ' 미리보기';
let html = '';
if (type === 'image') {
const gradients = [
'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)'
];
html = `
<div style="aspect-ratio: 1; background: ${gradients[index-1]}; display: flex; align-items: center; justify-content: center; color: white; padding: var(--spacing-xl); text-align: center;">
<div>
<div class="h2" style="color: white; margin-bottom: var(--spacing-m);">🎁</div>
<div class="h3" style="color: white;">${eventData.promotionMessage?.text?.split('\n')[0] || '이벤트 참여하세요!'}</div>
</div>
</div>
`;
} else if (type === 'video') {
html = `
<div style="aspect-ratio: 16/9; background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); display: flex; align-items: center; justify-content: center; position: relative;">
<div style="text-align: center; color: white;">
<span class="material-icons" style="font-size: 64px; color: white; margin-bottom: var(--spacing-m);">play_circle</span>
<div class="h4" style="color: white;">15초 홍보 영상</div>
</div>
</div>
`;
} else if (type === 'sns') {
html = `
<div style="padding: var(--spacing-l);">
<div style="margin-bottom: var(--spacing-m);">
<div class="body-m" style="font-weight: 600; margin-bottom: var(--spacing-s);">Instagram</div>
<div style="aspect-ratio: 1; background: linear-gradient(135deg, #30cfd0 0%, #330867 100%); border-radius: var(--radius-md);"></div>
</div>
<div>
<div class="body-m" style="font-weight: 600; margin-bottom: var(--spacing-s);">Naver Blog</div>
<div style="aspect-ratio: 4/3; background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); border-radius: var(--radius-md);"></div>
</div>
</div>
`;
} else if (type === 'qr') {
html = `
<div style="padding: var(--spacing-xl); background: white; text-align: center;">
<div class="h4" style="margin-bottom: var(--spacing-l);">${storeData.name || '우리가게'} 이벤트</div>
<div style="width: 200px; height: 200px; margin: 0 auto var(--spacing-l); background: white; border: 2px solid var(--color-gray-300);">
<div style="width: 100%; height: 100%; background: linear-gradient(45deg, #000 25%, transparent 25%, transparent 75%, #000 75%, #000), linear-gradient(45deg, #000 25%, transparent 25%, transparent 75%, #000 75%, #000); background-size: 20px 20px; background-position: 0 0, 10px 10px;"></div>
</div>
<div class="body-m" style="color: var(--color-gray-600);">QR 코드를 스캔하세요</div>
</div>
`;
}
content.innerHTML = html;
Modal.open(modal);
};
window.closeView = function() {
Modal.close(document.getElementById('viewModal'));
};
window.downloadAll = function() {
Loading.show('전체 콘텐츠 다운로드 중...');
setTimeout(function() {
Loading.hide();
Toast.success('📦 모든 콘텐츠가 다운로드되었습니다!');
}, 2000);
};
window.goBack = function() {
if (confirm('수정 화면으로 돌아가시겠습니까?')) {
window.location.href = '13-콘텐츠편집.html';
}
};
window.approve = function() {
if (confirm('콘텐츠를 승인하고 배포 단계로 진행하시겠습니까?')) {
eventData.contentApproved = true;
eventData.contentApprovedAt = new Date().toISOString();
AppState.currentEvent = eventData;
AppState.save();
Toast.success('콘텐츠가 승인되었습니다!');
setTimeout(function() {
window.location.href = '16-배포진행상태.html';
}, 1000);
}
};
})();
</script>
<style>
.gallery-item {
background: transparent;
border: none;
cursor: pointer;
transition: transform 0.2s ease;
}
.gallery-item:hover {
transform: scale(1.05);
}
</style>
</body>
</html>

View File

@ -0,0 +1,364 @@
<!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="css/common.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Pretendard:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<a href="#main-content" class="skip-link">본문으로 건너뛰기</a>
<header class="header" role="banner">
<div class="header-content">
<button type="button" class="btn-icon" onclick="window.history.back()" aria-label="이전 페이지로 돌아가기">
<span class="material-icons">arrow_back</span>
</button>
<h1 class="header-title">배포 진행 중</h1>
<div style="width: 40px;"></div>
</div>
</header>
<div class="progress-container" role="progressbar" aria-valuenow="66" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar" style="width: 66%;"></div>
<div class="progress-label">배포 2/3: 진행 상태</div>
</div>
<main id="main-content" class="container" role="main" style="padding-bottom: 100px;">
<section class="section">
<div style="text-align: center; margin-bottom: var(--spacing-xl);">
<div style="font-size: 48px; margin-bottom: var(--spacing-m);">🚀</div>
<h2 class="h3">배포 진행 중...</h2>
</div>
</section>
<section class="section" aria-live="polite">
<!-- Instagram -->
<div class="card" id="statusInstagram">
<div class="card-body">
<div style="display: flex; align-items: center; gap: var(--spacing-m);">
<div class="status-icon" style="background: linear-gradient(45deg, #f09433 0%,#e6683c 25%,#dc2743 50%,#cc2366 75%,#bc1888 100%);">
<span class="material-icons" style="color: white;">check</span>
</div>
<div style="flex: 1;">
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">Instagram</div>
<div class="caption" style="color: var(--color-success);">✅ 배포 완료 (3초)</div>
</div>
<button type="button" class="btn-text btn-sm" onclick="viewPost('instagram')">
게시물 보기
</button>
</div>
</div>
</div>
<!-- 우리동네TV -->
<div class="card" id="statusTV">
<div class="card-body">
<div style="display: flex; align-items: center; gap: var(--spacing-m);">
<div class="status-icon status-loading" style="background: var(--color-secondary-main);">
<span class="material-icons rotating" style="color: white;">sync</span>
</div>
<div style="flex: 1;">
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">우리동네TV</div>
<div class="caption" style="color: var(--color-info);">🔄 배포 중... (<span id="tvTime">15</span>초)</div>
<div class="progress-bar-container" style="margin-top: var(--spacing-xs);">
<div class="progress-bar-fill" id="tvProgress" style="width: 40%;"></div>
</div>
</div>
</div>
</div>
</div>
<!-- 지니TV -->
<div class="card" id="statusGenie">
<div class="card-body">
<div style="display: flex; align-items: center; gap: var(--spacing-m);">
<div class="status-icon" style="background: var(--color-gray-400);">
<span class="material-icons" style="color: white;">schedule</span>
</div>
<div style="flex: 1;">
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">지니TV</div>
<div class="caption" style="color: var(--color-gray-600);">⏳ 대기 중...</div>
</div>
</div>
</div>
</div>
<!-- Naver Blog -->
<div class="card" id="statusBlog">
<div class="card-body">
<div style="display: flex; align-items: center; gap: var(--spacing-m);">
<div class="status-icon" style="background: var(--color-error);">
<span class="material-icons" style="color: white;">error</span>
</div>
<div style="flex: 1;">
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">Naver Blog</div>
<div class="caption" style="color: var(--color-error);">❌ 배포 실패 (재시도 중)</div>
<div class="caption" style="color: var(--color-gray-600); margin-top: 4px;">1/3 재시도</div>
</div>
<button type="button" class="btn-text btn-sm" onclick="retryDeploy('blog')">
수동 재시도
</button>
</div>
</div>
</div>
</section>
<section class="section">
<div class="card">
<div class="card-body">
<div class="info-grid">
<div class="info-item" style="text-align: center;">
<span class="info-label">전체 진행률</span>
<span class="h3" style="color: var(--color-primary-main);" id="overallProgress">50%</span>
</div>
<div class="info-item" style="text-align: center;">
<span class="info-label">예상 소요</span>
<span class="h3" style="color: var(--color-info);" id="remainingTime">45초</span>
</div>
</div>
</div>
</div>
</section>
<div style="display: flex; gap: var(--spacing-s);">
<button type="button" class="btn btn-outline btn-lg" onclick="cancelDeploy()" style="flex: 1;">
취소
</button>
<button type="button" class="btn btn-outline btn-lg" onclick="refreshStatus()" style="flex: 1;">
<span class="material-icons">refresh</span>
<span>새로고침</span>
</button>
</div>
</main>
<div class="fixed-bottom-button" id="nextButton" style="display: none;">
<button type="button" class="btn btn-primary btn-lg" onclick="goToNext()">
<span class="material-icons">check_circle</span>
<span>완료 확인</span>
</button>
</div>
<script src="js/common.js"></script>
<script>
(function() {
'use strict';
let deploymentProgress = {
instagram: 100,
tv: 40,
genie: 0,
blog: -1
};
let elapsedTime = 0;
const totalChannels = 4;
// Start deployment simulation
document.addEventListener('DOMContentLoaded', function() {
simulateDeployment();
});
function simulateDeployment() {
const interval = setInterval(function() {
elapsedTime += 5;
// Update TV progress
if (deploymentProgress.tv < 100 && deploymentProgress.tv >= 0) {
deploymentProgress.tv += 15;
if (deploymentProgress.tv >= 100) {
deploymentProgress.tv = 100;
updateChannelStatus('tv', 'complete');
} else {
updateChannelProgress('tv', deploymentProgress.tv);
}
}
// Start Genie after TV
if (deploymentProgress.tv === 100 && deploymentProgress.genie === 0) {
deploymentProgress.genie = 10;
updateChannelStatus('genie', 'progress');
}
// Update Genie progress
if (deploymentProgress.genie > 0 && deploymentProgress.genie < 100) {
deploymentProgress.genie += 20;
if (deploymentProgress.genie >= 100) {
deploymentProgress.genie = 100;
updateChannelStatus('genie', 'complete');
} else {
updateChannelProgress('genie', deploymentProgress.genie);
}
}
// Retry blog after some time
if (elapsedTime === 20 && deploymentProgress.blog === -1) {
deploymentProgress.blog = 0;
updateChannelStatus('blog', 'retry');
}
if (deploymentProgress.blog >= 0 && deploymentProgress.blog < 100) {
deploymentProgress.blog += 25;
if (deploymentProgress.blog >= 100) {
deploymentProgress.blog = 100;
updateChannelStatus('blog', 'complete');
} else {
updateChannelProgress('blog', deploymentProgress.blog);
}
}
// Calculate overall progress
const completed = Object.values(deploymentProgress).filter(v => v === 100).length;
const overall = Math.round((completed / totalChannels) * 100);
document.getElementById('overallProgress').textContent = overall + '%';
const remaining = Math.max(0, 60 - elapsedTime);
document.getElementById('remainingTime').textContent = remaining + '초';
// Check if all complete
if (completed === totalChannels) {
clearInterval(interval);
showCompletion();
}
}, 5000);
}
function updateChannelProgress(channel, progress) {
const progressBar = document.getElementById(channel + 'Progress');
const timeEl = document.getElementById(channel + 'Time');
if (progressBar) {
progressBar.style.width = progress + '%';
}
if (timeEl) {
const remaining = Math.round((100 - progress) / 100 * 30);
timeEl.textContent = remaining;
}
}
function updateChannelStatus(channel, status) {
const card = document.getElementById('status' + capitalize(channel));
if (!card) return;
const icon = card.querySelector('.status-icon .material-icons');
const statusText = card.querySelector('.caption');
const statusIcon = card.querySelector('.status-icon');
if (status === 'complete') {
icon.textContent = 'check';
icon.classList.remove('rotating');
statusIcon.style.background = 'var(--color-success)';
statusText.textContent = '✅ 배포 완료';
statusText.style.color = 'var(--color-success)';
// Remove progress bar if exists
const progressBar = card.querySelector('.progress-bar-container');
if (progressBar) progressBar.remove();
} else if (status === 'progress') {
icon.textContent = 'sync';
icon.classList.add('rotating');
statusIcon.style.background = 'var(--color-secondary-main)';
statusText.innerHTML = '🔄 배포 중... (<span id="' + channel + 'Time">30</span>초)';
statusText.style.color = 'var(--color-info)';
// Add progress bar
const progressHTML = `<div class="progress-bar-container" style="margin-top: var(--spacing-xs);">
<div class="progress-bar-fill" id="${channel}Progress" style="width: 10%;"></div>
</div>`;
statusText.insertAdjacentHTML('afterend', progressHTML);
} else if (status === 'retry') {
icon.textContent = 'sync';
icon.classList.add('rotating');
statusIcon.style.background = 'var(--color-warning)';
statusText.textContent = '🔄 재시도 중...';
statusText.style.color = 'var(--color-warning)';
}
}
function capitalize(str) {
if (str === 'tv') return 'TV';
if (str === 'genie') return 'Genie';
if (str === 'blog') return 'Blog';
return str.charAt(0).toUpperCase() + str.slice(1);
}
function showCompletion() {
document.getElementById('nextButton').style.display = 'block';
Toast.success('🎉 모든 채널 배포가 완료되었습니다!');
}
window.viewPost = function(channel) {
Toast.show('📱 ' + capitalize(channel) + ' 게시물을 엽니다...');
// In real implementation, would open actual post URL
};
window.retryDeploy = function(channel) {
Loading.show('재시도 중...');
setTimeout(function() {
Loading.hide();
deploymentProgress[channel] = 0;
updateChannelStatus(channel, 'retry');
Toast.success('재시도가 시작되었습니다.');
}, 1000);
};
window.refreshStatus = function() {
Toast.show('🔄 상태를 업데이트합니다...');
// In real implementation, would fetch actual status
};
window.cancelDeploy = function() {
if (confirm('배포를 취소하시겠습니까? 진행 중인 작업이 중단됩니다.')) {
Toast.show('배포가 취소되었습니다.');
setTimeout(function() {
window.history.back();
}, 1000);
}
};
window.goToNext = function() {
window.location.href = '17-오프라인자료다운로드.html';
};
})();
</script>
<style>
.status-icon {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.progress-bar-container {
width: 100%;
height: 4px;
background: var(--color-gray-200);
border-radius: var(--radius-full);
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
background: linear-gradient(90deg, var(--color-primary-main), var(--color-secondary-main));
border-radius: var(--radius-full);
transition: width 0.5s ease;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.rotating {
animation: rotate 2s linear infinite;
}
</style>
</body>
</html>

View File

@ -0,0 +1,166 @@
<!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="css/common.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Pretendard:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<a href="#main-content" class="skip-link">본문으로 건너뛰기</a>
<header class="header" role="banner">
<div class="header-content">
<button type="button" class="btn-icon" onclick="window.history.back()" aria-label="이전 페이지로 돌아가기">
<span class="material-icons">arrow_back</span>
</button>
<h1 class="header-title">오프라인 자료</h1>
<div style="width: 40px;"></div>
</div>
</header>
<div class="progress-container" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar" style="width: 100%;"></div>
<div class="progress-label">배포 3/3: 자료 다운로드</div>
</div>
<main id="main-content" class="container" role="main" style="padding-bottom: 100px;">
<section class="section">
<div class="alert alert-success" role="status">
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
<span class="material-icons" style="font-size: 24px;">check_circle</span>
<span class="body-l" style="font-weight: 600;">배포 완료!</span>
</div>
</div>
</section>
<section class="section" aria-labelledby="materials-title">
<h2 id="materials-title" class="h3">오프라인 홍보 자료</h2>
<div class="card" onclick="downloadFile('qr-a4')">
<div class="card-body">
<div style="display: flex; align-items: center; gap: var(--spacing-m);">
<div style="width: 48px; height: 48px; background: var(--color-primary-lightest); border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center;">
<span class="material-icons" style="color: var(--color-primary-main);">description</span>
</div>
<div style="flex: 1;">
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">QR 포스터 (A4)</div>
<div class="caption" style="color: var(--color-gray-600);">2.1MB PDF</div>
</div>
<button type="button" class="btn btn-primary btn-sm" onclick="downloadFile('qr-a4', event)">
다운로드
</button>
</div>
</div>
</div>
<div class="card" onclick="downloadFile('qr-a3')">
<div class="card-body">
<div style="display: flex; align-items: center; gap: var(--spacing-m);">
<div style="width: 48px; height: 48px; background: var(--color-secondary-lightest); border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center;">
<span class="material-icons" style="color: var(--color-secondary-main);">description</span>
</div>
<div style="flex: 1;">
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">QR 포스터 (A3)</div>
<div class="caption" style="color: var(--color-gray-600);">4.5MB PDF</div>
</div>
<button type="button" class="btn btn-primary btn-sm" onclick="downloadFile('qr-a3', event)">
다운로드
</button>
</div>
</div>
</div>
<div class="card" onclick="downloadFile('qr-image')">
<div class="card-body">
<div style="display: flex; align-items: center; gap: var(--spacing-m);">
<div style="width: 48px; height: 48px; background: var(--color-success-lightest); border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center;">
<span class="material-icons" style="color: var(--color-success);">qr_code</span>
</div>
<div style="flex: 1;">
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">QR 코드 이미지</div>
<div class="caption" style="color: var(--color-gray-600);">150KB PNG</div>
</div>
<button type="button" class="btn btn-primary btn-sm" onclick="downloadFile('qr-image', event)">
다운로드
</button>
</div>
</div>
</div>
<div class="card" onclick="downloadFile('images')">
<div class="card-body">
<div style="display: flex; align-items: center; gap: var(--spacing-m);">
<div style="width: 48px; height: 48px; background: var(--color-warning-lightest); border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center;">
<span class="material-icons" style="color: var(--color-warning);">collections</span>
</div>
<div style="flex: 1;">
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">고해상도 이미지 (3종)</div>
<div class="caption" style="color: var(--color-gray-600);">8.3MB ZIP</div>
</div>
<button type="button" class="btn btn-primary btn-sm" onclick="downloadFile('images', event)">
다운로드
</button>
</div>
</div>
</div>
<button type="button" class="btn btn-primary btn-lg" onclick="downloadAll()" style="width: 100%; margin-top: var(--spacing-m);">
<span class="material-icons">download</span>
<span>전체 일괄 다운로드 (ZIP)</span>
</button>
</section>
</main>
<div class="fixed-bottom-button">
<button type="button" class="btn btn-primary btn-lg" onclick="complete()">
<span class="material-icons">check_circle</span>
<span>완료</span>
</button>
</div>
<script src="js/common.js"></script>
<script>
(function() {
'use strict';
window.downloadFile = function(type, event) {
if (event) {
event.stopPropagation();
event.preventDefault();
}
const fileNames = {
'qr-a4': 'QR포스터_A4.pdf',
'qr-a3': 'QR포스터_A3.pdf',
'qr-image': 'QR코드.png',
'images': '홍보이미지_3종.zip'
};
Loading.show('다운로드 중...');
setTimeout(function() {
Loading.hide();
Toast.success(fileNames[type] + ' 다운로드 완료!');
}, 1000);
};
window.downloadAll = function() {
Loading.show('전체 파일 압축 중...');
setTimeout(function() {
Loading.hide();
Toast.success('📦 전체 파일이 다운로드되었습니다!');
}, 2000);
};
window.complete = function() {
Toast.success('이벤트 배포가 완료되었습니다!');
setTimeout(function() {
window.location.href = '20-당첨자명단관리.html';
}, 1000);
};
})();
</script>
</body>
</html>

View File

@ -0,0 +1,432 @@
<!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="css/common.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Pretendard:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<a href="#main-content" class="skip-link">본문으로 건너뛰기</a>
<header class="header" role="banner">
<div class="header-content">
<button type="button" class="btn-icon" onclick="toggleMenu()" aria-label="메뉴">
<span class="material-icons">menu</span>
</button>
<h1 class="header-title">이벤트 대시보드</h1>
<button type="button" class="btn-icon" aria-label="알림">
<span class="material-icons">notifications</span>
</button>
</div>
</header>
<!-- Tab Navigation -->
<nav class="tabs" role="tablist">
<button type="button" class="tab active" role="tab" aria-selected="true" onclick="showTab('dashboard')">
<span class="material-icons">dashboard</span>
<span>대시보드</span>
</button>
<button type="button" class="tab" role="tab" aria-selected="false" onclick="showTab('winners')">
<span class="material-icons">emoji_events</span>
<span>당첨자</span>
</button>
<button type="button" class="tab" role="tab" aria-selected="false" onclick="showTab('analytics')">
<span class="material-icons">analytics</span>
<span>분석</span>
</button>
</nav>
<main id="main-content" class="container" role="main" style="padding-bottom: 80px;">
<!-- Dashboard Tab -->
<div id="dashboardTab" class="tab-content">
<section class="section">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-m);">
<div>
<div class="caption" style="color: var(--color-gray-600);">마지막 업데이트: <span id="lastUpdate">15:35</span></div>
</div>
<button type="button" class="btn-text btn-sm" onclick="refresh()">
<span class="material-icons">refresh</span>
<span>새로고침</span>
</button>
</div>
<div class="card">
<div class="card-body">
<div class="h4" style="margin-bottom: var(--spacing-xs);">연말 대박 이벤트</div>
<div class="body-s" style="color: var(--color-primary-main); font-weight: 600;">D-5 (2025-12-31까지)</div>
</div>
</div>
</section>
<section class="section">
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--spacing-m);">
<div class="card">
<div class="card-body">
<div class="caption" style="color: var(--color-gray-600); margin-bottom: var(--spacing-xs);">총 참여자</div>
<div class="h3" style="color: var(--color-primary-main); margin-bottom: var(--spacing-xs);">1,234명</div>
<div class="caption" style="color: var(--color-success);">+45 (오늘)</div>
</div>
</div>
<div class="card">
<div class="card-body">
<div class="caption" style="color: var(--color-gray-600); margin-bottom: var(--spacing-xs);">총 노출 수</div>
<div class="h3" style="color: var(--color-secondary-main); margin-bottom: var(--spacing-xs);">15,678회</div>
<div class="caption" style="color: var(--color-success);">+230 (1시간)</div>
</div>
</div>
<div class="card">
<div class="card-body">
<div class="caption" style="color: var(--color-gray-600); margin-bottom: var(--spacing-xs);">매출 증가율</div>
<div class="h3" style="color: var(--color-warning); margin-bottom: var(--spacing-xs);">+42%</div>
<div class="caption" style="color: var(--color-gray-600);">(이벤트 전 대비)</div>
</div>
</div>
<div class="card">
<div class="card-body">
<div class="caption" style="color: var(--color-gray-600); margin-bottom: var(--spacing-xs);">예상 ROI</div>
<div class="h3" style="color: var(--color-success); margin-bottom: var(--spacing-xs);">245%</div>
<div class="caption" style="color: var(--color-gray-600);">(투자 대비)</div>
</div>
</div>
</div>
</section>
<section class="section">
<h2 class="h4" style="margin-bottom: var(--spacing-m);">채널별 참여 현황</h2>
<div class="card">
<div class="card-body">
<div style="display: flex; flex-direction: column; gap: var(--spacing-m);">
<div>
<div style="display: flex; justify-content: space-between; margin-bottom: var(--spacing-xs);">
<span class="body-s">QR코드</span>
<span class="body-s" style="font-weight: 600;">45%</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar-fill" style="width: 45%; background: var(--color-primary-main);"></div>
</div>
</div>
<div>
<div style="display: flex; justify-content: space-between; margin-bottom: var(--spacing-xs);">
<span class="body-s">Instagram</span>
<span class="body-s" style="font-weight: 600;">30%</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar-fill" style="width: 30%; background: var(--color-secondary-main);"></div>
</div>
</div>
<div>
<div style="display: flex; justify-content: space-between; margin-bottom: var(--spacing-xs);">
<span class="body-s">우리동네TV</span>
<span class="body-s" style="font-weight: 600;">15%</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar-fill" style="width: 15%; background: var(--color-success);"></div>
</div>
</div>
<div>
<div style="display: flex; justify-content: space-between; margin-bottom: var(--spacing-xs);">
<span class="body-s">Naver Blog</span>
<span class="body-s" style="font-weight: 600;">10%</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar-fill" style="width: 10%; background: var(--color-warning);"></div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
<!-- Winners Tab -->
<div id="winnersTab" class="tab-content" style="display: none;">
<section class="section">
<div class="card">
<div class="card-body">
<div class="h4" style="margin-bottom: var(--spacing-xs);">연말 대박 이벤트</div>
<div class="body-s" style="color: var(--color-gray-600);">당첨인원: <span style="font-weight: 600; color: var(--color-primary-main);">100명</span></div>
</div>
</div>
</section>
<section class="section">
<input type="text" class="form-input" placeholder="🔍 이름 또는 전화번호 검색" id="searchInput" onkeyup="searchWinners()">
</section>
<section class="section">
<div style="display: flex; gap: var(--spacing-xs); margin-bottom: var(--spacing-m);">
<button type="button" class="btn btn-sm" onclick="filterWinners('all')" id="filterAll" style="background: var(--color-primary-main); color: white;">전체</button>
<button type="button" class="btn btn-outline btn-sm" onclick="filterWinners('pending')" id="filterPending">미지급</button>
<button type="button" class="btn btn-outline btn-sm" onclick="filterWinners('complete')" id="filterComplete">완료</button>
</div>
<div id="winnersList">
<!-- Winners will be rendered here -->
</div>
</section>
<section class="section">
<div style="display: flex; gap: var(--spacing-s);">
<button type="button" class="btn btn-outline btn-lg" onclick="viewAllParticipants()" style="flex: 1;">
<span class="material-icons">people</span>
<span>참여자 전체</span>
</button>
<button type="button" class="btn btn-primary btn-lg" onclick="downloadExcel()" style="flex: 1;">
<span class="material-icons">download</span>
<span>엑셀 다운로드</span>
</button>
</div>
</section>
</div>
<!-- Analytics Tab -->
<div id="analyticsTab" class="tab-content" style="display: none;">
<section class="section">
<h2 class="h4">채널별 성과 분석</h2>
<div class="card">
<div class="card-body">
<div style="aspect-ratio: 16/9; background: var(--color-gray-50); border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center;">
<div style="text-align: center;">
<span class="material-icons" style="font-size: 48px; color: var(--color-gray-400);">bar_chart</span>
<div class="body-s" style="color: var(--color-gray-600); margin-top: var(--spacing-s);">채널별 성과 차트</div>
</div>
</div>
</div>
</div>
</section>
<section class="section">
<h2 class="h4">ROI 분석</h2>
<div class="card">
<div class="card-body">
<div class="info-grid">
<div class="info-item">
<span class="info-label">총 투자 비용</span>
<span class="info-value" style="color: var(--color-primary-main);">500,000원</span>
</div>
<div class="info-item">
<span class="info-label">예상 수익</span>
<span class="info-value" style="color: var(--color-success);">1,225,000원</span>
</div>
<div class="info-item">
<span class="info-label">ROI</span>
<span class="info-value" style="color: var(--color-warning); font-weight: 700;">245%</span>
</div>
<div class="info-item">
<span class="info-label">순이익</span>
<span class="info-value" style="color: var(--color-success);">725,000원</span>
</div>
</div>
</div>
</div>
</section>
<section class="section">
<button type="button" class="btn btn-primary btn-lg" onclick="downloadReport()" style="width: 100%;">
<span class="material-icons">description</span>
<span>분석 리포트 다운로드</span>
</button>
</section>
</div>
</main>
<script src="js/common.js"></script>
<script>
(function() {
'use strict';
const winnersData = [
{ name: '김철수', phone: '010-1234-5678', code: 'A-12345678', date: '2025-12-15', channel: 'QR코드', complete: false },
{ name: '이영희', phone: '010-2345-6789', code: 'A-23456789', date: '2025-12-16', channel: 'Instagram', complete: true, completeDate: '2025-12-20' },
{ name: '박민수', phone: '010-3456-7890', code: 'A-34567890', date: '2025-12-17', channel: '우리동네TV', complete: false },
{ name: '정수진', phone: '010-4567-8901', code: 'A-45678901', date: '2025-12-18', channel: 'Instagram', complete: true, completeDate: '2025-12-21' }
];
let currentFilter = 'all';
document.addEventListener('DOMContentLoaded', function() {
renderWinners();
});
window.showTab = function(tabName) {
// Hide all tabs
document.querySelectorAll('.tab-content').forEach(el => el.style.display = 'none');
document.querySelectorAll('.tab').forEach(el => {
el.classList.remove('active');
el.setAttribute('aria-selected', 'false');
});
// Show selected tab
document.getElementById(tabName + 'Tab').style.display = 'block';
event.target.closest('.tab').classList.add('active');
event.target.closest('.tab').setAttribute('aria-selected', 'true');
};
function renderWinners(filter = 'all', searchTerm = '') {
let filtered = winnersData;
if (filter === 'pending') {
filtered = filtered.filter(w => !w.complete);
} else if (filter === 'complete') {
filtered = filtered.filter(w => w.complete);
}
if (searchTerm) {
filtered = filtered.filter(w =>
w.name.includes(searchTerm) || w.phone.includes(searchTerm)
);
}
const html = filtered.map((winner, index) => `
<div class="card">
<div class="card-body">
<div style="margin-bottom: var(--spacing-m);">
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">${winner.name}</div>
<div class="body-s" style="color: var(--color-gray-600);">${winner.phone}</div>
<div class="caption" style="color: var(--color-gray-600); margin-top: var(--spacing-xs);">
응모: ${winner.code} | 참여일: ${winner.date}<br>
경로: ${winner.channel}
</div>
</div>
<label style="display: flex; align-items: center; gap: var(--spacing-xs); cursor: pointer;">
<input type="checkbox" ${winner.complete ? 'checked' : ''} onchange="toggleComplete(${index}, this)">
<span class="body-s" style="color: ${winner.complete ? 'var(--color-success)' : 'var(--color-gray-600)'};">
${winner.complete ? '✅ 경품 지급 완료' : '☐ 경품 지급 완료'}
</span>
</label>
${winner.complete ? `<div class="caption" style="color: var(--color-success); margin-top: 4px;">지급일: ${winner.completeDate}</div>` : ''}
</div>
</div>
`).join('');
document.getElementById('winnersList').innerHTML = html;
}
window.filterWinners = function(filter) {
currentFilter = filter;
['filterAll', 'filterPending', 'filterComplete'].forEach(id => {
const btn = document.getElementById(id);
if (id === 'filter' + filter.charAt(0).toUpperCase() + filter.slice(1)) {
btn.style.background = 'var(--color-primary-main)';
btn.style.color = 'white';
btn.classList.remove('btn-outline');
} else {
btn.style.background = '';
btn.style.color = '';
btn.classList.add('btn-outline');
}
});
renderWinners(filter);
};
window.searchWinners = function() {
const searchTerm = document.getElementById('searchInput').value;
renderWinners(currentFilter, searchTerm);
};
window.toggleComplete = function(index, checkbox) {
winnersData[index].complete = checkbox.checked;
if (checkbox.checked) {
winnersData[index].completeDate = new Date().toISOString().split('T')[0];
Toast.success('지급 완료로 처리되었습니다.');
} else {
winnersData[index].completeDate = null;
Toast.show('미지급으로 변경되었습니다.');
}
renderWinners(currentFilter);
};
window.viewAllParticipants = function() {
Toast.show('📊 참여자 전체 명단을 표시합니다...');
};
window.downloadExcel = function() {
Loading.show('엑셀 파일 생성 중...');
setTimeout(function() {
Loading.hide();
Toast.success('📥 당첨자_명단.xlsx 다운로드 완료!');
}, 1500);
};
window.downloadReport = function() {
Loading.show('리포트 생성 중...');
setTimeout(function() {
Loading.hide();
Toast.success('📄 이벤트_분석_리포트.pdf 다운로드 완료!');
}, 2000);
};
window.refresh = function() {
const now = new Date();
const timeStr = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0');
document.getElementById('lastUpdate').textContent = timeStr;
Toast.success('데이터가 업데이트되었습니다.');
};
window.toggleMenu = function() {
Toast.show('메뉴 표시');
};
})();
</script>
<style>
.tabs {
display: flex;
background: white;
border-bottom: 1px solid var(--color-gray-300);
position: sticky;
top: 56px;
z-index: 10;
}
.tab {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: var(--spacing-s);
background: transparent;
border: none;
border-bottom: 2px solid transparent;
cursor: pointer;
transition: all 0.2s ease;
color: var(--color-gray-600);
}
.tab.active {
color: var(--color-primary-main);
border-bottom-color: var(--color-primary-main);
}
.tab .material-icons {
font-size: 20px;
}
.tab span:last-child {
font-size: 12px;
font-weight: 500;
}
.progress-bar-container {
width: 100%;
height: 8px;
background: var(--color-gray-200);
border-radius: var(--radius-full);
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
border-radius: var(--radius-full);
transition: width 0.3s ease;
}
</style>
</body>
</html>

View File

@ -0,0 +1,870 @@
/**
* KT AI 기반 소상공인 이벤트 자동 생성 서비스
* 공통 스타일시트
*
* 작성일: 2025-01-20
* 버전: 1.0
* 디자인 원칙: Mobile First, 접근성 우선, 일관성
*/
/* ============================================
1. CSS Variables (Design Tokens)
============================================ */
:root {
/* Primary Colors */
--color-primary-main: #E31E24;
--color-primary-light: #FF4D52;
--color-primary-dark: #C71820;
/* Secondary Colors */
--color-secondary-main: #0066FF;
--color-secondary-light: #4D94FF;
--color-secondary-dark: #004DBF;
/* Grayscale */
--color-black: #1A1A1A;
--color-gray-700: #4A4A4A;
--color-gray-500: #9E9E9E;
--color-gray-300: #D9D9D9;
--color-gray-100: #F5F5F5;
--color-white: #FFFFFF;
/* Semantic Colors */
--color-success: #00C853;
--color-warning: #FFA000;
--color-error: #D32F2F;
--color-info: #0288D1;
/* Typography */
--font-family-base: 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', system-ui, sans-serif;
/* Font Sizes (Mobile First) */
--font-size-display: 28px;
--font-size-h1: 24px;
--font-size-h2: 20px;
--font-size-h3: 18px;
--font-size-body-l: 16px;
--font-size-body-m: 14px;
--font-size-body-s: 12px;
--font-size-button: 16px;
/* Font Weights */
--font-weight-regular: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
/* Line Heights */
--line-height-tight: 1.3;
--line-height-normal: 1.4;
--line-height-relaxed: 1.5;
/* Spacing (4px Grid System) */
--spacing-xs: 4px;
--spacing-s: 8px;
--spacing-m: 16px;
--spacing-l: 24px;
--spacing-xl: 32px;
--spacing-2xl: 48px;
/* Border Radius */
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--radius-xl: 24px;
--radius-full: 50%;
/* Shadows */
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.08);
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.08);
--shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.12);
--shadow-xl: 0 8px 24px rgba(0, 0, 0, 0.2);
/* Transitions */
--transition-fast: 100ms ease-out;
--transition-normal: 200ms ease-out;
--transition-slow: 300ms ease-out;
/* Z-index */
--z-dropdown: 1000;
--z-sticky: 1020;
--z-fixed: 1030;
--z-modal-backdrop: 1040;
--z-modal: 1050;
--z-toast: 1060;
}
/* Tablet */
@media (min-width: 768px) {
:root {
--font-size-display: 32px;
--font-size-h1: 28px;
--font-size-h2: 22px;
--font-size-h3: 20px;
--font-size-body-l: 18px;
--font-size-body-m: 16px;
--font-size-body-s: 14px;
}
}
/* Desktop */
@media (min-width: 1024px) {
:root {
--font-size-display: 36px;
--font-size-h1: 32px;
--font-size-h2: 24px;
}
}
/* ============================================
2. Reset & Base Styles
============================================ */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: 16px;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: transparent;
}
body {
font-family: var(--font-family-base);
font-size: var(--font-size-body-m);
font-weight: var(--font-weight-regular);
line-height: var(--line-height-relaxed);
color: var(--color-black);
background-color: var(--color-gray-100);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* ============================================
3. Typography System
============================================ */
.display {
font-size: var(--font-size-display);
font-weight: var(--font-weight-bold);
line-height: var(--line-height-tight);
letter-spacing: -0.5px;
}
h1, .h1 {
font-size: var(--font-size-h1);
font-weight: var(--font-weight-bold);
line-height: var(--line-height-tight);
letter-spacing: -0.3px;
}
h2, .h2 {
font-size: var(--font-size-h2);
font-weight: var(--font-weight-bold);
line-height: var(--line-height-normal);
letter-spacing: -0.2px;
}
h3, .h3 {
font-size: var(--font-size-h3);
font-weight: var(--font-weight-semibold);
line-height: var(--line-height-normal);
}
.body-l {
font-size: var(--font-size-body-l);
font-weight: var(--font-weight-regular);
line-height: var(--line-height-relaxed);
}
.body-m {
font-size: var(--font-size-body-m);
font-weight: var(--font-weight-regular);
line-height: var(--line-height-relaxed);
}
.body-s {
font-size: var(--font-size-body-s);
font-weight: var(--font-weight-regular);
line-height: var(--line-height-relaxed);
}
/* ============================================
4. Layout
============================================ */
.container {
width: 100%;
margin: 0 auto;
padding: 0 20px;
}
@media (min-width: 768px) {
.container {
padding: 0 40px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1200px;
padding: 0 80px;
}
}
/* App Layout */
.app-wrapper {
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: var(--color-gray-100);
}
.app-content {
flex: 1;
padding-bottom: 80px; /* Bottom Navigation 공간 */
}
/* ============================================
5. Top App Bar
============================================ */
.app-bar {
position: sticky;
top: 0;
z-index: var(--z-sticky);
background-color: var(--color-white);
border-bottom: 1px solid var(--color-gray-300);
height: 56px;
display: flex;
align-items: center;
padding: 0 var(--spacing-s);
}
.app-bar__back {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
border: none;
background: none;
cursor: pointer;
color: var(--color-gray-700);
border-radius: var(--radius-full);
transition: background-color var(--transition-fast);
}
.app-bar__back:hover {
background-color: var(--color-gray-100);
}
.app-bar__title {
flex: 1;
font-size: var(--font-size-h3);
font-weight: var(--font-weight-semibold);
text-align: center;
margin-right: 48px; /* 뒤로가기 버튼과 균형 */
}
/* ============================================
6. Bottom Navigation
============================================ */
.bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: var(--z-fixed);
background-color: var(--color-white);
border-top: 1px solid var(--color-gray-300);
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.08);
height: 60px;
display: flex;
}
.bottom-nav__item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--spacing-xs);
border: none;
background: none;
color: var(--color-gray-500);
cursor: pointer;
text-decoration: none;
transition: color var(--transition-fast);
min-width: 44px;
min-height: 44px;
}
.bottom-nav__item:hover {
color: var(--color-primary-main);
}
.bottom-nav__item.active {
color: var(--color-primary-main);
}
.bottom-nav__icon {
font-size: 24px;
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
display: inline-block;
}
.bottom-nav__label {
font-size: var(--font-size-body-s);
font-weight: var(--font-weight-regular);
}
/* ============================================
7. Buttons
============================================ */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-s);
font-family: var(--font-family-base);
font-size: var(--font-size-button);
font-weight: var(--font-weight-semibold);
line-height: 1.5;
text-align: center;
text-decoration: none;
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
transition: all var(--transition-normal);
min-width: 44px;
min-height: 44px;
}
.btn:disabled {
cursor: not-allowed;
opacity: 0.6;
}
/* Primary Button */
.btn-primary {
background-color: var(--color-primary-main);
color: var(--color-white);
box-shadow: var(--shadow-sm);
}
.btn-primary:hover:not(:disabled) {
background-color: var(--color-primary-light);
}
.btn-primary:active:not(:disabled) {
background-color: var(--color-primary-dark);
transform: scale(0.95);
}
.btn-primary:disabled {
background-color: var(--color-gray-300);
color: var(--color-gray-500);
}
/* Secondary Button */
.btn-secondary {
background-color: var(--color-white);
color: var(--color-primary-main);
border: 2px solid var(--color-primary-main);
}
.btn-secondary:hover:not(:disabled) {
background-color: rgba(227, 30, 36, 0.05);
}
.btn-secondary:active:not(:disabled) {
background-color: rgba(227, 30, 36, 0.1);
transform: scale(0.95);
}
/* Text Button */
.btn-text {
background-color: transparent;
color: var(--color-primary-main);
}
.btn-text:hover:not(:disabled) {
background-color: rgba(227, 30, 36, 0.05);
}
/* Button Sizes */
.btn-lg {
padding: 16px 24px;
height: 48px;
}
.btn-md {
padding: 12px 20px;
height: 44px;
font-size: var(--font-size-body-m);
}
.btn-sm {
padding: 8px 16px;
height: 36px;
font-size: var(--font-size-body-s);
}
/* Full Width Button */
.btn-block {
width: 100%;
}
/* ============================================
8. Cards
============================================ */
.card {
background-color: var(--color-white);
border: 1px solid rgba(224, 224, 224, 1);
border-radius: var(--radius-md);
padding: var(--spacing-l);
box-shadow: var(--shadow-md);
transition: all var(--transition-normal);
}
.card:hover {
border-color: var(--color-primary-main);
box-shadow: var(--shadow-lg);
}
.card.selected {
border: 2px solid var(--color-primary-main);
background-color: rgba(227, 30, 36, 0.02);
}
.card__header {
margin-bottom: var(--spacing-m);
}
.card__title {
font-size: var(--font-size-h3);
font-weight: var(--font-weight-semibold);
margin-bottom: var(--spacing-s);
}
.card__body {
color: var(--color-gray-700);
font-size: var(--font-size-body-m);
}
.card__footer {
margin-top: var(--spacing-m);
padding-top: var(--spacing-m);
border-top: 1px solid var(--color-gray-300);
}
/* ============================================
9. Form Elements
============================================ */
.form-group {
margin-bottom: var(--spacing-m);
}
.form-label {
display: block;
font-size: var(--font-size-body-m);
font-weight: var(--font-weight-medium);
color: var(--color-black);
margin-bottom: var(--spacing-s);
}
.form-label.required::after {
content: '*';
color: var(--color-error);
margin-left: var(--spacing-xs);
}
.form-input {
width: 100%;
height: 48px;
padding: var(--spacing-m);
font-family: var(--font-family-base);
font-size: var(--font-size-body-l);
font-weight: var(--font-weight-regular);
color: var(--color-black);
background-color: var(--color-white);
border: 1px solid var(--color-gray-300);
border-radius: var(--radius-sm);
transition: all var(--transition-normal);
}
.form-input::placeholder {
color: var(--color-gray-500);
}
.form-input:focus {
outline: none;
border: 2px solid var(--color-secondary-main);
box-shadow: 0 0 0 4px rgba(0, 102, 255, 0.1);
}
.form-input:disabled {
background-color: var(--color-gray-100);
color: var(--color-gray-500);
cursor: not-allowed;
}
.form-input.error {
border: 2px solid var(--color-error);
box-shadow: 0 0 0 4px rgba(211, 47, 47, 0.1);
}
.form-error {
display: block;
margin-top: var(--spacing-s);
font-size: var(--font-size-body-s);
color: var(--color-error);
}
/* Textarea */
.form-textarea {
min-height: 120px;
resize: vertical;
padding: var(--spacing-m);
font-size: var(--font-size-body-m);
line-height: var(--line-height-relaxed);
}
/* Checkbox & Radio */
.form-check {
display: flex;
align-items: center;
gap: var(--spacing-s);
cursor: pointer;
min-height: 44px;
}
.form-check input[type="checkbox"],
.form-check input[type="radio"] {
width: 24px;
height: 24px;
cursor: pointer;
accent-color: var(--color-primary-main);
}
/* ============================================
10. Progress & Loading
============================================ */
.progress-bar {
width: 100%;
height: 8px;
background-color: var(--color-gray-100);
border-radius: 4px;
overflow: hidden;
}
.progress-bar__fill {
height: 100%;
background-color: var(--color-primary-main);
transition: width var(--transition-slow);
}
.spinner {
width: 32px;
height: 32px;
border: 3px solid var(--color-gray-100);
border-top-color: var(--color-primary-main);
border-radius: var(--radius-full);
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: var(--z-modal);
}
/* ============================================
11. Toast Notification
============================================ */
.toast {
position: fixed;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
z-index: var(--z-toast);
background-color: rgba(26, 26, 26, 0.9);
color: var(--color-white);
padding: var(--spacing-m) var(--spacing-l);
border-radius: var(--radius-sm);
font-size: var(--font-size-body-m);
box-shadow: var(--shadow-xl);
animation: toast-show 200ms ease-out;
max-width: calc(100% - 40px);
}
@keyframes toast-show {
from {
opacity: 0;
transform: translateX(-50%) translateY(20px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
.toast.hide {
animation: toast-hide 200ms ease-in forwards;
}
@keyframes toast-hide {
to {
opacity: 0;
transform: translateX(-50%) translateY(20px);
}
}
/* ============================================
12. Modal
============================================ */
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
z-index: var(--z-modal-backdrop);
animation: fade-in 250ms ease-out;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: var(--z-modal);
background-color: var(--color-white);
border-radius: var(--radius-lg);
padding: var(--spacing-l);
max-width: 400px;
width: calc(100% - 40px);
box-shadow: var(--shadow-xl);
animation: modal-show 250ms ease-out;
}
@keyframes modal-show {
from {
opacity: 0;
transform: translate(-50%, -50%) scale(0.95);
}
to {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
}
.modal__header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--spacing-m);
}
.modal__title {
font-size: var(--font-size-h2);
font-weight: var(--font-weight-bold);
}
.modal__close {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border: none;
background: none;
cursor: pointer;
color: var(--color-gray-700);
border-radius: var(--radius-full);
transition: background-color var(--transition-fast);
}
.modal__close:hover {
background-color: var(--color-gray-100);
}
.modal__body {
margin-bottom: var(--spacing-l);
color: var(--color-gray-700);
}
.modal__footer {
display: flex;
gap: var(--spacing-s);
justify-content: flex-end;
}
/* ============================================
13. Bottom Sheet
============================================ */
.bottom-sheet-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: fade-in 300ms ease-out;
}
.bottom-sheet {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: var(--z-modal);
background-color: var(--color-white);
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.15);
animation: slide-up 300ms ease-out;
}
@keyframes slide-up {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
.bottom-sheet__handle {
width: 40px;
height: 4px;
background-color: var(--color-gray-300);
border-radius: 2px;
margin: var(--spacing-m) auto;
}
.bottom-sheet__content {
padding: 0 var(--spacing-l) var(--spacing-l);
}
/* ============================================
14. Utility Classes
============================================ */
/* Display */
.d-none { display: none !important; }
.d-block { display: block !important; }
.d-flex { display: flex !important; }
.d-grid { display: grid !important; }
/* Flex */
.flex-column { flex-direction: column !important; }
.flex-wrap { flex-wrap: wrap !important; }
.justify-center { justify-content: center !important; }
.justify-between { justify-content: space-between !important; }
.align-center { align-items: center !important; }
.gap-xs { gap: var(--spacing-xs) !important; }
.gap-s { gap: var(--spacing-s) !important; }
.gap-m { gap: var(--spacing-m) !important; }
.gap-l { gap: var(--spacing-l) !important; }
/* Spacing */
.mt-s { margin-top: var(--spacing-s) !important; }
.mt-m { margin-top: var(--spacing-m) !important; }
.mt-l { margin-top: var(--spacing-l) !important; }
.mb-s { margin-bottom: var(--spacing-s) !important; }
.mb-m { margin-bottom: var(--spacing-m) !important; }
.mb-l { margin-bottom: var(--spacing-l) !important; }
.p-m { padding: var(--spacing-m) !important; }
.p-l { padding: var(--spacing-l) !important; }
/* Text */
.text-center { text-align: center !important; }
.text-right { text-align: right !important; }
.text-primary { color: var(--color-primary-main) !important; }
.text-secondary { color: var(--color-secondary-main) !important; }
.text-success { color: var(--color-success) !important; }
.text-error { color: var(--color-error) !important; }
.text-muted { color: var(--color-gray-500) !important; }
/* Background */
.bg-white { background-color: var(--color-white) !important; }
.bg-gray { background-color: var(--color-gray-100) !important; }
.bg-primary { background-color: var(--color-primary-main) !important; }
/* Border */
.border { border: 1px solid var(--color-gray-300) !important; }
.border-top { border-top: 1px solid var(--color-gray-300) !important; }
.border-bottom { border-bottom: 1px solid var(--color-gray-300) !important; }
/* Visibility */
.hidden { visibility: hidden !important; }
.sr-only {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}
/* ============================================
15. Accessibility
============================================ */
/* Focus Visible */
*:focus-visible {
outline: 2px solid var(--color-secondary-main);
outline-offset: 2px;
}
/* Skip Link */
.skip-link {
position: absolute;
top: -40px;
left: 0;
background-color: var(--color-primary-main);
color: var(--color-white);
padding: var(--spacing-s) var(--spacing-m);
text-decoration: none;
z-index: 9999;
}
.skip-link:focus {
top: 0;
}
/* ============================================
16. Animations
============================================ */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

552
design/uiux/prototype/js/common.js vendored Normal file
View File

@ -0,0 +1,552 @@
/**
* KT AI 기반 소상공인 이벤트 자동 생성 서비스
* 공통 JavaScript 유틸리티
*
* 작성일: 2025-01-20
* 버전: 1.0
*/
(function() {
'use strict';
// ============================================
// 1. 상태 관리
// ============================================
window.AppState = {
// 사용자 정보
user: null,
// 매장 정보
store: null,
// 현재 이벤트 정보
currentEvent: null,
// localStorage에서 데이터 로드
load() {
try {
this.user = JSON.parse(localStorage.getItem('kt_user') || 'null');
this.store = JSON.parse(localStorage.getItem('kt_store') || 'null');
this.currentEvent = JSON.parse(localStorage.getItem('kt_current_event') || 'null');
} catch (e) {
console.error('Failed to load app state:', e);
}
},
// localStorage에 데이터 저장
save() {
try {
localStorage.setItem('kt_user', JSON.stringify(this.user));
localStorage.setItem('kt_store', JSON.stringify(this.store));
localStorage.setItem('kt_current_event', JSON.stringify(this.currentEvent));
} catch (e) {
console.error('Failed to save app state:', e);
}
},
// 상태 초기화
clear() {
this.user = null;
this.store = null;
this.currentEvent = null;
localStorage.removeItem('kt_user');
localStorage.removeItem('kt_store');
localStorage.removeItem('kt_current_event');
}
};
// 페이지 로드 시 상태 복원
document.addEventListener('DOMContentLoaded', function() {
window.AppState.load();
});
// ============================================
// 2. Toast 알림
// ============================================
window.Toast = {
show(message, duration = 3000) {
// 기존 토스트 제거
const existingToast = document.querySelector('.toast');
if (existingToast) {
existingToast.remove();
}
// 새 토스트 생성
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = message;
toast.setAttribute('role', 'status');
toast.setAttribute('aria-live', 'polite');
document.body.appendChild(toast);
// 자동 제거
setTimeout(() => {
toast.classList.add('hide');
setTimeout(() => {
toast.remove();
}, 200);
}, duration);
},
success(message) {
this.show('✓ ' + message);
},
error(message) {
this.show('✕ ' + message);
},
info(message) {
this.show(' ' + message);
}
};
// ============================================
// 3. Modal 다이얼로그
// ============================================
window.Modal = {
show(options) {
const {
title = '',
body = '',
confirmText = '확인',
cancelText = '취소',
onConfirm = () => {},
onCancel = () => {},
showCancel = true
} = options;
// 백드롭 생성
const backdrop = document.createElement('div');
backdrop.className = 'modal-backdrop';
// 모달 생성
const modal = document.createElement('div');
modal.className = 'modal';
modal.setAttribute('role', 'dialog');
modal.setAttribute('aria-labelledby', 'modal-title');
modal.setAttribute('aria-modal', 'true');
modal.innerHTML = `
<div class="modal__header">
<h2 class="modal__title" id="modal-title">${title}</h2>
<button class="modal__close" aria-label="닫기">
<span class="material-icons">close</span>
</button>
</div>
<div class="modal__body">
${body}
</div>
<div class="modal__footer">
${showCancel ? `<button class="btn btn-secondary btn-md modal__cancel">${cancelText}</button>` : ''}
<button class="btn btn-primary btn-md modal__confirm">${confirmText}</button>
</div>
`;
// 닫기 함수
const close = () => {
backdrop.remove();
modal.remove();
document.body.style.overflow = '';
};
// 이벤트 리스너
modal.querySelector('.modal__close').addEventListener('click', () => {
close();
onCancel();
});
if (showCancel) {
modal.querySelector('.modal__cancel').addEventListener('click', () => {
close();
onCancel();
});
}
modal.querySelector('.modal__confirm').addEventListener('click', () => {
close();
onConfirm();
});
backdrop.addEventListener('click', (e) => {
if (e.target === backdrop) {
close();
onCancel();
}
});
// 추가
document.body.appendChild(backdrop);
document.body.appendChild(modal);
document.body.style.overflow = 'hidden';
// 첫 번째 버튼에 포커스
modal.querySelector('button').focus();
},
confirm(title, body, onConfirm) {
this.show({
title,
body,
confirmText: '확인',
cancelText: '취소',
onConfirm,
showCancel: true
});
},
alert(title, body, onConfirm = () => {}) {
this.show({
title,
body,
confirmText: '확인',
onConfirm,
showCancel: false
});
}
};
// ============================================
// 4. Bottom Sheet
// ============================================
window.BottomSheet = {
show(content, options = {}) {
const {
onClose = () => {}
} = options;
// 백드롭 생성
const backdrop = document.createElement('div');
backdrop.className = 'bottom-sheet-backdrop';
// Bottom Sheet 생성
const sheet = document.createElement('div');
sheet.className = 'bottom-sheet';
sheet.setAttribute('role', 'dialog');
sheet.setAttribute('aria-modal', 'true');
sheet.innerHTML = `
<div class="bottom-sheet__handle"></div>
<div class="bottom-sheet__content">
${content}
</div>
`;
// 닫기 함수
const close = () => {
backdrop.remove();
sheet.remove();
document.body.style.overflow = '';
onClose();
};
// 이벤트 리스너
backdrop.addEventListener('click', close);
// Handle 드래그로 닫기 (간단한 구현)
let startY = 0;
const handle = sheet.querySelector('.bottom-sheet__handle');
handle.addEventListener('touchstart', (e) => {
startY = e.touches[0].clientY;
});
handle.addEventListener('touchend', (e) => {
const endY = e.changedTouches[0].clientY;
if (endY - startY > 50) { // 50px 이상 드래그하면 닫기
close();
}
});
// 추가
document.body.appendChild(backdrop);
document.body.appendChild(sheet);
document.body.style.overflow = 'hidden';
return { close };
}
};
// ============================================
// 5. 로딩 인디케이터
// ============================================
window.Loading = {
show(message = '처리중...') {
// 기존 로딩 제거
this.hide();
const overlay = document.createElement('div');
overlay.className = 'loading-overlay';
overlay.id = 'app-loading';
overlay.innerHTML = `
<div style="text-align: center; color: white;">
<div class="spinner" style="margin: 0 auto 16px;"></div>
<div class="body-m">${message}</div>
</div>
`;
document.body.appendChild(overlay);
document.body.style.overflow = 'hidden';
},
hide() {
const overlay = document.getElementById('app-loading');
if (overlay) {
overlay.remove();
document.body.style.overflow = '';
}
}
};
// ============================================
// 6. 폼 검증 유틸리티
// ============================================
window.FormValidator = {
// 이메일 검증
isValidEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
},
// 전화번호 검증 (010-XXXX-XXXX)
isValidPhone(phone) {
const re = /^010-\d{4}-\d{4}$/;
return re.test(phone);
},
// 사업자번호 검증 (XXX-XX-XXXXX)
isValidBusinessNumber(number) {
const re = /^\d{3}-\d{2}-\d{5}$/;
return re.test(number);
},
// 이름 검증 (2자 이상)
isValidName(name) {
return name && name.length >= 2;
},
// 필드에 에러 표시
showError(inputElement, message) {
inputElement.classList.add('error');
// 기존 에러 메시지 제거
const existingError = inputElement.parentElement.querySelector('.form-error');
if (existingError) {
existingError.remove();
}
// 새 에러 메시지 추가
if (message) {
const errorDiv = document.createElement('div');
errorDiv.className = 'form-error';
errorDiv.textContent = message;
errorDiv.setAttribute('role', 'alert');
inputElement.parentElement.appendChild(errorDiv);
}
},
// 필드에서 에러 제거
clearError(inputElement) {
inputElement.classList.remove('error');
const errorDiv = inputElement.parentElement.querySelector('.form-error');
if (errorDiv) {
errorDiv.remove();
}
},
// 전체 폼 검증
validateForm(formElement) {
let isValid = true;
const inputs = formElement.querySelectorAll('[required]');
inputs.forEach(input => {
if (!input.value.trim()) {
this.showError(input, '필수 입력 항목입니다.');
isValid = false;
} else {
this.clearError(input);
}
});
return isValid;
}
};
// ============================================
// 7. 네비게이션 유틸리티
// ============================================
window.Navigation = {
// 페이지 이동
goto(page) {
window.location.href = page;
},
// 뒤로가기
back() {
window.history.back();
},
// Bottom Navigation 활성화 상태 설정
updateBottomNav(activePage) {
const navItems = document.querySelectorAll('.bottom-nav__item');
navItems.forEach(item => {
if (item.getAttribute('data-page') === activePage) {
item.classList.add('active');
} else {
item.classList.remove('active');
}
});
}
};
// ============================================
// 8. 유틸리티 함수
// ============================================
window.Utils = {
// 숫자 포맷 (1000 -> 1,000)
formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
},
// 날짜 포맷 (YYYY-MM-DD)
formatDate(date) {
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
},
// 날짜 포맷 (YYYY.MM.DD)
formatDateDot(date) {
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${year}.${month}.${day}`;
},
// 랜덤 ID 생성
generateId() {
return 'id-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
},
// Debounce 함수
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
},
// AI 처리 시뮬레이션
simulateAI(duration = 3000) {
return new Promise(resolve => {
setTimeout(resolve, duration);
});
},
// 이미지 로드 체크
preloadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
};
// ============================================
// 9. 공통 이벤트 핸들러
// ============================================
// 뒤로가기 버튼
document.addEventListener('click', function(e) {
if (e.target.closest('.app-bar__back')) {
e.preventDefault();
window.Navigation.back();
}
});
// 입력 필드 실시간 검증
document.addEventListener('input', function(e) {
const input = e.target;
// 이메일 필드
if (input.type === 'email' && input.value) {
if (!window.FormValidator.isValidEmail(input.value)) {
window.FormValidator.showError(input, '올바른 이메일 형식을 입력하세요.');
} else {
window.FormValidator.clearError(input);
}
}
// 전화번호 필드
if (input.name === 'phone' && input.value) {
// 자동 하이픈 추가
let value = input.value.replace(/[^0-9]/g, '');
if (value.length > 3 && value.length <= 7) {
value = value.slice(0, 3) + '-' + value.slice(3);
} else if (value.length > 7) {
value = value.slice(0, 3) + '-' + value.slice(3, 7) + '-' + value.slice(7, 11);
}
input.value = value;
// 검증
if (value.length === 13) {
if (!window.FormValidator.isValidPhone(value)) {
window.FormValidator.showError(input, '올바른 전화번호 형식을 입력하세요.');
} else {
window.FormValidator.clearError(input);
}
}
}
// 사업자번호 필드
if (input.name === 'business_number' && input.value) {
// 자동 하이픈 추가
let value = input.value.replace(/[^0-9]/g, '');
if (value.length > 3 && value.length <= 5) {
value = value.slice(0, 3) + '-' + value.slice(3);
} else if (value.length > 5) {
value = value.slice(0, 3) + '-' + value.slice(3, 5) + '-' + value.slice(5, 10);
}
input.value = value;
// 검증
if (value.length === 12) {
if (!window.FormValidator.isValidBusinessNumber(value)) {
window.FormValidator.showError(input, '올바른 사업자번호 형식을 입력하세요.');
} else {
window.FormValidator.clearError(input);
}
}
}
});
// Material Icons 로드 확인
if (!document.querySelector('link[href*="material-icons"]')) {
const link = document.createElement('link');
link.href = 'https://fonts.googleapis.com/icon?family=Material+Icons';
link.rel = 'stylesheet';
document.head.appendChild(link);
}
// Pretendard 폰트 로드 확인
if (!document.querySelector('link[href*="pretendard"]')) {
const link = document.createElement('link');
link.href = 'https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css';
link.rel = 'stylesheet';
document.head.appendChild(link);
}
console.log('KT Event Marketing App - Common JS loaded');
})();