프로토타입 번호

This commit is contained in:
doyeon 2025-10-21 11:23:08 +09:00
parent 380a4a5319
commit c441df22e3
23 changed files with 501 additions and 1061 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

View File

@ -4,140 +4,13 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI 이미지 생성 - KT 이벤트 마케팅</title>
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/common.css">
<style>
/* CSS Variables */
:root {
/* Colors */
--color-primary: #E31E24;
--color-primary-dark: #B71C1C;
--color-primary-light: #FFEBEE;
--color-secondary: #FF6B6B;
--color-secondary-dark: #FF5252;
--color-secondary-light: #FFE9E9;
--color-success: #4CAF50;
--color-success-light: #E8F5E9;
--color-warning: #FF9800;
--color-warning-light: #FFF3E0;
--color-error: #F44336;
--color-error-light: #FFEBEE;
--color-info: #2196F3;
--color-info-light: #E3F2FD;
/* Grayscale */
--color-white: #FFFFFF;
--color-gray-50: #FAFAFA;
--color-gray-100: #F5F5F5;
--color-gray-200: #EEEEEE;
--color-gray-300: #E0E0E0;
--color-gray-400: #BDBDBD;
--color-gray-500: #9E9E9E;
--color-gray-600: #757575;
--color-gray-700: #616161;
--color-gray-800: #424242;
--color-gray-900: #212121;
--color-black: #000000;
/* Text Colors */
--color-text-primary: #212121;
--color-text-secondary: #757575;
--color-text-tertiary: #9E9E9E;
--color-text-disabled: #BDBDBD;
--color-text-inverse: #FFFFFF;
/* Border Colors */
--color-border-light: #E0E0E0;
--color-border-medium: #BDBDBD;
--color-border-dark: #757575;
/* Background Colors */
--color-bg-primary: #FFFFFF;
--color-bg-secondary: #F5F5F5;
--color-bg-tertiary: #FAFAFA;
/* Spacing (4px base unit) */
--spacing-xs: 4px;
--spacing-s: 8px;
--spacing-m: 16px;
--spacing-l: 24px;
--spacing-xl: 32px;
--spacing-2xl: 48px;
--spacing-3xl: 64px;
/* Typography */
--font-family-primary: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-size-xs: 12px;
--font-size-s: 14px;
--font-size-m: 16px;
--font-size-l: 18px;
--font-size-xl: 20px;
--font-size-2xl: 24px;
--font-size-3xl: 32px;
--font-size-4xl: 40px;
--font-weight-regular: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--line-height-tight: 1.2;
--line-height-normal: 1.5;
--line-height-relaxed: 1.75;
/* Border Radius */
--radius-s: 4px;
--radius-m: 8px;
--radius-l: 12px;
--radius-xl: 16px;
--radius-2xl: 24px;
--radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
/* Transitions */
--transition-fast: 150ms ease-in-out;
--transition-normal: 250ms ease-in-out;
--transition-slow: 350ms ease-in-out;
/* Z-index */
--z-dropdown: 1000;
--z-sticky: 1020;
--z-fixed: 1030;
--z-modal-backdrop: 1040;
--z-modal: 1050;
--z-popover: 1060;
--z-tooltip: 1070;
}
/* Reset & Base Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-family-primary);
font-size: var(--font-size-m);
line-height: var(--line-height-normal);
color: var(--color-text-primary);
background: var(--color-bg-secondary);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
button {
font-family: inherit;
cursor: pointer;
}
/* Layout */
.image-container {
min-height: 100vh;
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
background: linear-gradient(135deg, #F8F9FF 0%, #FFFFFF 100%);
padding-bottom: var(--spacing-2xl);
}
@ -175,126 +48,307 @@
padding: var(--spacing-l) var(--spacing-m);
}
/* Loading State */
.loading-state {
/* Prompt Section */
.prompt-section {
background: var(--color-white);
border-radius: var(--radius-l);
padding: var(--spacing-2xl);
padding: var(--spacing-m);
margin-bottom: var(--spacing-l);
box-shadow: var(--shadow-sm);
}
.prompt-section__label {
font-size: var(--font-size-m);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
margin-bottom: var(--spacing-s);
display: flex;
align-items: center;
gap: var(--spacing-xs);
}
.prompt-section__textarea {
width: 100%;
min-height: 100px;
padding: var(--spacing-m);
border: 1px solid var(--color-border-light);
border-radius: var(--radius-m);
font-size: var(--font-size-m);
font-family: var(--font-family);
resize: vertical;
transition: all var(--transition-normal);
margin-bottom: var(--spacing-s);
}
.prompt-section__textarea:focus {
outline: none;
border-color: var(--color-secondary);
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1);
}
.prompt-section__hint {
font-size: var(--font-size-xs);
color: var(--color-text-tertiary);
margin-bottom: var(--spacing-m);
}
.prompt-section__styles {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--spacing-s);
margin-bottom: var(--spacing-m);
}
.style-chip {
padding: var(--spacing-s);
border: 2px solid var(--color-border-light);
border-radius: var(--radius-m);
text-align: center;
font-size: var(--font-size-s);
cursor: pointer;
transition: all var(--transition-normal);
}
.style-chip:hover {
border-color: var(--color-secondary);
}
.style-chip--selected {
border-color: var(--color-secondary);
background: linear-gradient(135deg, #F0F7FF 0%, #FFFFFF 100%);
color: var(--color-secondary);
font-weight: var(--font-weight-semibold);
}
.generate-button {
width: 100%;
padding: var(--spacing-m);
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-dark) 100%);
color: var(--color-white);
border: none;
border-radius: var(--radius-m);
font-size: var(--font-size-m);
font-weight: var(--font-weight-semibold);
cursor: pointer;
transition: all var(--transition-normal);
}
.generate-button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.loading-state__icon {
font-size: 64px;
margin-bottom: var(--spacing-m);
animation: pulse 2s ease-in-out infinite;
.generate-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
.generate-button--loading {
position: relative;
color: transparent;
}
.loading-state__title {
font-size: var(--font-size-l);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
margin-bottom: var(--spacing-s);
.generate-button--loading:after {
content: '';
position: absolute;
width: 20px;
height: 20px;
top: 50%;
left: 50%;
margin-left: -10px;
margin-top: -10px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: var(--color-white);
animation: spinner 0.6s linear infinite;
}
.loading-state__text {
font-size: var(--font-size-s);
color: var(--color-text-secondary);
margin-bottom: var(--spacing-l);
}
.progress-bar {
width: 100%;
height: 8px;
background: var(--color-gray-200);
border-radius: var(--radius-full);
overflow: hidden;
margin-bottom: var(--spacing-s);
}
.progress-bar__fill {
height: 100%;
background: linear-gradient(90deg, var(--color-secondary), var(--color-primary));
border-radius: var(--radius-full);
transition: width 0.3s ease;
}
.progress-text {
font-size: var(--font-size-s);
color: var(--color-text-secondary);
}
/* Results */
.image-results {
/* Loading State */
.loading-state {
display: none;
text-align: center;
padding: var(--spacing-2xl) var(--spacing-m);
}
.image-results--visible {
.loading-state--visible {
display: block;
}
.loading-animation {
width: 80px;
height: 80px;
margin: 0 auto var(--spacing-l);
position: relative;
}
.loading-animation__circle {
position: absolute;
width: 100%;
height: 100%;
border: 4px solid rgba(0, 102, 255, 0.1);
border-radius: 50%;
border-top-color: var(--color-secondary);
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-animation__icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 32px;
}
.loading-state__text {
font-size: var(--font-size-l);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
/* Results Section */
.results-section {
display: none;
}
.results-section--visible {
display: block;
}
.results-section__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-m);
}
.results-section__title {
font-size: var(--font-size-l);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
}
.results-section__regenerate {
background: none;
border: 1px solid var(--color-border-light);
color: var(--color-text-secondary);
padding: var(--spacing-xs) var(--spacing-m);
border-radius: var(--radius-m);
font-size: var(--font-size-s);
cursor: pointer;
transition: all var(--transition-normal);
}
.results-section__regenerate:hover {
border-color: var(--color-secondary);
color: var(--color-secondary);
}
/* Image Grid */
.image-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--spacing-m);
margin-bottom: var(--spacing-l);
}
.image-item {
position: relative;
aspect-ratio: 1;
background: linear-gradient(135deg, #FFE9E9 0%, #FFF5F5 100%);
/* Image Card */
.image-card {
background: var(--color-white);
border: 2px solid var(--color-border-light);
border-radius: var(--radius-l);
overflow: hidden;
cursor: pointer;
border: 3px solid transparent;
transition: all var(--transition-normal);
position: relative;
}
.image-item:hover {
transform: translateY(-4px);
.image-card:hover {
border-color: var(--color-secondary);
box-shadow: var(--shadow-md);
transform: translateY(-2px);
}
.image-card--selected {
border-color: var(--color-secondary);
box-shadow: var(--shadow-lg);
}
.image-item--selected {
border-color: var(--color-secondary);
box-shadow: var(--shadow-xl);
}
.image-item__preview {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 64px;
}
.image-item__checkbox {
.image-card__badge {
position: absolute;
top: var(--spacing-s);
right: var(--spacing-s);
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-dark) 100%);
color: var(--color-white);
padding: var(--spacing-xs) var(--spacing-s);
border-radius: var(--radius-s);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
box-shadow: var(--shadow-md);
z-index: 1;
}
.image-card__checkbox {
position: absolute;
top: var(--spacing-s);
left: var(--spacing-s);
width: 24px;
height: 24px;
background: var(--color-white);
border: 2px solid var(--color-border-light);
border-radius: 50%;
z-index: 1;
cursor: pointer;
}
.image-card__preview {
width: 100%;
aspect-ratio: 16 / 9;
background: linear-gradient(135deg, #E0E7FF 0%, #F0F7FF 100%);
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
position: relative;
}
.image-card__info {
padding: var(--spacing-m);
}
.image-card__title {
font-size: var(--font-size-m);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
margin-bottom: var(--spacing-xs);
}
.image-card__style {
font-size: var(--font-size-xs);
color: var(--color-white);
color: var(--color-text-secondary);
margin-bottom: var(--spacing-s);
}
.image-card__actions {
display: flex;
gap: var(--spacing-xs);
padding-top: var(--spacing-s);
border-top: 1px solid var(--color-border-light);
}
.image-card__action-btn {
flex: 1;
padding: var(--spacing-xs);
background: var(--color-white);
border: 1px solid var(--color-border-light);
border-radius: var(--radius-s);
font-size: var(--font-size-xs);
cursor: pointer;
transition: all var(--transition-normal);
}
.image-item--selected .image-item__checkbox {
background: var(--color-secondary);
.image-card__action-btn:hover {
border-color: var(--color-secondary);
color: var(--color-secondary);
}
/* Next Button */
@ -359,6 +413,10 @@
}
.image-grid {
grid-template-columns: repeat(2, 1fr);
}
.prompt-section__styles {
grid-template-columns: repeat(4, 1fr);
}
}
@ -379,163 +437,287 @@
</button>
<h1 class="image-header__title">AI 이미지 생성</h1>
<p class="image-header__subtitle">이벤트에 사용할 매력적인 이미지를 생성해 드려</p>
<p class="image-header__subtitle">이벤트에 활용할 이미지를 AI로 생성해 보세</p>
</header>
<!-- Content -->
<main class="image-content">
<!-- Prompt Section -->
<div class="prompt-section" id="promptSection">
<label class="prompt-section__label">
✨ 원하는 이미지 설명
</label>
<textarea
class="prompt-section__textarea"
id="promptInput"
placeholder="예: 친구들이 함께 행복하게 웃고 있는 모습, 밝고 따뜻한 분위기"
maxlength="500"
></textarea>
<p class="prompt-section__hint">
생성하고 싶은 이미지를 자세히 설명해 주세요 (최대 500자)
</p>
<label class="prompt-section__label">
🎨 이미지 스타일
</label>
<div class="prompt-section__styles">
<div class="style-chip style-chip--selected" data-style="modern">
🌟 모던
</div>
<div class="style-chip" data-style="warm">
☀️ 따뜻한
</div>
<div class="style-chip" data-style="vibrant">
🎨 생동감
</div>
<div class="style-chip" data-style="minimal">
✨ 미니멀
</div>
</div>
<button class="generate-button" id="generateButton">
🎨 이미지 생성하기
</button>
</div>
<!-- Loading State -->
<div class="loading-state" id="loadingState">
<div class="loading-state__icon">🎨</div>
<h2 class="loading-state__title">AI가 이미지를 생성하고 있어요</h2>
<p class="loading-state__text">
이벤트 정보를 바탕으로 최적의 이미지를 생성 중입니다.<br>
잠시만 기다려 주세요...
<div class="loading-animation">
<div class="loading-animation__circle"></div>
<div class="loading-animation__icon">🎨</div>
</div>
<p class="loading-state__text" id="loadingText">
AI가 이미지를 생성하고 있어요...
</p>
<div class="progress-bar">
<div class="progress-bar__fill" id="progressBar" style="width: 0%"></div>
</div>
<p class="progress-text" id="progressText">0%</p>
</div>
<!-- Results -->
<div class="image-results" id="imageResults">
<!-- Results Section -->
<div class="results-section" id="resultsSection">
<div class="results-section__header">
<h2 class="results-section__title">생성된 이미지</h2>
<button class="results-section__regenerate" id="regenerateButton">
🔄 다시 생성
</button>
</div>
<!-- Image Grid -->
<div class="image-grid" id="imageGrid">
<!-- Images will be inserted here -->
<!-- Image cards will be inserted here -->
</div>
<!-- Next Button -->
<button class="next-button" id="nextButton" disabled>
선택 완료 (최소 1개)
다음 단계로
</button>
</div>
</main>
</div>
<script src="js/common.js"></script>
<script>
// Utility functions
const $ = (selector) => document.querySelector(selector);
const addClass = (el, className) => el?.classList.add(className);
const removeClass = (el, className) => el?.classList.remove(className);
const hasClass = (el, className) => el?.classList.contains(className);
const storage = {
get: (key) => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : null;
} catch (e) {
console.error('Storage get error:', e);
return null;
}
},
set: (key, value) => {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (e) {
console.error('Storage set error:', e);
}
}
};
const { $, addClass, removeClass, storage } = window.CommonUtils;
// Elements
const promptSection = $('#promptSection');
const promptInput = $('#promptInput');
const styleChips = document.querySelectorAll('.style-chip');
const generateButton = $('#generateButton');
const loadingState = $('#loadingState');
const progressBar = $('#progressBar');
const progressText = $('#progressText');
const imageResults = $('#imageResults');
const loadingText = $('#loadingText');
const resultsSection = $('#resultsSection');
const imageGrid = $('#imageGrid');
const regenerateButton = $('#regenerateButton');
const nextButton = $('#nextButton');
// State
const selectedImages = new Set();
let currentProgress = 0;
let selectedStyle = 'modern';
let selectedImages = [];
const generatedImages = [];
// Mock generated images
const generatedImages = [
{ id: 1, icon: '🎉', alt: '친구 초대 이벤트 이미지 1' },
{ id: 2, icon: '🎊', alt: '친구 초대 이벤트 이미지 2' },
{ id: 3, icon: '🎈', alt: '친구 초대 이벤트 이미지 3' },
{ id: 4, icon: '🎁', alt: '친구 초대 이벤트 이미지 4' },
{ id: 5, icon: '🌟', alt: '친구 초대 이벤트 이미지 5' },
{ id: 6, icon: '✨', alt: '친구 초대 이벤트 이미지 6' },
{ id: 7, icon: '💝', alt: '친구 초대 이벤트 이미지 7' },
{ id: 8, icon: '🎯', alt: '친구 초대 이벤트 이미지 8' }
// Handle style selection
styleChips.forEach(chip => {
chip.addEventListener('click', () => {
styleChips.forEach(c => removeClass(c, 'style-chip--selected'));
addClass(chip, 'style-chip--selected');
selectedStyle = chip.getAttribute('data-style');
});
});
// Generate images
generateButton.addEventListener('click', () => {
const prompt = promptInput.value.trim();
if (!prompt) {
alert('이미지 설명을 입력해 주세요.');
return;
}
generateImages(prompt, selectedStyle);
});
// Regenerate images
regenerateButton.addEventListener('click', () => {
const prompt = promptInput.value.trim();
generateImages(prompt, selectedStyle);
});
// Generate images function
function generateImages(prompt, style) {
// Hide prompt section and results
promptSection.style.display = 'none';
removeClass(resultsSection, 'results-section--visible');
// Show loading
addClass(loadingState, 'loading-state--visible');
// Simulate AI generation
const loadingTexts = [
'AI가 이미지를 생성하고 있어요...',
'이미지 구도를 설계하고 있어요...',
'색상과 스타일을 조정하고 있어요...',
'마지막 마무리 중이에요...'
];
// Simulate AI image generation
function simulateGeneration() {
const interval = setInterval(() => {
currentProgress += 12.5;
if (currentProgress >= 100) {
currentProgress = 100;
clearInterval(interval);
setTimeout(showResults, 500);
let step = 0;
const loadingInterval = setInterval(() => {
if (step < loadingTexts.length) {
loadingText.textContent = loadingTexts[step];
step++;
}
updateProgress(currentProgress);
}, 400);
}, 1200);
// Show results after delay
setTimeout(() => {
clearInterval(loadingInterval);
removeClass(loadingState, 'loading-state--visible');
renderImages(prompt, style);
addClass(resultsSection, 'results-section--visible');
promptSection.style.display = 'block';
}, 5000);
}
function updateProgress(progress) {
progressBar.style.width = progress + '%';
progressText.textContent = Math.round(progress) + '%';
// Render generated images
function renderImages(prompt, style) {
const imageVariations = [
{
id: 1,
title: '옵션 1',
style: style === 'modern' ? '모던 스타일' :
style === 'warm' ? '따뜻한 스타일' :
style === 'vibrant' ? '생동감 있는 스타일' :
'미니멀 스타일',
icon: '🎉',
recommended: true
},
{
id: 2,
title: '옵션 2',
style: style === 'modern' ? '모던 스타일' :
style === 'warm' ? '따뜻한 스타일' :
style === 'vibrant' ? '생동감 있는 스타일' :
'미니멀 스타일',
icon: '🎊',
recommended: false
},
{
id: 3,
title: '옵션 3',
style: style === 'modern' ? '모던 스타일' :
style === 'warm' ? '따뜻한 스타일' :
style === 'vibrant' ? '생동감 있는 스타일' :
'미니멀 스타일',
icon: '🎈',
recommended: false
},
{
id: 4,
title: '옵션 4',
style: style === 'modern' ? '모던 스타일' :
style === 'warm' ? '따뜻한 스타일' :
style === 'vibrant' ? '생동감 있는 스타일' :
'미니멀 스타일',
icon: '🎁',
recommended: false
}
];
function showResults() {
loadingState.style.display = 'none';
addClass(imageResults, 'image-results--visible');
renderImages();
}
function renderImages() {
imageGrid.innerHTML = generatedImages.map(image => `
<div class="image-item" data-id="${image.id}">
<div class="image-item__preview">${image.icon}</div>
<div class="image-item__checkbox"></div>
imageGrid.innerHTML = imageVariations.map(image => `
<div class="image-card" data-id="${image.id}">
${image.recommended ? '<div class="image-card__badge">🤖 AI 추천</div>' : ''}
<input type="checkbox" class="image-card__checkbox" value="${image.id}">
<div class="image-card__preview">${image.icon}</div>
<div class="image-card__info">
<h3 class="image-card__title">${image.title}</h3>
<p class="image-card__style">${image.style}</p>
<div class="image-card__actions">
<button class="image-card__action-btn" onclick="downloadImage(${image.id})">
💾 저장
</button>
<button class="image-card__action-btn" onclick="editImage(${image.id})">
✏️ 수정
</button>
</div>
</div>
</div>
`).join('');
// Add click listeners
const items = imageGrid.querySelectorAll('.image-item');
items.forEach(item => {
item.addEventListener('click', () => {
const imageId = parseInt(item.getAttribute('data-id'));
toggleImage(imageId, item);
// Add event listeners
const cards = document.querySelectorAll('.image-card');
cards.forEach(card => {
const checkbox = card.querySelector('.image-card__checkbox');
card.addEventListener('click', (e) => {
if (e.target.closest('.image-card__action-btn')) return;
checkbox.checked = !checkbox.checked;
toggleImageSelection(card, checkbox.checked);
});
checkbox.addEventListener('click', (e) => {
e.stopPropagation();
toggleImageSelection(card, checkbox.checked);
});
});
}
function toggleImage(imageId, element) {
if (selectedImages.has(imageId)) {
selectedImages.delete(imageId);
removeClass(element, 'image-item--selected');
} else {
selectedImages.add(imageId);
addClass(element, 'image-item--selected');
// Toggle image selection
function toggleImageSelection(card, isSelected) {
const id = parseInt(card.getAttribute('data-id'));
if (isSelected) {
addClass(card, 'image-card--selected');
if (!selectedImages.includes(id)) {
selectedImages.push(id);
}
updateNextButton();
} else {
removeClass(card, 'image-card--selected');
selectedImages = selectedImages.filter(imageId => imageId !== id);
}
function updateNextButton() {
const count = selectedImages.size;
if (count > 0) {
nextButton.disabled = false;
nextButton.textContent = `선택 완료 (${count}개)`;
} else {
nextButton.disabled = true;
nextButton.textContent = '선택 완료 (최소 1개)';
}
nextButton.disabled = selectedImages.length === 0;
}
// Download image
window.downloadImage = function(id) {
alert(`이미지 ${id}를 다운로드했습니다.`);
};
// Edit image
window.editImage = function(id) {
alert(`이미지 ${id} 편집 기능은 다음 버전에서 제공됩니다.`);
};
// Handle next button
nextButton.addEventListener('click', () => {
if (selectedImages.size === 0) return;
if (selectedImages.length === 0) return;
// Save selected images
const eventContent = storage.get('eventContent') || {};
eventContent.generatedImages = {
selectedImages: Array.from(selectedImages),
images: generatedImages.filter(img => selectedImages.has(img.id)),
createdAt: new Date().toISOString()
prompt: promptInput.value.trim(),
style: selectedStyle,
selectedImages: selectedImages,
generatedAt: new Date().toISOString()
};
storage.set('eventContent', eventContent);
@ -545,15 +727,16 @@
// Navigate to next screen
setTimeout(() => {
window.location.href = '10-AI영상제작.html';
window.location.href = '11-SNS콘텐츠생성.html';
}, 800);
});
// Initialize
// Auto-fill prompt from event draft
window.addEventListener('load', () => {
setTimeout(() => {
simulateGeneration();
}, 500);
const eventDraft = storage.get('eventDraft') || {};
if (eventDraft.promotionalCopy && !promptInput.value) {
promptInput.value = `${eventDraft.promotionalCopy.headline} 이미지`;
}
});
</script>
</body>

View File

@ -1,743 +0,0 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI 이미지 생성 - KT 이벤트 마케팅</title>
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/common.css">
<style>
/* Layout */
.image-container {
min-height: 100vh;
background: linear-gradient(135deg, #F8F9FF 0%, #FFFFFF 100%);
padding-bottom: var(--spacing-2xl);
}
/* Header */
.image-header {
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-dark) 100%);
color: var(--color-white);
padding: var(--spacing-xl) var(--spacing-m);
box-shadow: var(--shadow-lg);
}
.image-header__back {
background: none;
border: none;
color: var(--color-white);
font-size: var(--font-size-xl);
padding: 0;
margin-bottom: var(--spacing-m);
cursor: pointer;
}
.image-header__title {
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-bold);
margin-bottom: var(--spacing-xs);
}
.image-header__subtitle {
font-size: var(--font-size-m);
opacity: 0.9;
}
/* Content */
.image-content {
padding: var(--spacing-l) var(--spacing-m);
}
/* Prompt Section */
.prompt-section {
background: var(--color-white);
border-radius: var(--radius-l);
padding: var(--spacing-m);
margin-bottom: var(--spacing-l);
box-shadow: var(--shadow-sm);
}
.prompt-section__label {
font-size: var(--font-size-m);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
margin-bottom: var(--spacing-s);
display: flex;
align-items: center;
gap: var(--spacing-xs);
}
.prompt-section__textarea {
width: 100%;
min-height: 100px;
padding: var(--spacing-m);
border: 1px solid var(--color-border-light);
border-radius: var(--radius-m);
font-size: var(--font-size-m);
font-family: var(--font-family);
resize: vertical;
transition: all var(--transition-normal);
margin-bottom: var(--spacing-s);
}
.prompt-section__textarea:focus {
outline: none;
border-color: var(--color-secondary);
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1);
}
.prompt-section__hint {
font-size: var(--font-size-xs);
color: var(--color-text-tertiary);
margin-bottom: var(--spacing-m);
}
.prompt-section__styles {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--spacing-s);
margin-bottom: var(--spacing-m);
}
.style-chip {
padding: var(--spacing-s);
border: 2px solid var(--color-border-light);
border-radius: var(--radius-m);
text-align: center;
font-size: var(--font-size-s);
cursor: pointer;
transition: all var(--transition-normal);
}
.style-chip:hover {
border-color: var(--color-secondary);
}
.style-chip--selected {
border-color: var(--color-secondary);
background: linear-gradient(135deg, #F0F7FF 0%, #FFFFFF 100%);
color: var(--color-secondary);
font-weight: var(--font-weight-semibold);
}
.generate-button {
width: 100%;
padding: var(--spacing-m);
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-dark) 100%);
color: var(--color-white);
border: none;
border-radius: var(--radius-m);
font-size: var(--font-size-m);
font-weight: var(--font-weight-semibold);
cursor: pointer;
transition: all var(--transition-normal);
}
.generate-button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.generate-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.generate-button--loading {
position: relative;
color: transparent;
}
.generate-button--loading:after {
content: '';
position: absolute;
width: 20px;
height: 20px;
top: 50%;
left: 50%;
margin-left: -10px;
margin-top: -10px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: var(--color-white);
animation: spinner 0.6s linear infinite;
}
/* Loading State */
.loading-state {
display: none;
text-align: center;
padding: var(--spacing-2xl) var(--spacing-m);
}
.loading-state--visible {
display: block;
}
.loading-animation {
width: 80px;
height: 80px;
margin: 0 auto var(--spacing-l);
position: relative;
}
.loading-animation__circle {
position: absolute;
width: 100%;
height: 100%;
border: 4px solid rgba(0, 102, 255, 0.1);
border-radius: 50%;
border-top-color: var(--color-secondary);
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-animation__icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 32px;
}
.loading-state__text {
font-size: var(--font-size-l);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
/* Results Section */
.results-section {
display: none;
}
.results-section--visible {
display: block;
}
.results-section__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-m);
}
.results-section__title {
font-size: var(--font-size-l);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
}
.results-section__regenerate {
background: none;
border: 1px solid var(--color-border-light);
color: var(--color-text-secondary);
padding: var(--spacing-xs) var(--spacing-m);
border-radius: var(--radius-m);
font-size: var(--font-size-s);
cursor: pointer;
transition: all var(--transition-normal);
}
.results-section__regenerate:hover {
border-color: var(--color-secondary);
color: var(--color-secondary);
}
/* Image Grid */
.image-grid {
display: grid;
gap: var(--spacing-m);
margin-bottom: var(--spacing-l);
}
/* Image Card */
.image-card {
background: var(--color-white);
border: 2px solid var(--color-border-light);
border-radius: var(--radius-l);
overflow: hidden;
cursor: pointer;
transition: all var(--transition-normal);
position: relative;
}
.image-card:hover {
border-color: var(--color-secondary);
box-shadow: var(--shadow-md);
transform: translateY(-2px);
}
.image-card--selected {
border-color: var(--color-secondary);
box-shadow: var(--shadow-lg);
}
.image-card__badge {
position: absolute;
top: var(--spacing-s);
right: var(--spacing-s);
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-dark) 100%);
color: var(--color-white);
padding: var(--spacing-xs) var(--spacing-s);
border-radius: var(--radius-s);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
box-shadow: var(--shadow-md);
z-index: 1;
}
.image-card__checkbox {
position: absolute;
top: var(--spacing-s);
left: var(--spacing-s);
width: 24px;
height: 24px;
z-index: 1;
cursor: pointer;
}
.image-card__preview {
width: 100%;
aspect-ratio: 16 / 9;
background: linear-gradient(135deg, #E0E7FF 0%, #F0F7FF 100%);
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
position: relative;
}
.image-card__info {
padding: var(--spacing-m);
}
.image-card__title {
font-size: var(--font-size-m);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
margin-bottom: var(--spacing-xs);
}
.image-card__style {
font-size: var(--font-size-xs);
color: var(--color-text-secondary);
margin-bottom: var(--spacing-s);
}
.image-card__actions {
display: flex;
gap: var(--spacing-xs);
padding-top: var(--spacing-s);
border-top: 1px solid var(--color-border-light);
}
.image-card__action-btn {
flex: 1;
padding: var(--spacing-xs);
background: var(--color-white);
border: 1px solid var(--color-border-light);
border-radius: var(--radius-s);
font-size: var(--font-size-xs);
cursor: pointer;
transition: all var(--transition-normal);
}
.image-card__action-btn:hover {
border-color: var(--color-secondary);
color: var(--color-secondary);
}
/* Next Button */
.next-button {
width: 100%;
padding: var(--spacing-m);
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-dark) 100%);
color: var(--color-white);
border: none;
border-radius: var(--radius-m);
font-size: var(--font-size-m);
font-weight: var(--font-weight-semibold);
cursor: pointer;
transition: all var(--transition-normal);
box-shadow: var(--shadow-md);
}
.next-button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.next-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.next-button--loading {
position: relative;
color: transparent;
}
.next-button--loading:after {
content: '';
position: absolute;
width: 20px;
height: 20px;
top: 50%;
left: 50%;
margin-left: -10px;
margin-top: -10px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: var(--color-white);
animation: spinner 0.6s linear infinite;
}
@keyframes spinner {
to { transform: rotate(360deg); }
}
/* Tablet */
@media (min-width: 768px) {
.image-header {
padding: var(--spacing-2xl) var(--spacing-xl);
}
.image-content {
max-width: 720px;
margin: 0 auto;
padding: var(--spacing-xl);
}
.image-grid {
grid-template-columns: repeat(2, 1fr);
}
.prompt-section__styles {
grid-template-columns: repeat(4, 1fr);
}
}
/* Desktop */
@media (min-width: 1024px) {
.image-content {
max-width: 960px;
}
}
</style>
</head>
<body>
<div class="image-container">
<!-- Header -->
<header class="image-header">
<button class="image-header__back" onclick="history.back()">
</button>
<h1 class="image-header__title">AI 이미지 생성</h1>
<p class="image-header__subtitle">이벤트에 활용할 이미지를 AI로 생성해 보세요</p>
</header>
<!-- Content -->
<main class="image-content">
<!-- Prompt Section -->
<div class="prompt-section" id="promptSection">
<label class="prompt-section__label">
✨ 원하는 이미지 설명
</label>
<textarea
class="prompt-section__textarea"
id="promptInput"
placeholder="예: 친구들이 함께 행복하게 웃고 있는 모습, 밝고 따뜻한 분위기"
maxlength="500"
></textarea>
<p class="prompt-section__hint">
생성하고 싶은 이미지를 자세히 설명해 주세요 (최대 500자)
</p>
<label class="prompt-section__label">
🎨 이미지 스타일
</label>
<div class="prompt-section__styles">
<div class="style-chip style-chip--selected" data-style="modern">
🌟 모던
</div>
<div class="style-chip" data-style="warm">
☀️ 따뜻한
</div>
<div class="style-chip" data-style="vibrant">
🎨 생동감
</div>
<div class="style-chip" data-style="minimal">
✨ 미니멀
</div>
</div>
<button class="generate-button" id="generateButton">
🎨 이미지 생성하기
</button>
</div>
<!-- Loading State -->
<div class="loading-state" id="loadingState">
<div class="loading-animation">
<div class="loading-animation__circle"></div>
<div class="loading-animation__icon">🎨</div>
</div>
<p class="loading-state__text" id="loadingText">
AI가 이미지를 생성하고 있어요...
</p>
</div>
<!-- Results Section -->
<div class="results-section" id="resultsSection">
<div class="results-section__header">
<h2 class="results-section__title">생성된 이미지</h2>
<button class="results-section__regenerate" id="regenerateButton">
🔄 다시 생성
</button>
</div>
<!-- Image Grid -->
<div class="image-grid" id="imageGrid">
<!-- Image cards will be inserted here -->
</div>
<!-- Next Button -->
<button class="next-button" id="nextButton" disabled>
다음 단계로
</button>
</div>
</main>
</div>
<script src="js/common.js"></script>
<script>
const { $, addClass, removeClass, storage } = window.CommonUtils;
// Elements
const promptSection = $('#promptSection');
const promptInput = $('#promptInput');
const styleChips = document.querySelectorAll('.style-chip');
const generateButton = $('#generateButton');
const loadingState = $('#loadingState');
const loadingText = $('#loadingText');
const resultsSection = $('#resultsSection');
const imageGrid = $('#imageGrid');
const regenerateButton = $('#regenerateButton');
const nextButton = $('#nextButton');
// State
let selectedStyle = 'modern';
let selectedImages = [];
const generatedImages = [];
// Handle style selection
styleChips.forEach(chip => {
chip.addEventListener('click', () => {
styleChips.forEach(c => removeClass(c, 'style-chip--selected'));
addClass(chip, 'style-chip--selected');
selectedStyle = chip.getAttribute('data-style');
});
});
// Generate images
generateButton.addEventListener('click', () => {
const prompt = promptInput.value.trim();
if (!prompt) {
alert('이미지 설명을 입력해 주세요.');
return;
}
generateImages(prompt, selectedStyle);
});
// Regenerate images
regenerateButton.addEventListener('click', () => {
const prompt = promptInput.value.trim();
generateImages(prompt, selectedStyle);
});
// Generate images function
function generateImages(prompt, style) {
// Hide prompt section and results
promptSection.style.display = 'none';
removeClass(resultsSection, 'results-section--visible');
// Show loading
addClass(loadingState, 'loading-state--visible');
// Simulate AI generation
const loadingTexts = [
'AI가 이미지를 생성하고 있어요...',
'이미지 구도를 설계하고 있어요...',
'색상과 스타일을 조정하고 있어요...',
'마지막 마무리 중이에요...'
];
let step = 0;
const loadingInterval = setInterval(() => {
if (step < loadingTexts.length) {
loadingText.textContent = loadingTexts[step];
step++;
}
}, 1200);
// Show results after delay
setTimeout(() => {
clearInterval(loadingInterval);
removeClass(loadingState, 'loading-state--visible');
renderImages(prompt, style);
addClass(resultsSection, 'results-section--visible');
promptSection.style.display = 'block';
}, 5000);
}
// Render generated images
function renderImages(prompt, style) {
const imageVariations = [
{
id: 1,
title: '옵션 1',
style: style === 'modern' ? '모던 스타일' :
style === 'warm' ? '따뜻한 스타일' :
style === 'vibrant' ? '생동감 있는 스타일' :
'미니멀 스타일',
icon: '🎉',
recommended: true
},
{
id: 2,
title: '옵션 2',
style: style === 'modern' ? '모던 스타일' :
style === 'warm' ? '따뜻한 스타일' :
style === 'vibrant' ? '생동감 있는 스타일' :
'미니멀 스타일',
icon: '🎊',
recommended: false
},
{
id: 3,
title: '옵션 3',
style: style === 'modern' ? '모던 스타일' :
style === 'warm' ? '따뜻한 스타일' :
style === 'vibrant' ? '생동감 있는 스타일' :
'미니멀 스타일',
icon: '🎈',
recommended: false
},
{
id: 4,
title: '옵션 4',
style: style === 'modern' ? '모던 스타일' :
style === 'warm' ? '따뜻한 스타일' :
style === 'vibrant' ? '생동감 있는 스타일' :
'미니멀 스타일',
icon: '🎁',
recommended: false
}
];
imageGrid.innerHTML = imageVariations.map(image => `
<div class="image-card" data-id="${image.id}">
${image.recommended ? '<div class="image-card__badge">🤖 AI 추천</div>' : ''}
<input type="checkbox" class="image-card__checkbox" value="${image.id}">
<div class="image-card__preview">${image.icon}</div>
<div class="image-card__info">
<h3 class="image-card__title">${image.title}</h3>
<p class="image-card__style">${image.style}</p>
<div class="image-card__actions">
<button class="image-card__action-btn" onclick="downloadImage(${image.id})">
💾 저장
</button>
<button class="image-card__action-btn" onclick="editImage(${image.id})">
✏️ 수정
</button>
</div>
</div>
</div>
`).join('');
// Add event listeners
const cards = document.querySelectorAll('.image-card');
cards.forEach(card => {
const checkbox = card.querySelector('.image-card__checkbox');
card.addEventListener('click', (e) => {
if (e.target.closest('.image-card__action-btn')) return;
checkbox.checked = !checkbox.checked;
toggleImageSelection(card, checkbox.checked);
});
checkbox.addEventListener('click', (e) => {
e.stopPropagation();
toggleImageSelection(card, checkbox.checked);
});
});
}
// Toggle image selection
function toggleImageSelection(card, isSelected) {
const id = parseInt(card.getAttribute('data-id'));
if (isSelected) {
addClass(card, 'image-card--selected');
if (!selectedImages.includes(id)) {
selectedImages.push(id);
}
} else {
removeClass(card, 'image-card--selected');
selectedImages = selectedImages.filter(imageId => imageId !== id);
}
nextButton.disabled = selectedImages.length === 0;
}
// Download image
window.downloadImage = function(id) {
alert(`이미지 ${id}를 다운로드했습니다.`);
};
// Edit image
window.editImage = function(id) {
alert(`이미지 ${id} 편집 기능은 다음 버전에서 제공됩니다.`);
};
// Handle next button
nextButton.addEventListener('click', () => {
if (selectedImages.length === 0) return;
// Save selected images
const eventContent = storage.get('eventContent') || {};
eventContent.generatedImages = {
prompt: promptInput.value.trim(),
style: selectedStyle,
selectedImages: selectedImages,
generatedAt: new Date().toISOString()
};
storage.set('eventContent', eventContent);
// Show loading
addClass(nextButton, 'next-button--loading');
nextButton.disabled = true;
// Navigate to next screen
setTimeout(() => {
window.location.href = '11-SNS콘텐츠생성.html';
}, 800);
});
// Auto-fill prompt from event draft
window.addEventListener('load', () => {
const eventDraft = storage.get('eventDraft') || {};
if (eventDraft.promotionalCopy && !promptInput.value) {
promptInput.value = `${eventDraft.promotionalCopy.headline} 이미지`;
}
});
</script>
</body>
</html>