mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2025-12-06 12:06:24 +00:00
프로토~
This commit is contained in:
parent
58ab3bac44
commit
729b776600
324
design/uiux/prototype/01-회원가입.html
Normal file
324
design/uiux/prototype/01-회원가입.html
Normal 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>
|
||||
755
design/uiux/prototype/02-매장정보등록.html
Normal file
755
design/uiux/prototype/02-매장정보등록.html
Normal 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="매장의 특별한 점이나 강점을 알려주세요 예) 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>
|
||||
494
design/uiux/prototype/03-이벤트목적선택.html
Normal file
494
design/uiux/prototype/03-이벤트목적선택.html
Normal 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>
|
||||
413
design/uiux/prototype/04-AI트렌드분석결과.html
Normal file
413
design/uiux/prototype/04-AI트렌드분석결과.html
Normal 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>
|
||||
563
design/uiux/prototype/05-AI경품추천.html
Normal file
563
design/uiux/prototype/05-AI경품추천.html
Normal 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>
|
||||
507
design/uiux/prototype/06-AI참여방법설계.html
Normal file
507
design/uiux/prototype/06-AI참여방법설계.html
Normal 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>
|
||||
501
design/uiux/prototype/07-AI홍보문구생성.html
Normal file
501
design/uiux/prototype/07-AI홍보문구생성.html
Normal 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>
|
||||
614
design/uiux/prototype/08-이벤트기획안승인.html
Normal file
614
design/uiux/prototype/08-이벤트기획안승인.html
Normal 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>
|
||||
648
design/uiux/prototype/10-AI영상제작.html
Normal file
648
design/uiux/prototype/10-AI영상제작.html
Normal 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>
|
||||
483
design/uiux/prototype/11-SNS콘텐츠생성.html
Normal file
483
design/uiux/prototype/11-SNS콘텐츠생성.html
Normal 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>
|
||||
240
design/uiux/prototype/12-QR포스터생성.html
Normal file
240
design/uiux/prototype/12-QR포스터생성.html
Normal 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>
|
||||
325
design/uiux/prototype/13-콘텐츠편집.html
Normal file
325
design/uiux/prototype/13-콘텐츠편집.html
Normal 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>
|
||||
279
design/uiux/prototype/14-콘텐츠최종승인.html
Normal file
279
design/uiux/prototype/14-콘텐츠최종승인.html
Normal 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>
|
||||
364
design/uiux/prototype/16-배포진행상태.html
Normal file
364
design/uiux/prototype/16-배포진행상태.html
Normal 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>
|
||||
166
design/uiux/prototype/17-오프라인자료다운로드.html
Normal file
166
design/uiux/prototype/17-오프라인자료다운로드.html
Normal 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>
|
||||
432
design/uiux/prototype/20-당첨자명단관리.html
Normal file
432
design/uiux/prototype/20-당첨자명단관리.html
Normal 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>
|
||||
870
design/uiux/prototype/css/common.css
Normal file
870
design/uiux/prototype/css/common.css
Normal 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
552
design/uiux/prototype/js/common.js
vendored
Normal 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');
|
||||
|
||||
})();
|
||||
Loading…
x
Reference in New Issue
Block a user