불필요한 폴더 정리
@ -1,685 +0,0 @@
|
||||
# KT AI 기반 소상공인 이벤트 자동 생성 서비스 - 클라우드 아키텍처 패턴 선정서
|
||||
|
||||
**작성일**: 2025-10-21
|
||||
**작성자**: System Architect (박영자)
|
||||
**버전**: 2.0 (3개 핵심 패턴 적용)
|
||||
|
||||
---
|
||||
|
||||
## 1. 요구사항 분석
|
||||
|
||||
### 1.1 마이크로서비스별 기능 및 비기능 요구사항
|
||||
|
||||
#### 1.1.1 User 서비스
|
||||
**기능 요구사항**:
|
||||
- 회원가입/로그인 (JWT 인증)
|
||||
- 매장 정보 관리 및 사업자번호 검증
|
||||
- KT 인증 시스템 연동
|
||||
|
||||
**비기능 요구사항**:
|
||||
- 응답시간: 1초 이내
|
||||
- 가용성: 99.9%
|
||||
- 보안: 개인정보 암호화 (AES-256), HTTPS/TLS
|
||||
- 확장성: 1,000 동시 사용자
|
||||
|
||||
**기술적 도전과제**:
|
||||
- 외부 사업자번호 검증 API 의존성
|
||||
- 인증 토큰 관리 및 세션 유지
|
||||
- 개인정보 보호 규정 준수
|
||||
|
||||
---
|
||||
|
||||
#### 1.1.2 Event Planning 서비스
|
||||
**기능 요구사항**:
|
||||
- AI 기반 업종/지역 트렌드 분석
|
||||
- AI 이벤트상품 추천 (Claude API)
|
||||
- AI 참여 방법 설계
|
||||
- AI 홍보 문구 생성 (GPT-4 API)
|
||||
|
||||
**비기능 요구사항**:
|
||||
- **응답시간: 10초 이내 (전체 기획 과정)** ⚡
|
||||
- AI API 병렬 호출 필수
|
||||
- 추첨형/선착순형 이벤트 자동 프로세스 분기
|
||||
- 확장성: 100개 동시 이벤트 기획
|
||||
|
||||
**기술적 도전과제**:
|
||||
- **Claude + GPT-4 API 병렬 호출 및 응답 시간 관리**
|
||||
- AI 프롬프트 최적화로 10초 목표 달성
|
||||
- 트렌드 데이터베이스 실시간 조회 성능
|
||||
- 응답 캐싱 전략
|
||||
|
||||
---
|
||||
|
||||
#### 1.1.3 Content Generation 서비스
|
||||
**기능 요구사항**:
|
||||
- AI 이미지 생성 (Stable Diffusion, 3종)
|
||||
- AI 영상 제작 (15초)
|
||||
- SNS 콘텐츠 자동 생성 (플랫폼별 최적화)
|
||||
- QR 포스터 생성
|
||||
|
||||
**비기능 요구사항**:
|
||||
- **응답시간: 5-8분 이내 (병렬 처리 시)** ⚡
|
||||
- 이미지 생성: 2-3분 (Stable Diffusion 특성)
|
||||
- 영상 제작: 3-5분 (AI 영상 엔진 특성)
|
||||
- GPU 가속 활용
|
||||
- 확장성: 50개 동시 콘텐츠 생성
|
||||
|
||||
**기술적 도전과제**:
|
||||
- **이미지 3종 + 영상 1개 병렬 생성**
|
||||
- 진행 상황 실시간 피드백
|
||||
- 백그라운드 비동기 처리 필수
|
||||
- 품질과 속도 균형 유지
|
||||
|
||||
---
|
||||
|
||||
#### 1.1.4 Distribution 서비스
|
||||
**기능 요구사항**:
|
||||
- 다중 채널 배포 (우리동네TV, 링고비즈, 지니TV, Instagram, Naver Blog, Kakao Channel)
|
||||
- 네이버 클로바 TTS 연동 (연결음 생성)
|
||||
- 배포 실패 시 자동 재시도 (3회)
|
||||
|
||||
**비기능 요구사항**:
|
||||
- **응답시간: 1분 이내 (전체 배포 과정)** ⚡
|
||||
- 채널별 병렬 배포 필수
|
||||
- 배포 상태 실시간 업데이트
|
||||
- 확장성: 100개 동시 배포
|
||||
|
||||
**기술적 도전과제**:
|
||||
- **6개 외부 API 병렬 호출 및 통합**
|
||||
- API 장애 대응 (Circuit Breaker, Retry)
|
||||
- 네이버 클로바 TTS 품질 보장
|
||||
- 배포 실패 복구 전략
|
||||
|
||||
---
|
||||
|
||||
#### 1.1.5 Participation 서비스
|
||||
**기능 요구사항**:
|
||||
- 이벤트 참여 신청 및 중복 방지
|
||||
- 추첨형 자동 추첨 (매장 방문 고객 가산점)
|
||||
- 선착순형 쿠폰 소진 시 자동 종료
|
||||
- 당첨 알림 발송 (SMS/카카오 알림톡)
|
||||
|
||||
**비기능 요구사항**:
|
||||
- 응답시간: 1초 이내
|
||||
- 1인 1회 참여 보장 (멱등성)
|
||||
- 공정한 추첨 알고리즘
|
||||
- 확장성: 10,000 동시 참여
|
||||
|
||||
**기술적 도전과제**:
|
||||
- 중복 참여 방지 (전화번호 기준)
|
||||
- 추첨형/선착순형 프로세스 분기
|
||||
- 대량 SMS/알림톡 발송
|
||||
- 매장 방문 고객 가산점 처리
|
||||
|
||||
---
|
||||
|
||||
#### 1.1.6 Analytics 서비스
|
||||
**기능 요구사항**:
|
||||
- 실시간 대시보드 (참여자 수, 노출 수, 매출 증가율)
|
||||
- 5분 간격 데이터 수집 및 업데이트
|
||||
- 채널별 성과 분석 (Instagram, Naver Blog, Kakao Channel API)
|
||||
- ROI 자동 계산
|
||||
- 분석 리포트 PDF 생성
|
||||
|
||||
**비기능 요구사항**:
|
||||
- **데이터 수집 주기: 5분 간격** ⏱
|
||||
- 실시간 데이터 시각화
|
||||
- 확장성: 100개 이벤트 동시 분석
|
||||
|
||||
**기술적 도전과제**:
|
||||
- **다중 데이터 소스 통합 (KT API, POS, SNS API)**
|
||||
- 5분 간격 스케줄러 기반 수집
|
||||
- 대시보드 실시간 업데이트
|
||||
|
||||
---
|
||||
|
||||
#### 1.1.7 AI Learning 서비스
|
||||
**기능 요구사항**:
|
||||
- 이벤트 결과 분석 및 개선안 생성
|
||||
- 성공 패턴 학습 및 재활용
|
||||
- 다음 이벤트 아이디어 제안 (시즌별)
|
||||
|
||||
**비기능 요구사항**:
|
||||
- 빅데이터 분석 시스템 연동
|
||||
- AI 머신러닝 엔진 API 호출
|
||||
- 학습 데이터 누적 및 모델 개선
|
||||
- 확장성: 누적 데이터 기반 지속적 학습
|
||||
|
||||
**기술적 도전과제**:
|
||||
- 성공/실패 패턴 자동 학습
|
||||
- 업종별/지역별 데이터 축적
|
||||
- 추천 정확도 향상 알고리즘
|
||||
|
||||
---
|
||||
|
||||
### 1.2 통합 분석 및 핵심 도전과제
|
||||
|
||||
#### 성능 목표 요약
|
||||
| 서비스 | 목표 응답시간 | 핵심 최적화 전략 |
|
||||
|--------|--------------|------------------|
|
||||
| Event Planning | **10초 이내** | AI API 병렬 호출 |
|
||||
| Content Generation | **5-8분 이내** | 이미지+영상 병렬 생성, 비동기 처리 |
|
||||
| Distribution | **1분 이내** | 6개 채널 병렬 배포 |
|
||||
| Analytics | **5분 주기** | 스케줄러 수집 |
|
||||
|
||||
#### 확장성 요구사항
|
||||
- **동시 이벤트 처리**: 최소 100개
|
||||
- **AI API 동시 호출**: 최소 50개
|
||||
- **배포 API 동시 호출**: 최소 100개
|
||||
|
||||
#### 외부 시스템 의존성
|
||||
- **KT 채널**: 우리동네TV, 링고비즈, 지니TV
|
||||
- **AI API**: Claude, GPT-4, Stable Diffusion
|
||||
- **SNS**: Instagram, Naver Blog, Kakao Channel
|
||||
- **기타**: 네이버 클로바 TTS, 사업자번호 검증, POS 시스템
|
||||
|
||||
---
|
||||
|
||||
## 2. 클라우드 아키텍처 패턴 선정
|
||||
|
||||
### 2.1 패턴 평가 매트릭스
|
||||
|
||||
#### 핵심 패턴 정량 평가
|
||||
|
||||
| 패턴 | 기능 적합성<br/>(35%) | 성능 효과<br/>(25%) | 운영 복잡도<br/>(20%) | 확장성<br/>(15%) | 비용 효율성<br/>(5%) | **총점** | **우선순위** |
|
||||
|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| **API Gateway** | 10 × 0.35 = 3.5 | 9 × 0.25 = 2.25 | 9 × 0.20 = 1.8 | 9 × 0.15 = 1.35 | 8 × 0.05 = 0.4 | **9.30** | 🥇 1위 |
|
||||
| **Async Request-Reply** | 10 × 0.35 = 3.5 | 9 × 0.25 = 2.25 | 7 × 0.20 = 1.4 | 8 × 0.15 = 1.2 | 7 × 0.05 = 0.35 | **8.70** | 🥈 2위 |
|
||||
| **Circuit Breaker** | 9 × 0.35 = 3.15 | 7 × 0.25 = 1.75 | 8 × 0.20 = 1.6 | 8 × 0.15 = 1.2 | 8 × 0.05 = 0.4 | **8.10** | 🥉 3위 |
|
||||
|
||||
---
|
||||
|
||||
### 2.2 선정 패턴 및 적용 전략
|
||||
|
||||
#### 🥇 **1. API Gateway** (총점: 9.30)
|
||||
|
||||
**선정 이유**:
|
||||
- 단일 진입점으로 7개 마이크로서비스 라우팅
|
||||
- 인증/인가 중앙 처리 (JWT 토큰 검증)
|
||||
- Rate Limiting으로 100개 동시 이벤트 관리
|
||||
- 횡단 관심사 분리 (로깅, 모니터링)
|
||||
|
||||
**적용 서비스**: 전체 서비스
|
||||
|
||||
**예상 효과**:
|
||||
- 클라이언트 요청 단순화 (1개 엔드포인트)
|
||||
- 인증 처리 시간 50% 단축
|
||||
- 서비스 간 결합도 감소
|
||||
|
||||
**구현 기술**:
|
||||
- **기술 스택**: Kong, AWS API Gateway, Spring Cloud Gateway
|
||||
- **Rate Limiting**: 사용자당 1분 100 요청
|
||||
- **JWT 토큰**: 만료 시간 1시간, Refresh Token 7일
|
||||
- **로깅**: 모든 요청/응답 로그 저장 (CloudWatch, ELK)
|
||||
|
||||
---
|
||||
|
||||
#### 🥈 **2. Async Request-Reply** (총점: 8.70)
|
||||
|
||||
**선정 이유**:
|
||||
- **Content Generation 서비스의 5-8분 처리 시간 대응**
|
||||
- 이미지/영상 생성 백그라운드 처리
|
||||
- 진행 상황 실시간 피드백 (폴링)
|
||||
- 클라이언트 블로킹 방지
|
||||
|
||||
**적용 서비스**: Content Generation
|
||||
|
||||
**예상 효과**:
|
||||
- 사용자 대기 시간 체감 80% 감소
|
||||
- 시스템 응답성 향상
|
||||
- 동시 콘텐츠 생성 50개 처리 가능
|
||||
|
||||
**구현 기술**:
|
||||
- **Job ID 관리**: UUID v4 사용
|
||||
- **상태 폴링**: 5초 간격, 최대 10분 타임아웃
|
||||
- **상태 관리**: Redis 또는 DB 기반 Job 상태 추적
|
||||
|
||||
**구현 예시**:
|
||||
```javascript
|
||||
// 클라이언트: 콘텐츠 생성 요청
|
||||
const response = await axios.post('/api/content/generate', {
|
||||
eventId: 'evt-001',
|
||||
imageCount: 3
|
||||
});
|
||||
const jobId = response.data.jobId; // Job ID 발급
|
||||
|
||||
// 상태 폴링 (5초 간격)
|
||||
const checkStatus = setInterval(async () => {
|
||||
const status = await axios.get(`/api/content/status/${jobId}`);
|
||||
if (status.data.completed) {
|
||||
clearInterval(checkStatus);
|
||||
// 콘텐츠 다운로드
|
||||
}
|
||||
}, 5000);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 🥉 **3. Circuit Breaker** (총점: 8.10)
|
||||
|
||||
**선정 이유**:
|
||||
- **6개 외부 API 장애 대응 (KT 채널, AI API, SNS)**
|
||||
- Distribution 서비스의 배포 실패 방지
|
||||
- API 장애 시 빠른 실패 및 폴백
|
||||
- 연쇄 장애 전파 차단
|
||||
|
||||
**적용 서비스**: Distribution, Event Planning, Content Generation
|
||||
|
||||
**예상 효과**:
|
||||
- API 장애 시 응답 시간 95% 단축
|
||||
- 시스템 가용성 99.9% 보장
|
||||
- 배포 성공률 98% 이상 유지
|
||||
|
||||
**구현 기술**:
|
||||
- **타임아웃 설정**: 외부 API별 차등 (15초 ~ 60초)
|
||||
- **실패 임계값**: 50% 실패 시 OPEN
|
||||
- **재시도 간격**: 30초 후 HALF-OPEN
|
||||
- **폴백 전략**: 기본값 제공 또는 실패 메시지
|
||||
|
||||
**구현 예시** (Node.js with opossum):
|
||||
```javascript
|
||||
const CircuitBreaker = require('opossum');
|
||||
|
||||
// 우리동네TV API 호출
|
||||
const callWooridongneTV = async (data) => {
|
||||
return axios.post('https://api.wooridongne.kt.com/deploy', data);
|
||||
};
|
||||
|
||||
const breaker = new CircuitBreaker(callWooridongneTV, {
|
||||
timeout: 15000, // 15초 타임아웃
|
||||
errorThresholdPercentage: 50, // 50% 실패 시 OPEN
|
||||
resetTimeout: 30000 // 30초 후 재시도
|
||||
});
|
||||
|
||||
breaker.fallback(() => ({
|
||||
success: false,
|
||||
message: '우리동네TV 배포 실패'
|
||||
}));
|
||||
|
||||
// 배포 요청
|
||||
const result = await breaker.fire(deployData);
|
||||
```
|
||||
|
||||
**Spring Boot 예시** (Resilience4j):
|
||||
```java
|
||||
@Service
|
||||
public class DistributionService {
|
||||
|
||||
@CircuitBreaker(name = "wooridongneTV", fallbackMethod = "fallbackDeploy")
|
||||
public DeployResult deployToWooridongneTV(DeployData data) {
|
||||
return wooridongneAPI.deploy(data);
|
||||
}
|
||||
|
||||
private DeployResult fallbackDeploy(DeployData data, Exception e) {
|
||||
return DeployResult.builder()
|
||||
.success(false)
|
||||
.message("우리동네TV 배포 실패: " + e.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 서비스별 패턴 적용 설계
|
||||
|
||||
### 3.1 전체 아키텍처 구조
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "클라이언트"
|
||||
Web[웹 애플리케이션]
|
||||
Mobile[모바일 앱]
|
||||
end
|
||||
|
||||
subgraph "API Gateway Layer"
|
||||
Gateway[API Gateway<br/>인증/인가/라우팅/Rate Limiting]
|
||||
end
|
||||
|
||||
subgraph "마이크로서비스"
|
||||
User[User 서비스<br/>회원/매장 관리]
|
||||
Planning[Event Planning 서비스<br/>AI 이벤트 기획]
|
||||
Content[Content Generation 서비스<br/>AI 콘텐츠 생성<br/>Async Request-Reply]
|
||||
Dist[Distribution 서비스<br/>다중 채널 배포<br/>Circuit Breaker]
|
||||
Participation[Participation 서비스<br/>참여/추첨 관리]
|
||||
Analytics[Analytics 서비스<br/>효과 측정]
|
||||
AILearn[AI Learning 서비스<br/>학습/개선]
|
||||
end
|
||||
|
||||
subgraph "데이터 계층"
|
||||
UserDB[(User DB<br/>PostgreSQL)]
|
||||
PlanningDB[(Planning DB<br/>MongoDB)]
|
||||
ContentDB[(Content DB<br/>MongoDB)]
|
||||
DistDB[(Distribution DB<br/>PostgreSQL)]
|
||||
PartDB[(Participation DB<br/>PostgreSQL)]
|
||||
AnalyticsDB[(Analytics DB<br/>MongoDB)]
|
||||
AILearnDB[(AI Learning DB<br/>MongoDB)]
|
||||
JobStore[(Job Store<br/>Redis)]
|
||||
end
|
||||
|
||||
subgraph "외부 시스템"
|
||||
KTAPI[KT 채널 API<br/>우리동네TV/링고비즈/지니TV]
|
||||
AIAPI[AI API<br/>Claude/GPT-4/Stable Diffusion]
|
||||
SNS[SNS API<br/>Instagram/Naver/Kakao]
|
||||
Clova[네이버 클로바 TTS]
|
||||
POS[POS 시스템]
|
||||
end
|
||||
|
||||
Web --> Gateway
|
||||
Mobile --> Gateway
|
||||
|
||||
Gateway --> User
|
||||
Gateway --> Planning
|
||||
Gateway --> Content
|
||||
Gateway --> Dist
|
||||
Gateway --> Participation
|
||||
Gateway --> Analytics
|
||||
Gateway --> AILearn
|
||||
|
||||
User --> UserDB
|
||||
Planning --> PlanningDB
|
||||
Content --> ContentDB
|
||||
Content --> JobStore
|
||||
Dist --> DistDB
|
||||
Participation --> PartDB
|
||||
Analytics --> AnalyticsDB
|
||||
AILearn --> AILearnDB
|
||||
|
||||
Planning -->|Circuit Breaker| AIAPI
|
||||
Content -->|Circuit Breaker| AIAPI
|
||||
Dist -->|Circuit Breaker| KTAPI
|
||||
Dist -->|Circuit Breaker| SNS
|
||||
Dist -->|Circuit Breaker| Clova
|
||||
Analytics --> KTAPI
|
||||
Analytics --> SNS
|
||||
Analytics --> POS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.2 Event Planning 서비스 - 10초 응답 최적화
|
||||
|
||||
**패턴 적용**:
|
||||
- **Circuit Breaker**: Claude + GPT-4 API 장애 대응
|
||||
- **병렬 처리**: AI API 동시 호출
|
||||
|
||||
**아키텍처**:
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Gateway
|
||||
participant Planning as Event Planning
|
||||
participant DB as Planning DB
|
||||
participant Claude as Claude API
|
||||
participant GPT4 as GPT-4 API
|
||||
|
||||
Client->>Gateway: 이벤트 기획 시작
|
||||
Gateway->>Planning: 기획 요청
|
||||
|
||||
Note over Planning,DB: Phase 1: 트렌드 분석 (3초 목표)
|
||||
Planning->>DB: 트렌드 데이터 조회
|
||||
DB-->>Planning: 트렌드 데이터
|
||||
|
||||
Note over Planning,GPT4: Phase 2: AI 병렬 호출 (7초 목표)
|
||||
par Claude: 이벤트상품 + 참여방법
|
||||
Planning->>Claude: 이벤트상품 추천 요청<br/>+ 참여방법 설계 요청
|
||||
Claude-->>Planning: 추천 결과 (5초)
|
||||
and GPT-4: 홍보문구
|
||||
Planning->>GPT4: 홍보문구 생성 요청
|
||||
GPT4-->>Planning: 홍보문구 (4초)
|
||||
end
|
||||
|
||||
Planning->>DB: 기획안 저장
|
||||
Planning-->>Gateway: 완성된 기획안 (총 10초 이내)
|
||||
Gateway-->>Client: 기획안 제공
|
||||
```
|
||||
|
||||
**예상 성과**:
|
||||
- **총 응답시간: 10초 이내** (목표 달성)
|
||||
- AI API 병렬 호출: 9초 → 5초
|
||||
|
||||
---
|
||||
|
||||
### 3.3 Content Generation 서비스 - 비동기 처리
|
||||
|
||||
**패턴 적용**:
|
||||
- **Async Request-Reply**: 5-8분 장시간 처리
|
||||
- **Circuit Breaker**: Stable Diffusion API 장애 대응
|
||||
|
||||
**아키텍처**:
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Gateway
|
||||
participant Content as Content Generation
|
||||
participant JobStore as Job Store (Redis)
|
||||
participant Worker as Background Worker
|
||||
participant SD as Stable Diffusion
|
||||
participant VideoAI as AI Video Engine
|
||||
participant DB as Content DB
|
||||
|
||||
Client->>Gateway: 콘텐츠 생성 요청
|
||||
Gateway->>Content: 생성 요청
|
||||
Content->>JobStore: Job 생성 (상태: PENDING)
|
||||
Content-->>Gateway: Job ID 발급
|
||||
Gateway-->>Client: Job ID 반환
|
||||
|
||||
Note over Client: 클라이언트는 다른 작업 가능
|
||||
|
||||
Content->>Worker: 비동기 작업 시작
|
||||
|
||||
par 이미지 3종 생성 (2-3분)
|
||||
Worker->>SD: 이미지 생성 요청 (3건 병렬)
|
||||
SD-->>Worker: 이미지 3종
|
||||
and 영상 1개 생성 (3-5분)
|
||||
Worker->>VideoAI: 영상 제작 요청
|
||||
VideoAI-->>Worker: 15초 영상
|
||||
end
|
||||
|
||||
Worker->>DB: 콘텐츠 저장
|
||||
Worker->>JobStore: 상태 업데이트 (COMPLETED)
|
||||
|
||||
loop 상태 확인 (5초 간격)
|
||||
Client->>Gateway: Job 상태 조회
|
||||
Gateway->>Content: 상태 조회
|
||||
Content->>JobStore: 상태 확인
|
||||
JobStore-->>Content: 상태 + 진행률
|
||||
Content-->>Gateway: 진행 상황 (예: 60% 완료)
|
||||
Gateway-->>Client: 진행률 표시
|
||||
end
|
||||
|
||||
Client->>Gateway: 최종 상태 조회
|
||||
Gateway->>Content: 상태 조회
|
||||
Content-->>Gateway: COMPLETED + 콘텐츠 URL
|
||||
Gateway-->>Client: 콘텐츠 다운로드
|
||||
```
|
||||
|
||||
**예상 성과**:
|
||||
- **총 처리시간: 5-8분** (목표 달성)
|
||||
- 사용자 대기 체감 시간: 8분 → 0초 (비동기)
|
||||
- 동시 처리 능력: 50개 콘텐츠
|
||||
|
||||
---
|
||||
|
||||
### 3.4 Distribution 서비스 - 안정적 배포
|
||||
|
||||
**패턴 적용**:
|
||||
- **Circuit Breaker**: 6개 외부 API 장애 대응
|
||||
- **병렬 배포**: 6개 채널 동시 배포
|
||||
|
||||
**아키텍처**:
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Gateway
|
||||
participant Dist as Distribution
|
||||
participant CB1 as Circuit Breaker<br/>(우리동네TV)
|
||||
participant CB2 as Circuit Breaker<br/>(지니TV)
|
||||
participant CB3 as Circuit Breaker<br/>(Instagram)
|
||||
participant KTAPI as KT API
|
||||
participant SNS as SNS API
|
||||
|
||||
Client->>Gateway: 배포 요청 (6개 채널)
|
||||
Gateway->>Dist: 배포 시작
|
||||
|
||||
par 병렬 배포 (1분 목표)
|
||||
Dist->>CB1: 우리동네TV 배포
|
||||
CB1->>KTAPI: API 호출
|
||||
alt API 성공
|
||||
KTAPI-->>CB1: 배포 완료
|
||||
else API 실패
|
||||
KTAPI--xCB1: 오류
|
||||
CB1-->>Dist: Fallback (배포 실패)
|
||||
end
|
||||
CB1-->>Dist: 결과 반환
|
||||
and
|
||||
Dist->>CB2: 지니TV 배포
|
||||
CB2->>KTAPI: API 호출
|
||||
KTAPI-->>CB2: 배포 완료
|
||||
CB2-->>Dist: 결과 반환
|
||||
and
|
||||
Dist->>CB3: Instagram 배포
|
||||
CB3->>SNS: API 호출
|
||||
SNS-->>CB3: 포스팅 완료
|
||||
CB3-->>Dist: 결과 반환
|
||||
end
|
||||
|
||||
Dist-->>Gateway: 배포 결과 (성공 5/6)
|
||||
Gateway-->>Client: 배포 완료 + 실패 채널 안내
|
||||
```
|
||||
|
||||
**예상 성과**:
|
||||
- **총 배포시간: 1분 이내** (목표 달성)
|
||||
- API 장애 시 응답: 30초 → 3초 (Circuit Breaker)
|
||||
- 배포 안정성: 95% → 98%
|
||||
|
||||
---
|
||||
|
||||
## 4. 구현 로드맵
|
||||
|
||||
### Phase 1: MVP (Minimum Viable Product) - 3개월
|
||||
|
||||
**목표**: 핵심 비즈니스 기능 제공 및 빠른 출시
|
||||
|
||||
**적용 패턴**:
|
||||
1. **API Gateway** - 단일 진입점 및 인증/인가
|
||||
2. **Async Request-Reply** - 콘텐츠 생성 비동기 처리
|
||||
3. **Circuit Breaker** - 외부 API 장애 대응
|
||||
|
||||
**구현 우선순위**:
|
||||
1. **Week 1-4**: User 서비스 + API Gateway
|
||||
- 회원가입/로그인 (JWT 인증)
|
||||
- 매장 정보 관리
|
||||
- 사업자번호 검증 연동
|
||||
- API Gateway 구축 (Kong 또는 Spring Cloud Gateway)
|
||||
|
||||
2. **Week 5-8**: Event Planning 서비스
|
||||
- AI API 연동 (Claude, GPT-4)
|
||||
- Circuit Breaker 적용
|
||||
- AI API 병렬 호출 구현
|
||||
|
||||
3. **Week 9-10**: Content Generation 서비스
|
||||
- Async Request-Reply 패턴 구현
|
||||
- Stable Diffusion 연동
|
||||
- 진행 상황 폴링 API
|
||||
- Redis 기반 Job Store 구축
|
||||
|
||||
4. **Week 11-12**: Distribution + Participation 서비스
|
||||
- 6개 채널 병렬 배포 (Circuit Breaker)
|
||||
- 추첨/선착순 분기 로직
|
||||
|
||||
**성공 지표**:
|
||||
- Event Planning: 10초 이내 응답 ✅
|
||||
- Content Generation: 8분 이내 완료 ✅
|
||||
- Distribution: 1분 이내 배포 ✅
|
||||
- 동시 이벤트 처리: 50개
|
||||
|
||||
---
|
||||
|
||||
## 5. 예상 성과 지표
|
||||
|
||||
### 5.1 성능 개선
|
||||
|
||||
| 지표 | 현재 (패턴 미적용) | Phase 1 MVP |
|
||||
|------|-------------------|-------------|
|
||||
| **Event Planning 응답시간** | 25초 | **10초** ✅ |
|
||||
| **Content Generation 완료시간** | 12분 | **8분** ✅ |
|
||||
| **Distribution 배포시간** | 3분 | **1분** ✅ |
|
||||
| **동시 이벤트 처리** | 20개 | 50개 |
|
||||
| **시스템 가용성** | 95% | 99% |
|
||||
|
||||
---
|
||||
|
||||
### 5.2 패턴별 기대 효과
|
||||
|
||||
| 패턴 | 적용 효과 | 개선율 |
|
||||
|------|----------|--------|
|
||||
| **API Gateway** | 클라이언트 통합 엔드포인트, 인증 중앙화 | 인증 처리 50% 단축 |
|
||||
| **Async Request-Reply** | 사용자 대기 시간 체감 감소 | 80% 체감 시간 감소 |
|
||||
| **Circuit Breaker** | API 장애 시 빠른 실패, 연쇄 장애 방지 | 응답 시간 95% 단축 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 리스크 관리
|
||||
|
||||
### 6.1 기술적 리스크
|
||||
|
||||
| 리스크 | 확률 | 영향도 | 완화 전략 |
|
||||
|--------|------|--------|-----------|
|
||||
| AI API 응답 지연 (>10초) | 중간 | 높음 | 병렬 호출, 프롬프트 최적화 |
|
||||
| 외부 API 장애 | 높음 | 높음 | Circuit Breaker, 폴백 전략 |
|
||||
| Job Store (Redis) 장애 | 낮음 | 중간 | Redis Cluster, DB 폴백 |
|
||||
|
||||
### 6.2 운영 리스크
|
||||
|
||||
| 리스크 | 확률 | 영향도 | 완화 전략 |
|
||||
|--------|------|--------|-----------|
|
||||
| 피크 타임 트래픽 폭주 | 높음 | 높음 | Auto Scaling, Rate Limiting |
|
||||
| 배포 실패율 증가 | 중간 | 중간 | Circuit Breaker 폴백 전략 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 결론
|
||||
|
||||
### 7.1 핵심 패턴 요약
|
||||
|
||||
본 서비스는 **3개의 클라우드 아키텍처 패턴**을 적용하여 성능, 확장성, 안정성을 보장합니다:
|
||||
|
||||
1. **API Gateway**: 단일 진입점 및 횡단 관심사 분리
|
||||
2. **Async Request-Reply**: 장시간 처리 작업 비동기화
|
||||
3. **Circuit Breaker**: 외부 API 장애 대응
|
||||
|
||||
### 7.2 예상 효과
|
||||
|
||||
- **Event Planning**: 25초 → **10초** (60% 단축) ✅
|
||||
- **Content Generation**: 12분 → **8분** (33% 단축) ✅
|
||||
- **Distribution**: 3분 → **1분** (67% 단축) ✅
|
||||
- **시스템 가용성**: 95% → **99%** ✅
|
||||
- **동시 이벤트 처리**: 20개 → **50개** (2.5배 향상) ✅
|
||||
|
||||
### 7.3 다음 단계
|
||||
|
||||
1. **Phase 1 MVP 착수** (3개월)
|
||||
- API Gateway + Async Request-Reply + Circuit Breaker 우선 구현
|
||||
- 핵심 비즈니스 기능 검증
|
||||
|
||||
2. **성능 모니터링**
|
||||
- Prometheus + Grafana 대시보드 구축
|
||||
- 각 패턴별 성과 지표 측정
|
||||
|
||||
3. **지속적 개선**
|
||||
- 추가 패턴 도입 검토 (Cache-Aside, CQRS 등)
|
||||
- AI 학습 정확도 향상
|
||||
|
||||
---
|
||||
|
||||
**문서 승인**:
|
||||
- [ ] System Architect (박영자)
|
||||
- [ ] Backend Developer (최수연)
|
||||
- [ ] DevOps Engineer (송근정)
|
||||
- [ ] PO (갑빠)
|
||||
|
||||
**참조 문서**:
|
||||
- design/userstory.md
|
||||
- claude/cloud-design-patterns.md
|
||||
- design/pattern/architecture-pattern-backup-20251021.md (백업)
|
||||
@ -1,414 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<!-- Pretendard Font -->
|
||||
<link rel="stylesheet" as="style" crossorigin
|
||||
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable.min.css">
|
||||
|
||||
<!-- Stylesheets -->
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/reset.css">
|
||||
<link rel="stylesheet" href="css/layout.css">
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<link rel="stylesheet" href="css/components/buttons.css">
|
||||
<link rel="stylesheet" href="css/components/inputs.css">
|
||||
<link rel="stylesheet" href="css/components/loaders.css">
|
||||
|
||||
<style>
|
||||
/* Login Page Specific Styles */
|
||||
.login-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
padding: var(--spacing-l);
|
||||
}
|
||||
|
||||
.login-container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
box-shadow: var(--shadow-lg);
|
||||
padding: var(--spacing-2xl) var(--spacing-l);
|
||||
animation: fadeIn var(--transition-normal) var(--ease-out);
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin: 0 auto var(--spacing-l);
|
||||
background: var(--gradient-primary);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: var(--font-size-h1);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-black);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
font-size: var(--font-size-body-medium);
|
||||
color: var(--color-gray-500);
|
||||
}
|
||||
|
||||
.login-form {
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.login-options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.login-link {
|
||||
font-size: var(--font-size-body-small);
|
||||
color: var(--color-gray-500);
|
||||
text-decoration: none;
|
||||
transition: color var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.login-link:hover {
|
||||
color: var(--color-primary);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.login-divider {
|
||||
text-align: center;
|
||||
margin: var(--spacing-l) 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.login-divider::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background-color: var(--color-gray-300);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.login-divider span {
|
||||
position: relative;
|
||||
background-color: var(--color-white);
|
||||
padding: 0 var(--spacing-m);
|
||||
font-size: var(--font-size-body-small);
|
||||
color: var(--color-gray-500);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.login-signup {
|
||||
text-align: center;
|
||||
font-size: var(--font-size-body-medium);
|
||||
color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
.login-signup a {
|
||||
color: var(--color-primary);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.login-signup a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.password-toggle {
|
||||
position: absolute;
|
||||
right: var(--spacing-m);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-gray-500);
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.password-toggle:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* Tablet and Desktop */
|
||||
@media (min-width: 768px) {
|
||||
.login-container {
|
||||
padding: var(--spacing-2xl);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.login-page {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.login-hero {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--gradient-primary);
|
||||
color: var(--color-white);
|
||||
padding: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.login-hero__title {
|
||||
font-size: 36px;
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: var(--spacing-m);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-hero__subtitle {
|
||||
font-size: var(--font-size-body-large);
|
||||
text-align: center;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.login-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.login-container {
|
||||
box-shadow: none;
|
||||
max-width: 480px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-page">
|
||||
<!-- Hero Section (Desktop Only) -->
|
||||
<div class="login-hero hidden-mobile hidden-tablet">
|
||||
<div>
|
||||
<h1 class="login-hero__title">AI로 간편하게,<br>성공으로 확실하게</h1>
|
||||
<p class="login-hero__subtitle">소상공인을 위한 스마트 이벤트 마케팅 솔루션</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Login Form Section -->
|
||||
<div class="login-wrapper">
|
||||
<div class="login-container">
|
||||
<div class="login-header">
|
||||
<div class="login-logo" aria-label="KT 이벤트 마케팅">
|
||||
🎯
|
||||
</div>
|
||||
<h1 class="login-title">KT 이벤트 마케팅</h1>
|
||||
<p class="login-subtitle">소상공인 이벤트 자동 생성 서비스</p>
|
||||
</div>
|
||||
|
||||
<form class="login-form" id="loginForm" novalidate>
|
||||
<div class="form-group">
|
||||
<label for="email" class="form-label form-label--required">이메일</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
class="input"
|
||||
placeholder="example@email.com"
|
||||
autocomplete="email"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password" class="form-label form-label--required">비밀번호</label>
|
||||
<div style="position: relative;">
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
class="input"
|
||||
placeholder="비밀번호를 입력하세요"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="password-toggle"
|
||||
id="passwordToggle"
|
||||
aria-label="비밀번호 보기/숨기기"
|
||||
>
|
||||
<span id="passwordIcon">👁</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="login-options">
|
||||
<div class="checkbox-wrapper">
|
||||
<input type="checkbox" id="autoLogin" name="autoLogin" class="checkbox">
|
||||
<label for="autoLogin" class="checkbox-label">자동 로그인</label>
|
||||
</div>
|
||||
<a href="#" class="login-link" id="forgotPassword">비밀번호 찾기</a>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-large btn-block" id="loginButton">
|
||||
로그인
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="login-divider">
|
||||
<span>또는</span>
|
||||
</div>
|
||||
|
||||
<div class="login-signup">
|
||||
계정이 없으신가요? <a href="01-회원가입.html">회원가입</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="js/common.js"></script>
|
||||
<script src="js/form.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const { FormValidator, Toast } = window.KTEvent;
|
||||
|
||||
// Password Toggle
|
||||
const passwordInput = document.getElementById('password');
|
||||
const passwordToggle = document.getElementById('passwordToggle');
|
||||
const passwordIcon = document.getElementById('passwordIcon');
|
||||
|
||||
passwordToggle.addEventListener('click', () => {
|
||||
if (passwordInput.type === 'password') {
|
||||
passwordInput.type = 'text';
|
||||
passwordIcon.textContent = '👁️🗨️';
|
||||
} else {
|
||||
passwordInput.type = 'password';
|
||||
passwordIcon.textContent = '👁';
|
||||
}
|
||||
});
|
||||
|
||||
// Form Validation
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
const validator = new FormValidator(loginForm, {
|
||||
validateOnBlur: true,
|
||||
validateOnInput: true,
|
||||
scrollToError: true
|
||||
});
|
||||
|
||||
// Add validation rules
|
||||
validator
|
||||
.addField('email', {
|
||||
required: true,
|
||||
email: true,
|
||||
messages: {
|
||||
required: '이메일을 입력해주세요.',
|
||||
email: '올바른 이메일 형식이 아닙니다.'
|
||||
}
|
||||
})
|
||||
.addField('password', {
|
||||
required: true,
|
||||
minLength: 6,
|
||||
messages: {
|
||||
required: '비밀번호를 입력해주세요.',
|
||||
minLength: '비밀번호는 최소 6자 이상이어야 합니다.'
|
||||
}
|
||||
});
|
||||
|
||||
// Override onSubmit
|
||||
validator.onSubmit = function() {
|
||||
const values = this.getValues();
|
||||
const loginButton = document.getElementById('loginButton');
|
||||
|
||||
// Show loading state
|
||||
loginButton.disabled = true;
|
||||
loginButton.classList.add('btn-loading');
|
||||
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
// Mock authentication
|
||||
if (values.email === 'demo@kt.com' && values.password === 'demo1234') {
|
||||
// Save to localStorage
|
||||
window.KTEvent.storage.set('user', {
|
||||
email: values.email,
|
||||
autoLogin: values.autoLogin,
|
||||
loginAt: new Date().toISOString()
|
||||
});
|
||||
|
||||
Toast.show('로그인에 성공했습니다!', {
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
// Redirect to home
|
||||
setTimeout(() => {
|
||||
window.location.href = '03-홈화면.html';
|
||||
}, 1000);
|
||||
} else {
|
||||
// Login failed
|
||||
loginButton.disabled = false;
|
||||
loginButton.classList.remove('btn-loading');
|
||||
|
||||
Toast.show('이메일 또는 비밀번호가 일치하지 않습니다.', {
|
||||
type: 'error',
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
// Forgot Password
|
||||
document.getElementById('forgotPassword').addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
Toast.show('비밀번호 찾기 기능은 준비 중입니다.', {
|
||||
type: 'info',
|
||||
duration: 2000
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-fill demo credentials (for prototype testing)
|
||||
if (window.location.search.includes('demo=true')) {
|
||||
document.getElementById('email').value = 'demo@kt.com';
|
||||
document.getElementById('password').value = 'demo1234';
|
||||
}
|
||||
|
||||
// Check if already logged in
|
||||
const user = window.KTEvent.storage.get('user');
|
||||
if (user && user.autoLogin) {
|
||||
Toast.show('자동 로그인 중...', {
|
||||
type: 'info',
|
||||
duration: 1500
|
||||
});
|
||||
setTimeout(() => {
|
||||
window.location.href = '03-홈화면.html';
|
||||
}, 1500);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,347 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<!-- Pretendard Font -->
|
||||
<link rel="stylesheet" as="style" crossorigin
|
||||
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable.min.css">
|
||||
|
||||
<!-- Stylesheets -->
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/reset.css">
|
||||
<link rel="stylesheet" href="css/layout.css">
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<link rel="stylesheet" href="css/components/buttons.css">
|
||||
<link rel="stylesheet" href="css/components/inputs.css">
|
||||
<link rel="stylesheet" href="css/components/navigation.css">
|
||||
<link rel="stylesheet" href="css/components/modals.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<!-- App Bar -->
|
||||
<header class="app-bar">
|
||||
<div class="app-bar__container">
|
||||
<div class="app-bar__left">
|
||||
<svg class="app-bar__back" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="M19 12H5M12 19l-7-7 7-7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="app-bar__title">회원가입</h1>
|
||||
<div class="app-bar__right"></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="page-main">
|
||||
<div class="container" style="max-width: 500px; padding-top: var(--spacing-2xl); padding-bottom: var(--spacing-2xl);">
|
||||
<div class="text-center mb-2xl">
|
||||
<div style="font-size: 48px; margin-bottom: var(--spacing-m);">🎉</div>
|
||||
<h2 class="h2 mb-s">KT 이벤트 마케팅</h2>
|
||||
<p class="body-medium text-gray-500">환영합니다</p>
|
||||
</div>
|
||||
|
||||
<form id="signupForm" novalidate>
|
||||
<!-- 이름 -->
|
||||
<div class="form-group">
|
||||
<label for="name" class="form-label form-label--required">이름</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
class="input"
|
||||
placeholder="2자 이상 입력"
|
||||
autocomplete="name"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- 전화번호 -->
|
||||
<div class="form-group">
|
||||
<label for="phone" class="form-label form-label--required">전화번호</label>
|
||||
<input
|
||||
type="tel"
|
||||
id="phone"
|
||||
name="phone"
|
||||
class="input"
|
||||
placeholder="010-XXXX-XXXX"
|
||||
autocomplete="tel"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- 이메일 -->
|
||||
<div class="form-group">
|
||||
<label for="email" class="form-label form-label--required">이메일</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
class="input"
|
||||
placeholder="example@email.com"
|
||||
autocomplete="email"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- 비밀번호 -->
|
||||
<div class="form-group">
|
||||
<label for="password" class="form-label form-label--required">비밀번호</label>
|
||||
<div style="position: relative;">
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
class="input"
|
||||
placeholder="비밀번호 입력"
|
||||
autocomplete="new-password"
|
||||
required
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="password-toggle"
|
||||
data-target="password"
|
||||
aria-label="비밀번호 보기/숨기기"
|
||||
style="position: absolute; right: 16px; top: 50%; transform: translateY(-50%); background: none; border: none; cursor: pointer; padding: 0; display: flex; align-items: center;"
|
||||
>
|
||||
<span style="font-size: 20px;">👁</span>
|
||||
</button>
|
||||
</div>
|
||||
<span class="form-helper">최소 8자, 영문/숫자/특수문자 조합</span>
|
||||
</div>
|
||||
|
||||
<!-- 비밀번호 확인 -->
|
||||
<div class="form-group">
|
||||
<label for="passwordConfirm" class="form-label form-label--required">비밀번호 확인</label>
|
||||
<div style="position: relative;">
|
||||
<input
|
||||
type="password"
|
||||
id="passwordConfirm"
|
||||
name="passwordConfirm"
|
||||
class="input"
|
||||
placeholder="비밀번호 재입력"
|
||||
autocomplete="new-password"
|
||||
required
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="password-toggle"
|
||||
data-target="passwordConfirm"
|
||||
aria-label="비밀번호 확인 보기/숨기기"
|
||||
style="position: absolute; right: 16px; top: 50%; transform: translateY(-50%); background: none; border: none; cursor: pointer; padding: 0; display: flex; align-items: center;"
|
||||
>
|
||||
<span style="font-size: 20px;">👁</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 개인정보 수집 동의 -->
|
||||
<div class="form-group">
|
||||
<div class="checkbox-wrapper">
|
||||
<input type="checkbox" id="agreePrivacy" name="agreePrivacy" class="checkbox" required>
|
||||
<label for="agreePrivacy" class="checkbox-label">
|
||||
개인정보 수집 동의 (필수) <a href="#" id="viewPrivacy" style="color: var(--color-primary); text-decoration: underline;">자세히보기</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 제출 버튼 -->
|
||||
<button type="submit" class="btn btn-primary btn-large btn-block mt-xl">
|
||||
매장 정보 등록
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="text-center mt-l">
|
||||
<p class="body-small text-gray-500">
|
||||
이미 계정이 있으신가요? <a href="00-로그인.html" style="color: var(--color-primary); font-weight: var(--font-weight-semibold);">로그인</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="js/common.js"></script>
|
||||
<script src="js/navigation.js"></script>
|
||||
<script src="js/modal.js"></script>
|
||||
<script src="js/form.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const { FormValidator, Toast, Dialog } = window.KTEvent;
|
||||
|
||||
// Password Toggle
|
||||
document.querySelectorAll('.password-toggle').forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
const targetId = button.getAttribute('data-target');
|
||||
const input = document.getElementById(targetId);
|
||||
const icon = button.querySelector('span');
|
||||
|
||||
if (input.type === 'password') {
|
||||
input.type = 'text';
|
||||
icon.textContent = '👁️🗨️';
|
||||
} else {
|
||||
input.type = 'password';
|
||||
icon.textContent = '👁';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Phone Number Formatting
|
||||
const phoneInput = document.getElementById('phone');
|
||||
phoneInput.addEventListener('input', (e) => {
|
||||
let value = e.target.value.replace(/[^0-9]/g, '');
|
||||
if (value.length > 11) value = value.slice(0, 11);
|
||||
|
||||
if (value.length >= 7) {
|
||||
value = value.replace(/(\d{3})(\d{4})(\d{0,4})/, '$1-$2-$3');
|
||||
} else if (value.length >= 3) {
|
||||
value = value.replace(/(\d{3})(\d{0,4})/, '$1-$2');
|
||||
}
|
||||
|
||||
e.target.value = value;
|
||||
});
|
||||
|
||||
// Form Validation
|
||||
const signupForm = document.getElementById('signupForm');
|
||||
const validator = new FormValidator(signupForm, {
|
||||
validateOnBlur: true,
|
||||
validateOnInput: true,
|
||||
scrollToError: true
|
||||
});
|
||||
|
||||
// Password strength validation
|
||||
const validatePasswordStrength = (password) => {
|
||||
if (password.length < 8) return false;
|
||||
|
||||
const hasLetter = /[a-zA-Z]/.test(password);
|
||||
const hasNumber = /[0-9]/.test(password);
|
||||
const hasSpecial = /[!@#$%^&*(),.?":{}|<>]/.test(password);
|
||||
|
||||
return hasLetter && hasNumber && hasSpecial;
|
||||
};
|
||||
|
||||
// Add validation rules
|
||||
validator
|
||||
.addField('name', {
|
||||
required: true,
|
||||
minLength: 2,
|
||||
messages: {
|
||||
required: '이름을 입력해주세요.',
|
||||
minLength: '이름을 2자 이상 입력해주세요.'
|
||||
}
|
||||
})
|
||||
.addField('phone', {
|
||||
required: true,
|
||||
phone: true,
|
||||
messages: {
|
||||
required: '전화번호를 입력해주세요.',
|
||||
phone: '올바른 전화번호 형식이 아닙니다. (010-XXXX-XXXX)'
|
||||
}
|
||||
})
|
||||
.addField('email', {
|
||||
required: true,
|
||||
email: true,
|
||||
messages: {
|
||||
required: '이메일을 입력해주세요.',
|
||||
email: '올바른 이메일 형식이 아닙니다.'
|
||||
}
|
||||
})
|
||||
.addField('password', {
|
||||
required: true,
|
||||
custom: (value) => validatePasswordStrength(value),
|
||||
messages: {
|
||||
required: '비밀번호를 입력해주세요.',
|
||||
custom: '최소 8자 이상, 영문/숫자/특수문자를 조합해주세요.'
|
||||
}
|
||||
})
|
||||
.addField('passwordConfirm', {
|
||||
required: true,
|
||||
custom: (value) => value === document.getElementById('password').value,
|
||||
messages: {
|
||||
required: '비밀번호 확인을 입력해주세요.',
|
||||
custom: '비밀번호가 일치하지 않습니다.'
|
||||
}
|
||||
})
|
||||
.addField('agreePrivacy', {
|
||||
required: true,
|
||||
custom: (value, field) => field.checked,
|
||||
messages: {
|
||||
required: '개인정보 수집 동의가 필요합니다.',
|
||||
custom: '개인정보 수집 동의가 필요합니다.'
|
||||
}
|
||||
});
|
||||
|
||||
// Override onSubmit
|
||||
validator.onSubmit = function() {
|
||||
const values = this.getValues();
|
||||
const submitButton = signupForm.querySelector('button[type="submit"]');
|
||||
|
||||
// Show loading state
|
||||
submitButton.disabled = true;
|
||||
submitButton.classList.add('btn-loading');
|
||||
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
// Mock duplicate check
|
||||
const existingEmails = ['test@example.com', 'demo@kt.com'];
|
||||
const existingPhones = ['010-1234-5678'];
|
||||
|
||||
if (existingEmails.includes(values.email)) {
|
||||
submitButton.disabled = false;
|
||||
submitButton.classList.remove('btn-loading');
|
||||
validator.setError('email', '이미 가입된 이메일입니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingPhones.includes(values.phone)) {
|
||||
submitButton.disabled = false;
|
||||
submitButton.classList.remove('btn-loading');
|
||||
validator.setError('phone', '이미 가입된 전화번호입니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Save user data
|
||||
window.KTEvent.storage.set('signupData', {
|
||||
name: values.name,
|
||||
phone: values.phone,
|
||||
email: values.email,
|
||||
createdAt: new Date().toISOString()
|
||||
});
|
||||
|
||||
Toast.show('회원가입이 완료되었습니다!', {
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
// Redirect to store info registration
|
||||
setTimeout(() => {
|
||||
window.location.href = '02-매장정보등록.html';
|
||||
}, 1000);
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
// View Privacy Policy
|
||||
document.getElementById('viewPrivacy').addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const result = await Dialog.confirm({
|
||||
title: '개인정보 수집 및 이용 동의',
|
||||
message: 'KT 이벤트 마케팅 서비스는 다음과 같이 개인정보를 수집 및 이용합니다.\n\n1. 수집 항목: 이름, 전화번호, 이메일, 매장 정보\n2. 수집 목적: 서비스 제공, 이벤트 생성 및 관리\n3. 보유 기간: 회원 탈퇴 시까지\n\n자세한 내용은 개인정보처리방침을 확인해주세요.',
|
||||
confirmText: '동의',
|
||||
cancelText: '닫기'
|
||||
});
|
||||
|
||||
if (result) {
|
||||
document.getElementById('agreePrivacy').checked = true;
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,770 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<!-- Pretendard Font -->
|
||||
<link rel="stylesheet" as="style" crossorigin
|
||||
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable.min.css">
|
||||
|
||||
<!-- Stylesheets -->
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/reset.css">
|
||||
<link rel="stylesheet" href="css/layout.css">
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<link rel="stylesheet" href="css/components/buttons.css">
|
||||
<link rel="stylesheet" href="css/components/inputs.css">
|
||||
<link rel="stylesheet" href="css/components/navigation.css">
|
||||
<link rel="stylesheet" href="css/components/modals.css">
|
||||
|
||||
<style>
|
||||
/* Store Registration Specific Styles */
|
||||
.store-registration {
|
||||
padding-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.progress-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-s);
|
||||
padding: var(--spacing-l) 0;
|
||||
background-color: var(--color-white);
|
||||
}
|
||||
|
||||
.progress-step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.progress-step__number {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
font-size: var(--font-size-body-medium);
|
||||
}
|
||||
|
||||
.progress-step--completed .progress-step__number {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.progress-step--active .progress-step__number {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
box-shadow: 0 0 0 4px rgba(227, 30, 36, 0.2);
|
||||
}
|
||||
|
||||
.progress-step--pending .progress-step__number {
|
||||
background-color: var(--color-gray-300);
|
||||
color: var(--color-gray-500);
|
||||
}
|
||||
|
||||
.progress-step__label {
|
||||
font-size: var(--font-size-body-small);
|
||||
color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
.progress-divider {
|
||||
width: 24px;
|
||||
height: 2px;
|
||||
background-color: var(--color-gray-300);
|
||||
}
|
||||
|
||||
.progress-divider--completed {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-black);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
font-size: var(--font-size-body-small);
|
||||
color: var(--color-gray-500);
|
||||
margin-top: calc(-1 * var(--spacing-s));
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.menu-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.menu-item__input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.menu-item__remove {
|
||||
width: 44px;
|
||||
height: 48px;
|
||||
background-color: var(--color-gray-100);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
border-radius: var(--radius-s);
|
||||
color: var(--color-error);
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.menu-item__remove:hover {
|
||||
background-color: var(--color-error);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.add-menu-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
padding: var(--spacing-m);
|
||||
background-color: var(--color-gray-100);
|
||||
border: 1px dashed var(--color-gray-400);
|
||||
border-radius: var(--radius-s);
|
||||
color: var(--color-gray-700);
|
||||
font-size: var(--font-size-body-medium);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.add-menu-button:hover {
|
||||
background-color: var(--color-gray-200);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.image-upload-area {
|
||||
display: flex;
|
||||
gap: var(--spacing-m);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.image-upload-box {
|
||||
position: relative;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 2px dashed var(--color-gray-400);
|
||||
border-radius: var(--radius-s);
|
||||
background-color: var(--color-gray-100);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image-upload-box:hover {
|
||||
border-color: var(--color-primary);
|
||||
background-color: var(--color-gray-200);
|
||||
}
|
||||
|
||||
.image-upload-box input[type="file"] {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.image-upload-box__placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: var(--color-gray-500);
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.image-upload-box__placeholder-text {
|
||||
font-size: var(--font-size-body-small);
|
||||
margin-top: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.image-upload-box__preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.image-upload-box__remove {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
color: var(--color-white);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.image-upload-box:hover .image-upload-box__remove {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.business-number-helper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
font-size: var(--font-size-body-small);
|
||||
color: var(--color-gray-500);
|
||||
margin-top: var(--spacing-s);
|
||||
}
|
||||
|
||||
.business-number-helper--verified {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.business-number-helper--error {
|
||||
color: var(--color-error);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<!-- App Bar -->
|
||||
<header class="app-bar">
|
||||
<div class="app-bar__container">
|
||||
<div class="app-bar__left">
|
||||
<svg class="app-bar__back" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="M19 12H5M12 19l-7-7 7-7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="app-bar__title">매장정보 등록</h1>
|
||||
<div class="app-bar__right"></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="page-main">
|
||||
<div class="store-registration">
|
||||
<!-- Progress Indicator -->
|
||||
<div class="progress-indicator">
|
||||
<div class="progress-step progress-step--completed">
|
||||
<div class="progress-step__number">1</div>
|
||||
<span class="progress-step__label">회원가입</span>
|
||||
</div>
|
||||
<div class="progress-divider progress-divider--completed"></div>
|
||||
<div class="progress-step progress-step--active">
|
||||
<div class="progress-step__number">2</div>
|
||||
<span class="progress-step__label">매장정보</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container" style="max-width: 600px; padding-top: var(--spacing-xl);">
|
||||
<form id="storeForm" novalidate>
|
||||
<!-- 필수 정보 -->
|
||||
<section class="mb-2xl">
|
||||
<h2 class="section-title">필수 정보</h2>
|
||||
<p class="section-subtitle">정확한 정보를 입력하면 더 나은 이벤트를 만들 수 있어요</p>
|
||||
|
||||
<!-- 매장명 -->
|
||||
<div class="form-group">
|
||||
<label for="storeName" class="form-label form-label--required">매장명</label>
|
||||
<input
|
||||
type="text"
|
||||
id="storeName"
|
||||
name="storeName"
|
||||
class="input"
|
||||
placeholder="매장 이름을 입력하세요"
|
||||
maxlength="50"
|
||||
autocomplete="organization"
|
||||
required
|
||||
>
|
||||
<span class="form-helper">최대 50자까지 입력 가능합니다</span>
|
||||
</div>
|
||||
|
||||
<!-- 업종 -->
|
||||
<div class="form-group">
|
||||
<label for="businessType" class="form-label form-label--required">업종</label>
|
||||
<select id="businessType" name="businessType" class="input" required>
|
||||
<option value="">업종을 선택하세요</option>
|
||||
<option value="restaurant">음식점</option>
|
||||
<option value="cafe">카페/디저트</option>
|
||||
<option value="retail">소매/유통</option>
|
||||
<option value="beauty">뷰티/미용</option>
|
||||
<option value="fitness">피트니스/헬스</option>
|
||||
<option value="education">교육/학원</option>
|
||||
<option value="service">서비스업</option>
|
||||
<option value="other">기타</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 주소 -->
|
||||
<div class="form-group">
|
||||
<label for="address" class="form-label form-label--required">주소</label>
|
||||
<div style="display: flex; gap: var(--spacing-s);">
|
||||
<input
|
||||
type="text"
|
||||
id="address"
|
||||
name="address"
|
||||
class="input"
|
||||
placeholder="주소를 검색하세요"
|
||||
readonly
|
||||
required
|
||||
style="flex: 1;"
|
||||
>
|
||||
<button type="button" id="addressSearchBtn" class="btn btn-secondary btn-medium">
|
||||
검색
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 상세 주소 -->
|
||||
<div class="form-group">
|
||||
<label for="addressDetail" class="form-label">상세 주소</label>
|
||||
<input
|
||||
type="text"
|
||||
id="addressDetail"
|
||||
name="addressDetail"
|
||||
class="input"
|
||||
placeholder="동, 호수 등 상세 주소 입력"
|
||||
autocomplete="address-line2"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- 사업자번호 -->
|
||||
<div class="form-group">
|
||||
<label for="businessNumber" class="form-label form-label--required">사업자번호</label>
|
||||
<input
|
||||
type="text"
|
||||
id="businessNumber"
|
||||
name="businessNumber"
|
||||
class="input"
|
||||
placeholder="000-00-00000"
|
||||
maxlength="12"
|
||||
autocomplete="off"
|
||||
required
|
||||
>
|
||||
<div id="businessNumberHelper" class="business-number-helper">
|
||||
<span>사업자번호 10자리를 입력하세요</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 선택 정보 -->
|
||||
<section class="mb-2xl">
|
||||
<h2 class="section-title">선택 정보</h2>
|
||||
<p class="section-subtitle">더 자세한 정보를 입력하면 맞춤형 이벤트를 제공해드려요</p>
|
||||
|
||||
<!-- 메뉴/상품 -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">메뉴/상품</label>
|
||||
<div id="menuItems" class="menu-items">
|
||||
<div class="menu-item">
|
||||
<input
|
||||
type="text"
|
||||
class="input menu-item__input"
|
||||
placeholder="예: 아메리카노, 케이크"
|
||||
maxlength="30"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="addMenuBtn" class="add-menu-button mt-m">
|
||||
<span style="font-size: 20px;">+</span>
|
||||
<span>메뉴 추가</span>
|
||||
</button>
|
||||
<span class="form-helper">최대 10개까지 추가할 수 있습니다</span>
|
||||
</div>
|
||||
|
||||
<!-- 매장 특징 -->
|
||||
<div class="form-group">
|
||||
<label for="storeFeatures" class="form-label">매장 특징</label>
|
||||
<textarea
|
||||
id="storeFeatures"
|
||||
name="storeFeatures"
|
||||
class="input"
|
||||
placeholder="매장의 특징이나 강점을 간단히 입력하세요 예: 강남역 2번 출구 앞, 24시간 운영, 주차 가능"
|
||||
rows="3"
|
||||
maxlength="200"
|
||||
style="resize: vertical; min-height: 80px;"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 대표 이미지 -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">대표 이미지</label>
|
||||
<div class="image-upload-area" id="imageUploadArea">
|
||||
<label class="image-upload-box" id="imageBox1">
|
||||
<input type="file" accept="image/*" data-index="0">
|
||||
<div class="image-upload-box__placeholder">
|
||||
<span>📷</span>
|
||||
<span class="image-upload-box__placeholder-text">추가</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="image-upload-box" id="imageBox2" style="display: none;">
|
||||
<input type="file" accept="image/*" data-index="1">
|
||||
<div class="image-upload-box__placeholder">
|
||||
<span>📷</span>
|
||||
<span class="image-upload-box__placeholder-text">추가</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="image-upload-box" id="imageBox3" style="display: none;">
|
||||
<input type="file" accept="image/*" data-index="2">
|
||||
<div class="image-upload-box__placeholder">
|
||||
<span>📷</span>
|
||||
<span class="image-upload-box__placeholder-text">추가</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<span class="form-helper">매장 또는 상품 사진을 최대 3장까지 업로드할 수 있습니다</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 제출 버튼 -->
|
||||
<button type="submit" class="btn btn-primary btn-large btn-block">
|
||||
완료하고 시작하기
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="js/common.js"></script>
|
||||
<script src="js/navigation.js"></script>
|
||||
<script src="js/modal.js"></script>
|
||||
<script src="js/form.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const { FormValidator, Toast, Dialog, createElement } = window.KTEvent;
|
||||
|
||||
// Menu Items Management
|
||||
const menuItemsContainer = document.getElementById('menuItems');
|
||||
const addMenuBtn = document.getElementById('addMenuBtn');
|
||||
let menuItemCount = 1;
|
||||
|
||||
addMenuBtn.addEventListener('click', () => {
|
||||
if (menuItemCount >= 10) {
|
||||
Toast.show('메뉴는 최대 10개까지 추가할 수 있습니다.', { type: 'warning' });
|
||||
return;
|
||||
}
|
||||
|
||||
const menuItem = createElement('div', 'menu-item');
|
||||
menuItem.innerHTML = `
|
||||
<input
|
||||
type="text"
|
||||
class="input menu-item__input"
|
||||
placeholder="예: 아메리카노, 케이크"
|
||||
maxlength="30"
|
||||
>
|
||||
<button type="button" class="menu-item__remove" aria-label="메뉴 삭제">×</button>
|
||||
`;
|
||||
|
||||
const removeBtn = menuItem.querySelector('.menu-item__remove');
|
||||
removeBtn.addEventListener('click', () => {
|
||||
menuItem.remove();
|
||||
menuItemCount--;
|
||||
updateAddButtonState();
|
||||
});
|
||||
|
||||
menuItemsContainer.appendChild(menuItem);
|
||||
menuItemCount++;
|
||||
updateAddButtonState();
|
||||
});
|
||||
|
||||
function updateAddButtonState() {
|
||||
if (menuItemCount >= 10) {
|
||||
addMenuBtn.disabled = true;
|
||||
addMenuBtn.style.opacity = '0.5';
|
||||
addMenuBtn.style.cursor = 'not-allowed';
|
||||
} else {
|
||||
addMenuBtn.disabled = false;
|
||||
addMenuBtn.style.opacity = '1';
|
||||
addMenuBtn.style.cursor = 'pointer';
|
||||
}
|
||||
}
|
||||
|
||||
// Image Upload Management
|
||||
const imageBoxes = [
|
||||
document.getElementById('imageBox1'),
|
||||
document.getElementById('imageBox2'),
|
||||
document.getElementById('imageBox3')
|
||||
];
|
||||
const uploadedImages = [null, null, null];
|
||||
|
||||
imageBoxes.forEach((box, index) => {
|
||||
const input = box.querySelector('input[type="file"]');
|
||||
|
||||
input.addEventListener('change', (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
// Validate file size (max 5MB)
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
Toast.show('이미지 크기는 5MB 이하여야 합니다.', { type: 'error' });
|
||||
input.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Preview image
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
box.innerHTML = `
|
||||
<img src="${e.target.result}" class="image-upload-box__preview" alt="매장 이미지 ${index + 1}">
|
||||
<button type="button" class="image-upload-box__remove" aria-label="이미지 삭제">×</button>
|
||||
`;
|
||||
|
||||
uploadedImages[index] = file;
|
||||
|
||||
// Add remove functionality
|
||||
const removeBtn = box.querySelector('.image-upload-box__remove');
|
||||
removeBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
removeImage(index);
|
||||
});
|
||||
|
||||
// Show next image box
|
||||
if (index < 2) {
|
||||
imageBoxes[index + 1].style.display = 'block';
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
});
|
||||
|
||||
function removeImage(index) {
|
||||
uploadedImages[index] = null;
|
||||
const box = imageBoxes[index];
|
||||
|
||||
box.innerHTML = `
|
||||
<input type="file" accept="image/*" data-index="${index}">
|
||||
<div class="image-upload-box__placeholder">
|
||||
<span>📷</span>
|
||||
<span class="image-upload-box__placeholder-text">추가</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Re-attach event listener
|
||||
const input = box.querySelector('input[type="file"]');
|
||||
input.addEventListener('change', (e) => {
|
||||
// Use the same logic as above (would be better to extract to a function)
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
Toast.show('이미지 크기는 5MB 이하여야 합니다.', { type: 'error' });
|
||||
input.value = '';
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
box.innerHTML = `
|
||||
<img src="${e.target.result}" class="image-upload-box__preview" alt="매장 이미지 ${index + 1}">
|
||||
<button type="button" class="image-upload-box__remove" aria-label="이미지 삭제">×</button>
|
||||
`;
|
||||
uploadedImages[index] = file;
|
||||
const removeBtn = box.querySelector('.image-upload-box__remove');
|
||||
removeBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
removeImage(index);
|
||||
});
|
||||
if (index < 2) {
|
||||
imageBoxes[index + 1].style.display = 'block';
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
// Hide subsequent boxes if this one is removed
|
||||
for (let i = index + 1; i < 3; i++) {
|
||||
if (!uploadedImages[i]) {
|
||||
imageBoxes[i].style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Address Search (Mock)
|
||||
const addressSearchBtn = document.getElementById('addressSearchBtn');
|
||||
const addressInput = document.getElementById('address');
|
||||
|
||||
addressSearchBtn.addEventListener('click', async () => {
|
||||
const result = await Dialog.confirm({
|
||||
title: '주소 검색',
|
||||
message: '실제 서비스에서는 카카오 주소 검색 API가 연동됩니다.\n\n프로토타입에서는 샘플 주소를 사용하시겠습니까?',
|
||||
confirmText: '샘플 주소 사용',
|
||||
cancelText: '취소'
|
||||
});
|
||||
|
||||
if (result) {
|
||||
addressInput.value = '서울특별시 강남구 테헤란로 152';
|
||||
Toast.show('주소가 입력되었습니다.', { type: 'success' });
|
||||
}
|
||||
});
|
||||
|
||||
// Business Number Formatting
|
||||
const businessNumberInput = document.getElementById('businessNumber');
|
||||
const businessNumberHelper = document.getElementById('businessNumberHelper');
|
||||
|
||||
businessNumberInput.addEventListener('input', (e) => {
|
||||
let value = e.target.value.replace(/[^0-9]/g, '');
|
||||
if (value.length > 10) value = value.slice(0, 10);
|
||||
|
||||
if (value.length >= 5) {
|
||||
value = value.replace(/(\d{3})(\d{2})(\d{0,5})/, '$1-$2-$3');
|
||||
} else if (value.length >= 3) {
|
||||
value = value.replace(/(\d{3})(\d{0,2})/, '$1-$2');
|
||||
}
|
||||
|
||||
e.target.value = value;
|
||||
|
||||
// Validate business number when 10 digits are entered
|
||||
const digitsOnly = value.replace(/[^0-9]/g, '');
|
||||
if (digitsOnly.length === 10) {
|
||||
validateBusinessNumber(digitsOnly);
|
||||
} else {
|
||||
businessNumberHelper.innerHTML = '<span>사업자번호 10자리를 입력하세요</span>';
|
||||
businessNumberHelper.className = 'business-number-helper';
|
||||
}
|
||||
});
|
||||
|
||||
function validateBusinessNumber(number) {
|
||||
// Mock validation - in real app would call tax office API
|
||||
// Simple checksum validation for Korean business numbers
|
||||
const weights = [1, 3, 7, 1, 3, 7, 1, 3, 5];
|
||||
let sum = 0;
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
sum += parseInt(number[i]) * weights[i];
|
||||
}
|
||||
|
||||
sum += Math.floor((parseInt(number[8]) * 5) / 10);
|
||||
const checkDigit = (10 - (sum % 10)) % 10;
|
||||
|
||||
if (checkDigit === parseInt(number[9])) {
|
||||
businessNumberHelper.innerHTML = '<span>✓ 유효한 사업자번호입니다</span>';
|
||||
businessNumberHelper.className = 'business-number-helper business-number-helper--verified';
|
||||
} else {
|
||||
businessNumberHelper.innerHTML = '<span>✗ 유효하지 않은 사업자번호입니다</span>';
|
||||
businessNumberHelper.className = 'business-number-helper business-number-helper--error';
|
||||
}
|
||||
}
|
||||
|
||||
// Form Validation
|
||||
const storeForm = document.getElementById('storeForm');
|
||||
const validator = new FormValidator(storeForm, {
|
||||
validateOnBlur: true,
|
||||
validateOnInput: true,
|
||||
scrollToError: true
|
||||
});
|
||||
|
||||
// Add validation rules
|
||||
validator
|
||||
.addField('storeName', {
|
||||
required: true,
|
||||
minLength: 2,
|
||||
maxLength: 50,
|
||||
messages: {
|
||||
required: '매장명을 입력해주세요.',
|
||||
minLength: '매장명은 2자 이상이어야 합니다.',
|
||||
maxLength: '매장명은 50자 이하여야 합니다.'
|
||||
}
|
||||
})
|
||||
.addField('businessType', {
|
||||
required: true,
|
||||
custom: (value) => value !== '',
|
||||
messages: {
|
||||
required: '업종을 선택해주세요.',
|
||||
custom: '업종을 선택해주세요.'
|
||||
}
|
||||
})
|
||||
.addField('address', {
|
||||
required: true,
|
||||
messages: {
|
||||
required: '주소를 검색해주세요.'
|
||||
}
|
||||
})
|
||||
.addField('businessNumber', {
|
||||
required: true,
|
||||
custom: (value) => {
|
||||
const digitsOnly = value.replace(/[^0-9]/g, '');
|
||||
if (digitsOnly.length !== 10) return false;
|
||||
|
||||
// Validate checksum
|
||||
const weights = [1, 3, 7, 1, 3, 7, 1, 3, 5];
|
||||
let sum = 0;
|
||||
for (let i = 0; i < 9; i++) {
|
||||
sum += parseInt(digitsOnly[i]) * weights[i];
|
||||
}
|
||||
sum += Math.floor((parseInt(digitsOnly[8]) * 5) / 10);
|
||||
const checkDigit = (10 - (sum % 10)) % 10;
|
||||
|
||||
return checkDigit === parseInt(digitsOnly[9]);
|
||||
},
|
||||
messages: {
|
||||
required: '사업자번호를 입력해주세요.',
|
||||
custom: '유효한 사업자번호를 입력해주세요.'
|
||||
}
|
||||
});
|
||||
|
||||
// Override onSubmit
|
||||
validator.onSubmit = function() {
|
||||
const values = this.getValues();
|
||||
const submitButton = storeForm.querySelector('button[type="submit"]');
|
||||
|
||||
// Collect menu items
|
||||
const menuInputs = document.querySelectorAll('.menu-item__input');
|
||||
const menuItems = Array.from(menuInputs)
|
||||
.map(input => input.value.trim())
|
||||
.filter(value => value !== '');
|
||||
|
||||
// Show loading state
|
||||
submitButton.disabled = true;
|
||||
submitButton.classList.add('btn-loading');
|
||||
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
// Save store data
|
||||
const storeData = {
|
||||
storeName: values.storeName,
|
||||
businessType: values.businessType,
|
||||
address: values.address,
|
||||
addressDetail: values.addressDetail || '',
|
||||
businessNumber: values.businessNumber,
|
||||
menuItems: menuItems,
|
||||
storeFeatures: values.storeFeatures || '',
|
||||
images: uploadedImages.filter(img => img !== null).length,
|
||||
registeredAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
window.KTEvent.storage.set('storeData', storeData);
|
||||
|
||||
Toast.show('매장 정보가 등록되었습니다!', {
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
// Redirect to home
|
||||
setTimeout(() => {
|
||||
window.location.href = '03-홈화면.html';
|
||||
}, 1000);
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,470 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<!-- Pretendard Font -->
|
||||
<link rel="stylesheet" as="style" crossorigin
|
||||
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable.min.css">
|
||||
|
||||
<!-- Stylesheets -->
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/reset.css">
|
||||
<link rel="stylesheet" href="css/layout.css">
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<link rel="stylesheet" href="css/components/buttons.css">
|
||||
<link rel="stylesheet" href="css/components/cards.css">
|
||||
<link rel="stylesheet" href="css/components/navigation.css">
|
||||
|
||||
<style>
|
||||
/* Home Screen Specific Styles */
|
||||
.home-header {
|
||||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
|
||||
color: var(--color-white);
|
||||
padding: var(--spacing-xl) var(--spacing-l) var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.home-header__greeting {
|
||||
font-size: var(--font-size-h2);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.home-header__subtitle {
|
||||
font-size: var(--font-size-body-medium);
|
||||
opacity: 0.9;
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.quick-action-card {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--spacing-l);
|
||||
text-align: center;
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.quick-action-card:hover {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.quick-action-card__icon {
|
||||
font-size: 32px;
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.quick-action-card__title {
|
||||
font-size: var(--font-size-body-medium);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
padding: var(--spacing-l);
|
||||
background-color: var(--color-white);
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.stat-card--compact {
|
||||
background-color: var(--color-white);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--spacing-m);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-card--compact__value {
|
||||
font-size: var(--font-size-h1);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-primary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.stat-card--compact__label {
|
||||
font-size: var(--font-size-body-small);
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.section-header__title {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
.section-header__link {
|
||||
font-size: var(--font-size-body-small);
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.section-header__link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.events-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: var(--spacing-2xl);
|
||||
background-color: var(--color-gray-100);
|
||||
border-radius: var(--radius-m);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.empty-state__icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.empty-state__title {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-black);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.empty-state__description {
|
||||
font-size: var(--font-size-body-medium);
|
||||
color: var(--color-gray-600);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.ai-insights {
|
||||
background: linear-gradient(135deg, #F0F7FF 0%, #E3F2FD 100%);
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--spacing-l);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.ai-insights__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.ai-insights__icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.ai-insights__title {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.ai-insights__content {
|
||||
font-size: var(--font-size-body-medium);
|
||||
color: var(--color-gray-700);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.quick-actions {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<!-- Home Header -->
|
||||
<header class="home-header">
|
||||
<div class="home-header__greeting" id="greeting">안녕하세요!</div>
|
||||
<div class="home-header__subtitle">AI로 간편하게 이벤트를 만들어보세요</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="quick-actions">
|
||||
<div class="quick-action-card" onclick="location.href='04-이벤트목적선택.html'">
|
||||
<div class="quick-action-card__icon">✨</div>
|
||||
<div class="quick-action-card__title">새 이벤트</div>
|
||||
</div>
|
||||
<div class="quick-action-card" onclick="location.href='25-이벤트목록.html'">
|
||||
<div class="quick-action-card__icon">📋</div>
|
||||
<div class="quick-action-card__title">이벤트 목록</div>
|
||||
</div>
|
||||
<div class="quick-action-card" onclick="location.href='21-실시간대시보드.html'">
|
||||
<div class="quick-action-card__icon">📊</div>
|
||||
<div class="quick-action-card__title">실시간 현황</div>
|
||||
</div>
|
||||
<div class="quick-action-card" onclick="location.href='26-마이페이지.html'">
|
||||
<div class="quick-action-card__icon">⚙️</div>
|
||||
<div class="quick-action-card__title">설정</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="page-main" style="padding-bottom: var(--bottom-nav-height);">
|
||||
<div class="stats-section">
|
||||
<!-- Stats Grid -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card--compact">
|
||||
<div class="stat-card--compact__value" id="totalEvents">0</div>
|
||||
<div class="stat-card--compact__label">진행 중인<br>이벤트</div>
|
||||
</div>
|
||||
<div class="stat-card--compact">
|
||||
<div class="stat-card--compact__value" id="totalParticipants">0</div>
|
||||
<div class="stat-card--compact__label">총 참여자</div>
|
||||
</div>
|
||||
<div class="stat-card--compact">
|
||||
<div class="stat-card--compact__value" id="conversionRate">0%</div>
|
||||
<div class="stat-card--compact__label">전환율</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI Insights -->
|
||||
<div class="ai-insights">
|
||||
<div class="ai-insights__header">
|
||||
<span class="ai-insights__icon">🤖</span>
|
||||
<h3 class="ai-insights__title">AI 인사이트</h3>
|
||||
</div>
|
||||
<div class="ai-insights__content" id="aiInsight">
|
||||
이벤트를 생성하시면 AI가 맞춤형 인사이트를 제공해드립니다.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Events Section -->
|
||||
<div class="section-header">
|
||||
<h2 class="section-header__title">최근 이벤트</h2>
|
||||
<a href="25-이벤트목록.html" class="section-header__link">
|
||||
전체보기
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor">
|
||||
<path d="M6 12l4-4-4-4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div class="empty-state" id="emptyState">
|
||||
<div class="empty-state__icon">🎯</div>
|
||||
<h3 class="empty-state__title">첫 이벤트를 만들어보세요!</h3>
|
||||
<p class="empty-state__description">
|
||||
AI가 업종과 목적에 맞는<br>
|
||||
최적의 이벤트를 추천해드립니다
|
||||
</p>
|
||||
<button
|
||||
class="btn btn-primary btn-large"
|
||||
onclick="location.href='04-이벤트목적선택.html'"
|
||||
>
|
||||
AI 이벤트 만들기
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Recent Events List (Hidden initially) -->
|
||||
<div class="events-list" id="eventsList" style="display: none;">
|
||||
<!-- Events will be dynamically added here -->
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav">
|
||||
<a href="03-홈화면.html" class="bottom-nav__item bottom-nav__item--active">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">홈</span>
|
||||
</a>
|
||||
<a href="25-이벤트목록.html" class="bottom-nav__item">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm2 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">이벤트</span>
|
||||
</a>
|
||||
<a href="04-이벤트목적선택.html" class="bottom-nav__item bottom-nav__item--primary">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M12 6v12M6 12h12" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">만들기</span>
|
||||
</a>
|
||||
<a href="21-실시간대시보드.html" class="bottom-nav__item">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">분석</span>
|
||||
</a>
|
||||
<a href="26-마이페이지.html" class="bottom-nav__item">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">MY</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="js/common.js"></script>
|
||||
<script src="js/navigation.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const { storage, createElement } = window.KTEvent;
|
||||
|
||||
// Get user and store data
|
||||
const user = storage.get('user');
|
||||
const storeData = storage.get('storeData');
|
||||
const signupData = storage.get('signupData');
|
||||
|
||||
// Update greeting with user name
|
||||
const greetingEl = document.getElementById('greeting');
|
||||
if (signupData && signupData.name) {
|
||||
greetingEl.textContent = `${signupData.name}님, 안녕하세요!`;
|
||||
} else if (user && user.email) {
|
||||
const userName = user.email.split('@')[0];
|
||||
greetingEl.textContent = `${userName}님, 안녕하세요!`;
|
||||
}
|
||||
|
||||
// Load events from storage
|
||||
const events = storage.get('events', []);
|
||||
|
||||
// Update stats
|
||||
document.getElementById('totalEvents').textContent = events.length;
|
||||
|
||||
// Calculate total participants
|
||||
const totalParticipants = events.reduce((sum, event) => {
|
||||
return sum + (event.participants || 0);
|
||||
}, 0);
|
||||
document.getElementById('totalParticipants').textContent = totalParticipants.toLocaleString();
|
||||
|
||||
// Calculate conversion rate
|
||||
const totalViews = events.reduce((sum, event) => sum + (event.views || 0), 0);
|
||||
const conversionRate = totalViews > 0
|
||||
? ((totalParticipants / totalViews) * 100).toFixed(1)
|
||||
: 0;
|
||||
document.getElementById('conversionRate').textContent = `${conversionRate}%`;
|
||||
|
||||
// Update AI Insights
|
||||
const aiInsightEl = document.getElementById('aiInsight');
|
||||
if (events.length > 0) {
|
||||
const recentEvent = events[0];
|
||||
const insights = [
|
||||
`최근 이벤트의 참여율이 ${conversionRate}%입니다. 업계 평균(2.5%)보다 우수해요! 🎉`,
|
||||
`${storeData?.businessType === 'restaurant' ? '음식점' : '매장'}의 경우, 오후 2-5시 이벤트 참여가 가장 활발합니다. 이 시간대를 활용해보세요! ⏰`,
|
||||
`SNS 공유를 통한 참여가 65%를 차지하고 있습니다. 공유 인센티브를 강화해보는 건 어떨까요? 📱`
|
||||
];
|
||||
const randomInsight = insights[Math.floor(Math.random() * insights.length)];
|
||||
aiInsightEl.textContent = randomInsight;
|
||||
} else if (storeData) {
|
||||
const businessTypeInsights = {
|
||||
'restaurant': '음식점은 "1+1 할인" 이벤트가 가장 효과적입니다. 런치타임이나 저녁시간대를 타겟팅해보세요!',
|
||||
'cafe': '카페는 "음료 무료 업그레이드" 이벤트가 인기입니다. SNS 인증샷 이벤트도 추천드려요!',
|
||||
'retail': '소매점은 "첫 구매 할인" 이벤트로 신규 고객 유치에 집중해보세요.',
|
||||
'beauty': '뷰티샵은 "친구 추천" 이벤트가 효과적입니다. 재방문 쿠폰을 활용해보세요!',
|
||||
'fitness': '헬스장은 "무료 체험권" 이벤트가 가장 효과적입니다. 그룹 할인도 고려해보세요!',
|
||||
'education': '학원은 "첫 달 할인" 이벤트가 인기입니다. 상담 예약 이벤트도 추천드려요!',
|
||||
'service': '서비스업은 "첫 이용 할인" 이벤트로 신규 고객을 유치해보세요.',
|
||||
'other': '업종에 맞는 맞춤형 이벤트를 AI가 추천해드립니다. 지금 시작해보세요!'
|
||||
};
|
||||
const insight = businessTypeInsights[storeData.businessType] || businessTypeInsights['other'];
|
||||
aiInsightEl.textContent = `🤖 ${insight}`;
|
||||
}
|
||||
|
||||
// Render events or empty state
|
||||
const emptyState = document.getElementById('emptyState');
|
||||
const eventsList = document.getElementById('eventsList');
|
||||
|
||||
if (events.length === 0) {
|
||||
emptyState.style.display = 'block';
|
||||
eventsList.style.display = 'none';
|
||||
} else {
|
||||
emptyState.style.display = 'none';
|
||||
eventsList.style.display = 'flex';
|
||||
|
||||
// Render recent events (max 3)
|
||||
const recentEvents = events.slice(0, 3);
|
||||
recentEvents.forEach(event => {
|
||||
const eventCard = createElement('div', 'event-card');
|
||||
|
||||
const statusBadge = event.status === 'active' ?
|
||||
'<span class="badge badge-success">진행중</span>' :
|
||||
event.status === 'scheduled' ?
|
||||
'<span class="badge badge-warning">예정</span>' :
|
||||
'<span class="badge badge-secondary">종료</span>';
|
||||
|
||||
eventCard.innerHTML = `
|
||||
<div class="event-card__image" style="background: linear-gradient(135deg, ${event.color || '#E31E24'} 0%, ${event.colorDark || '#C71820'} 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 48px;">
|
||||
${event.icon || '🎉'}
|
||||
</div>
|
||||
<div class="event-card__content">
|
||||
<div class="event-card__header">
|
||||
<h3 class="event-card__title">${event.title || '이벤트 제목'}</h3>
|
||||
${statusBadge}
|
||||
</div>
|
||||
<p class="event-card__description">${event.description || '이벤트 설명'}</p>
|
||||
<div class="event-card__meta">
|
||||
<span class="event-card__date">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M11 1v2H5V1H3v2H2c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2h-1V1h-2zm3 14H2V8h12v7zm0-9H2V5h12v1z"/>
|
||||
</svg>
|
||||
${event.startDate ? new Date(event.startDate).toLocaleDateString('ko-KR') : '날짜 미정'}
|
||||
</span>
|
||||
<span class="event-card__participants">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 8c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
|
||||
</svg>
|
||||
${event.participants || 0}명 참여
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
eventCard.style.cursor = 'pointer';
|
||||
eventCard.addEventListener('click', () => {
|
||||
// Navigate to event detail page (to be implemented)
|
||||
console.log('Navigate to event:', event.id);
|
||||
});
|
||||
|
||||
eventsList.appendChild(eventCard);
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-redirect check (commented out for prototype)
|
||||
// if (!user) {
|
||||
// window.location.href = '00-로그인.html';
|
||||
// } else if (!storeData) {
|
||||
// window.location.href = '02-매장정보등록.html';
|
||||
// }
|
||||
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,516 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<!-- Pretendard Font -->
|
||||
<link rel="stylesheet" as="style" crossorigin
|
||||
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable.min.css">
|
||||
|
||||
<!-- Stylesheets -->
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/reset.css">
|
||||
<link rel="stylesheet" href="css/layout.css">
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<link rel="stylesheet" href="css/components/buttons.css">
|
||||
<link rel="stylesheet" href="css/components/cards.css">
|
||||
<link rel="stylesheet" href="css/components/navigation.css">
|
||||
|
||||
<style>
|
||||
/* Event Purpose Selection Specific Styles */
|
||||
.purpose-selection {
|
||||
padding: var(--spacing-l);
|
||||
padding-bottom: calc(var(--bottom-nav-height) + var(--spacing-l));
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.intro-section {
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.intro-section__icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.intro-section__title {
|
||||
font-size: var(--font-size-h1);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-black);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.intro-section__subtitle {
|
||||
font-size: var(--font-size-body-medium);
|
||||
color: var(--color-gray-600);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.purpose-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.purpose-card {
|
||||
position: relative;
|
||||
background-color: var(--color-white);
|
||||
border: 2px solid var(--color-gray-300);
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--spacing-l);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.purpose-card:hover {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.purpose-card--selected {
|
||||
border-color: var(--color-primary);
|
||||
border-width: 2px;
|
||||
background-color: #FFF5F5;
|
||||
box-shadow: var(--shadow-primary);
|
||||
}
|
||||
|
||||
.purpose-card__radio {
|
||||
appearance: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid var(--color-gray-400);
|
||||
border-radius: 50%;
|
||||
margin-top: 4px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.purpose-card--selected .purpose-card__radio {
|
||||
border-color: var(--color-primary);
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.purpose-card--selected .purpose-card__radio::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: var(--color-white);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.purpose-card__content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.purpose-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.purpose-card__icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.purpose-card__title {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
.purpose-card__description {
|
||||
font-size: var(--font-size-body-medium);
|
||||
color: var(--color-gray-600);
|
||||
line-height: 1.6;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.purpose-card__tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 12px;
|
||||
background-color: var(--color-secondary-light);
|
||||
color: var(--color-secondary);
|
||||
font-size: var(--font-size-body-small);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
border-radius: var(--radius-s);
|
||||
}
|
||||
|
||||
.purpose-card__examples {
|
||||
font-size: var(--font-size-body-small);
|
||||
color: var(--color-gray-500);
|
||||
margin-top: var(--spacing-s);
|
||||
}
|
||||
|
||||
.ai-tip {
|
||||
background: linear-gradient(135deg, #F0F7FF 0%, #E3F2FD 100%);
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--spacing-l);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.ai-tip__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.ai-tip__icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.ai-tip__title {
|
||||
font-size: var(--font-size-body-large);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.ai-tip__content {
|
||||
font-size: var(--font-size-body-medium);
|
||||
color: var(--color-gray-700);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
position: fixed;
|
||||
bottom: var(--bottom-nav-height);
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--color-white);
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
border-top: 1px solid var(--color-gray-300);
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.cta-section__inner {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.purpose-options {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<!-- App Bar -->
|
||||
<header class="app-bar">
|
||||
<div class="app-bar__container">
|
||||
<div class="app-bar__left">
|
||||
<svg class="app-bar__back" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="M19 12H5M12 19l-7-7 7-7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="app-bar__title">새 이벤트 만들기</h1>
|
||||
<div class="app-bar__right"></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="page-main">
|
||||
<div class="purpose-selection">
|
||||
<!-- Intro Section -->
|
||||
<div class="intro-section">
|
||||
<div class="intro-section__icon">🎯</div>
|
||||
<h2 class="intro-section__title">이벤트 목적을 선택하세요</h2>
|
||||
<p class="intro-section__subtitle">
|
||||
목적에 맞는 최적의 이벤트를<br>
|
||||
AI가 추천해드립니다
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- AI Tip -->
|
||||
<div class="ai-tip">
|
||||
<div class="ai-tip__header">
|
||||
<span class="ai-tip__icon">💡</span>
|
||||
<span class="ai-tip__title">AI TIP</span>
|
||||
</div>
|
||||
<div class="ai-tip__content" id="aiTip">
|
||||
선택하신 목적에 맞는 최신 트렌드와 성공 사례를 분석하여 맞춤형 이벤트를 제안해드립니다.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Purpose Options -->
|
||||
<div class="purpose-options" id="purposeOptions">
|
||||
<label class="purpose-card" data-purpose="new-customers">
|
||||
<input type="radio" name="purpose" value="new-customers" class="purpose-card__radio">
|
||||
<div class="purpose-card__content">
|
||||
<div class="purpose-card__header">
|
||||
<span class="purpose-card__icon">👥</span>
|
||||
<h3 class="purpose-card__title">신규 고객 유치</h3>
|
||||
</div>
|
||||
<p class="purpose-card__description">
|
||||
새로운 고객을 끌어들이고 매장 인지도를 높이고 싶어요
|
||||
</p>
|
||||
<span class="purpose-card__tag">AI 추천</span>
|
||||
<div class="purpose-card__examples">
|
||||
예: 첫 방문 할인, 친구 추천 이벤트
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="purpose-card" data-purpose="repeat-visits">
|
||||
<input type="radio" name="purpose" value="repeat-visits" class="purpose-card__radio">
|
||||
<div class="purpose-card__content">
|
||||
<div class="purpose-card__header">
|
||||
<span class="purpose-card__icon">🔄</span>
|
||||
<h3 class="purpose-card__title">재방문 유도</h3>
|
||||
</div>
|
||||
<p class="purpose-card__description">
|
||||
기존 고객의 재방문을 늘리고 충성도를 높이고 싶어요
|
||||
</p>
|
||||
<div class="purpose-card__examples">
|
||||
예: 스탬프 적립, 재방문 쿠폰
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="purpose-card" data-purpose="sales-boost">
|
||||
<input type="radio" name="purpose" value="sales-boost" class="purpose-card__radio">
|
||||
<div class="purpose-card__content">
|
||||
<div class="purpose-card__header">
|
||||
<span class="purpose-card__icon">💰</span>
|
||||
<h3 class="purpose-card__title">매출 증대</h3>
|
||||
</div>
|
||||
<p class="purpose-card__description">
|
||||
객단가를 올리고 매출을 늘리고 싶어요
|
||||
</p>
|
||||
<div class="purpose-card__examples">
|
||||
예: 세트 할인, 한정 특가 이벤트
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="purpose-card" data-purpose="brand-awareness">
|
||||
<input type="radio" name="purpose" value="brand-awareness" class="purpose-card__radio">
|
||||
<div class="purpose-card__content">
|
||||
<div class="purpose-card__header">
|
||||
<span class="purpose-card__icon">⭐</span>
|
||||
<h3 class="purpose-card__title">브랜드 인지도</h3>
|
||||
</div>
|
||||
<p class="purpose-card__description">
|
||||
매장을 더 많은 사람에게 알리고 SNS에서 화제가 되고 싶어요
|
||||
</p>
|
||||
<div class="purpose-card__examples">
|
||||
예: SNS 인증샷, 바이럴 이벤트
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="purpose-card" data-purpose="engagement">
|
||||
<input type="radio" name="purpose" value="engagement" class="purpose-card__radio">
|
||||
<div class="purpose-card__content">
|
||||
<div class="purpose-card__header">
|
||||
<span class="purpose-card__icon">🎉</span>
|
||||
<h3 class="purpose-card__title">고객 참여 활성화</h3>
|
||||
</div>
|
||||
<p class="purpose-card__description">
|
||||
고객과 더 많이 소통하고 커뮤니티를 만들고 싶어요
|
||||
</p>
|
||||
<div class="purpose-card__examples">
|
||||
예: 리뷰 이벤트, 고객 투표
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="purpose-card" data-purpose="seasonal">
|
||||
<input type="radio" name="purpose" value="seasonal" class="purpose-card__radio">
|
||||
<div class="purpose-card__content">
|
||||
<div class="purpose-card__header">
|
||||
<span class="purpose-card__icon">🎊</span>
|
||||
<h3 class="purpose-card__title">시즌/기념일 마케팅</h3>
|
||||
</div>
|
||||
<p class="purpose-card__description">
|
||||
계절이나 기념일에 맞는 특별한 이벤트를 하고 싶어요
|
||||
</p>
|
||||
<div class="purpose-card__examples">
|
||||
예: 크리스마스, 창립기념일 이벤트
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- CTA Section -->
|
||||
<div class="cta-section">
|
||||
<div class="cta-section__inner">
|
||||
<button
|
||||
type="button"
|
||||
id="nextButton"
|
||||
class="btn btn-primary btn-large btn-block"
|
||||
disabled
|
||||
>
|
||||
AI 트렌드 분석 시작
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav">
|
||||
<a href="03-홈화면.html" class="bottom-nav__item">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">홈</span>
|
||||
</a>
|
||||
<a href="25-이벤트목록.html" class="bottom-nav__item">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm2 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">이벤트</span>
|
||||
</a>
|
||||
<a href="04-이벤트목적선택.html" class="bottom-nav__item bottom-nav__item--active bottom-nav__item--primary">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M12 6v12M6 12h12" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">만들기</span>
|
||||
</a>
|
||||
<a href="21-실시간대시보드.html" class="bottom-nav__item">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">분석</span>
|
||||
</a>
|
||||
<a href="26-마이페이지.html" class="bottom-nav__item">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">MY</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="js/common.js"></script>
|
||||
<script src="js/navigation.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const { storage, addClass, removeClass } = window.KTEvent;
|
||||
|
||||
const purposeCards = document.querySelectorAll('.purpose-card');
|
||||
const purposeOptions = document.querySelectorAll('input[name="purpose"]');
|
||||
const nextButton = document.getElementById('nextButton');
|
||||
const aiTip = document.getElementById('aiTip');
|
||||
|
||||
let selectedPurpose = null;
|
||||
|
||||
// Purpose-specific AI tips
|
||||
const aiTips = {
|
||||
'new-customers': '신규 고객 유치는 SNS 광고와 친구 추천이 가장 효과적입니다. 첫 방문 혜택을 강조하면 참여율이 평균 42% 증가합니다.',
|
||||
'repeat-visits': '재방문 유도는 스탬프 적립과 회원 등급제가 효과적입니다. 3회 방문 고객의 재방문율은 첫 방문 대비 3.2배 높습니다.',
|
||||
'sales-boost': '매출 증대는 세트 할인과 한정 수량 특가가 효과적입니다. 긴급성을 강조하면 평균 객단가가 28% 증가합니다.',
|
||||
'brand-awareness': 'SNS 인증샷 이벤트는 바이럴 효과가 가장 높습니다. 해시태그를 활용하면 도달 범위가 평균 5배 증가합니다.',
|
||||
'engagement': '고객 참여는 리뷰 이벤트와 투표가 효과적입니다. 참여형 이벤트는 브랜드 충성도를 47% 향상시킵니다.',
|
||||
'seasonal': '시즌 이벤트는 2주 전부터 준비하면 효과가 가장 좋습니다. 연휴 직전 참여율이 평균 대비 65% 높습니다.'
|
||||
};
|
||||
|
||||
// Get store data for personalized tips
|
||||
const storeData = storage.get('storeData');
|
||||
|
||||
// Handle purpose card selection
|
||||
purposeCards.forEach(card => {
|
||||
card.addEventListener('click', () => {
|
||||
// Remove selected class from all cards
|
||||
purposeCards.forEach(c => removeClass(c, 'purpose-card--selected'));
|
||||
|
||||
// Add selected class to clicked card
|
||||
addClass(card, 'purpose-card--selected');
|
||||
|
||||
// Get the selected purpose
|
||||
const purpose = card.getAttribute('data-purpose');
|
||||
selectedPurpose = purpose;
|
||||
|
||||
// Update radio button
|
||||
const radio = card.querySelector('input[type="radio"]');
|
||||
radio.checked = true;
|
||||
|
||||
// Update AI tip
|
||||
if (aiTips[purpose]) {
|
||||
aiTip.textContent = aiTips[purpose];
|
||||
}
|
||||
|
||||
// Enable next button
|
||||
nextButton.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
// Handle radio button change (for keyboard navigation)
|
||||
purposeOptions.forEach(option => {
|
||||
option.addEventListener('change', () => {
|
||||
const purpose = option.value;
|
||||
const card = option.closest('.purpose-card');
|
||||
|
||||
purposeCards.forEach(c => removeClass(c, 'purpose-card--selected'));
|
||||
addClass(card, 'purpose-card--selected');
|
||||
|
||||
selectedPurpose = purpose;
|
||||
|
||||
if (aiTips[purpose]) {
|
||||
aiTip.textContent = aiTips[purpose];
|
||||
}
|
||||
|
||||
nextButton.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
// Handle next button click
|
||||
nextButton.addEventListener('click', () => {
|
||||
if (!selectedPurpose) return;
|
||||
|
||||
// Show loading state
|
||||
nextButton.disabled = true;
|
||||
nextButton.classList.add('btn-loading');
|
||||
nextButton.textContent = 'AI 분석 중...';
|
||||
|
||||
// Save selected purpose
|
||||
const eventDraft = storage.get('eventDraft', {});
|
||||
eventDraft.purpose = selectedPurpose;
|
||||
eventDraft.purposeSelectedAt = new Date().toISOString();
|
||||
storage.set('eventDraft', eventDraft);
|
||||
|
||||
// Simulate AI analysis
|
||||
setTimeout(() => {
|
||||
window.location.href = '05-AI트렌드분석결과.html';
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
// Auto-select "new-customers" if it's recommended
|
||||
if (storeData && storeData.businessType) {
|
||||
// Auto-select and highlight AI recommended option
|
||||
const recommendedCard = document.querySelector('[data-purpose="new-customers"]');
|
||||
if (recommendedCard) {
|
||||
setTimeout(() => {
|
||||
recommendedCard.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,680 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<!-- Pretendard Font -->
|
||||
<link rel="stylesheet" as="style" crossorigin
|
||||
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable.min.css">
|
||||
|
||||
<!-- Stylesheets -->
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/reset.css">
|
||||
<link rel="stylesheet" href="css/layout.css">
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<link rel="stylesheet" href="css/components/buttons.css">
|
||||
<link rel="stylesheet" href="css/components/cards.css">
|
||||
<link rel="stylesheet" href="css/components/navigation.css">
|
||||
<link rel="stylesheet" href="css/components/loaders.css">
|
||||
|
||||
<style>
|
||||
/* AI Trend Analysis Specific Styles */
|
||||
.trend-analysis {
|
||||
padding-bottom: calc(var(--bottom-nav-height) + 80px);
|
||||
}
|
||||
|
||||
.analysis-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 60vh;
|
||||
padding: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.analysis-loading__icon {
|
||||
font-size: 80px;
|
||||
margin-bottom: var(--spacing-l);
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.1); opacity: 0.8; }
|
||||
}
|
||||
|
||||
.analysis-loading__title {
|
||||
font-size: var(--font-size-h2);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-black);
|
||||
margin-bottom: var(--spacing-s);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.analysis-loading__subtitle {
|
||||
font-size: var(--font-size-body-medium);
|
||||
color: var(--color-gray-600);
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.analysis-results {
|
||||
display: none;
|
||||
padding: var(--spacing-l);
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
animation: fadeIn var(--transition-normal) var(--ease-out);
|
||||
}
|
||||
|
||||
.results-header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
padding: var(--spacing-xl) 0;
|
||||
background: linear-gradient(135deg, var(--color-secondary-light) 0%, #E3F2FD 100%);
|
||||
border-radius: var(--radius-m);
|
||||
}
|
||||
|
||||
.results-header__icon {
|
||||
font-size: 56px;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.results-header__title {
|
||||
font-size: var(--font-size-h1);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-secondary);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.results-header__subtitle {
|
||||
font-size: var(--font-size-body-large);
|
||||
color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-black);
|
||||
margin-bottom: var(--spacing-l);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.section-title__icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.insight-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.insight-card {
|
||||
background-color: var(--color-white);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--spacing-l);
|
||||
}
|
||||
|
||||
.insight-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.insight-card__title {
|
||||
font-size: var(--font-size-body-large);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
.insight-card__icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.insight-card__value {
|
||||
font-size: var(--font-size-display);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-primary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.insight-card__description {
|
||||
font-size: var(--font-size-body-small);
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
.trend-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.trend-item {
|
||||
background-color: var(--color-white);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--spacing-l);
|
||||
}
|
||||
|
||||
.trend-item__header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.trend-item__rank {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
|
||||
color: var(--color-white);
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-bold);
|
||||
border-radius: var(--radius-s);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.trend-item__content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.trend-item__title {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-black);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.trend-item__description {
|
||||
font-size: var(--font-size-body-medium);
|
||||
color: var(--color-gray-600);
|
||||
line-height: 1.6;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.trend-item__stats {
|
||||
display: flex;
|
||||
gap: var(--spacing-l);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.trend-stat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
font-size: var(--font-size-body-small);
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
.trend-stat__label {
|
||||
color: var(--color-gray-500);
|
||||
}
|
||||
|
||||
.trend-stat__value {
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.ai-recommendation {
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFE5E5 100%);
|
||||
border: 2px solid var(--color-primary-light);
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--spacing-xl);
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.ai-recommendation__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.ai-recommendation__icon {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.ai-recommendation__title {
|
||||
font-size: var(--font-size-h2);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.ai-recommendation__content {
|
||||
font-size: var(--font-size-body-large);
|
||||
color: var(--color-gray-800);
|
||||
line-height: 1.8;
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.ai-recommendation__tags {
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ai-recommendation__tag {
|
||||
padding: 6px 12px;
|
||||
background-color: var(--color-white);
|
||||
color: var(--color-primary);
|
||||
font-size: var(--font-size-body-small);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
border-radius: var(--radius-s);
|
||||
border: 1px solid var(--color-primary);
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
position: fixed;
|
||||
bottom: var(--bottom-nav-height);
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--color-white);
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
border-top: 1px solid var(--color-gray-300);
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.cta-section__inner {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<!-- App Bar -->
|
||||
<header class="app-bar">
|
||||
<div class="app-bar__container">
|
||||
<div class="app-bar__left">
|
||||
<svg class="app-bar__back" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="M19 12H5M12 19l-7-7 7-7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="app-bar__title">AI 트렌드 분석</h1>
|
||||
<div class="app-bar__right"></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="page-main trend-analysis">
|
||||
<!-- Loading State -->
|
||||
<div class="analysis-loading" id="loadingState">
|
||||
<div class="analysis-loading__icon">🤖</div>
|
||||
<h2 class="analysis-loading__title">AI가 트렌드를 분석하고 있어요</h2>
|
||||
<p class="analysis-loading__subtitle">
|
||||
최신 데이터를 기반으로<br>
|
||||
업종별 성공 사례를 분석 중입니다
|
||||
</p>
|
||||
<div class="ai-progress">
|
||||
<div class="ai-progress__bar">
|
||||
<div class="ai-progress__fill" id="progressBar"></div>
|
||||
</div>
|
||||
<div class="ai-progress__text" id="progressText">데이터 수집 중...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Analysis Results -->
|
||||
<div class="analysis-results" id="analysisResults">
|
||||
<!-- Results Header -->
|
||||
<div class="results-header">
|
||||
<div class="results-header__icon">✨</div>
|
||||
<h2 class="results-header__title">분석이 완료되었습니다!</h2>
|
||||
<p class="results-header__subtitle" id="resultsSubtitle">신규 고객 유치를 위한 트렌드를 분석했어요</p>
|
||||
</div>
|
||||
|
||||
<!-- Key Insights -->
|
||||
<h3 class="section-title">
|
||||
<span class="section-title__icon">📊</span>
|
||||
<span>핵심 인사이트</span>
|
||||
</h3>
|
||||
|
||||
<div class="insight-cards">
|
||||
<div class="insight-card">
|
||||
<div class="insight-card__header">
|
||||
<span class="insight-card__title">평균 참여율</span>
|
||||
<span class="insight-card__icon">📈</span>
|
||||
</div>
|
||||
<div class="insight-card__value" id="insightEngagement">42%</div>
|
||||
<div class="insight-card__description">업계 평균 대비 +18%p 높은 수치</div>
|
||||
</div>
|
||||
|
||||
<div class="insight-card">
|
||||
<div class="insight-card__header">
|
||||
<span class="insight-card__title">최적 기간</span>
|
||||
<span class="insight-card__icon">📅</span>
|
||||
</div>
|
||||
<div class="insight-card__value" id="insightDuration">7일</div>
|
||||
<div class="insight-card__description">참여율이 가장 높은 이벤트 기간</div>
|
||||
</div>
|
||||
|
||||
<div class="insight-card">
|
||||
<div class="insight-card__header">
|
||||
<span class="insight-card__title">ROI</span>
|
||||
<span class="insight-card__icon">💰</span>
|
||||
</div>
|
||||
<div class="insight-card__value" id="insightROI">3.2배</div>
|
||||
<div class="insight-card__description">평균 투자 대비 수익률</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Top Trends -->
|
||||
<h3 class="section-title">
|
||||
<span class="section-title__icon">🔥</span>
|
||||
<span>인기 이벤트 유형 TOP 3</span>
|
||||
</h3>
|
||||
|
||||
<div class="trend-list" id="trendList">
|
||||
<!-- Trends will be dynamically added -->
|
||||
</div>
|
||||
|
||||
<!-- AI Recommendation -->
|
||||
<div class="ai-recommendation">
|
||||
<div class="ai-recommendation__header">
|
||||
<span class="ai-recommendation__icon">🤖</span>
|
||||
<h3 class="ai-recommendation__title">AI 추천</h3>
|
||||
</div>
|
||||
<p class="ai-recommendation__content" id="aiRecommendation">
|
||||
현재 트렌드를 분석한 결과, "친구 초대 이벤트"가 가장 효과적입니다. SNS 공유를 통한 바이럴 효과가 뛰어나며, 신규 고객 유치율이 평균 3.2배 높습니다.
|
||||
</p>
|
||||
<div class="ai-recommendation__tags">
|
||||
<span class="ai-recommendation__tag">#신규고객유치</span>
|
||||
<span class="ai-recommendation__tag">#바이럴효과</span>
|
||||
<span class="ai-recommendation__tag">#SNS마케팅</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- CTA Section -->
|
||||
<div class="cta-section" style="display: none;" id="ctaSection">
|
||||
<div class="cta-section__inner">
|
||||
<button
|
||||
type="button"
|
||||
id="nextButton"
|
||||
class="btn btn-primary btn-large btn-block"
|
||||
>
|
||||
이벤트 상품 추천 받기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav">
|
||||
<a href="03-홈화면.html" class="bottom-nav__item">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">홈</span>
|
||||
</a>
|
||||
<a href="25-이벤트목록.html" class="bottom-nav__item">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm2 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">이벤트</span>
|
||||
</a>
|
||||
<a href="04-이벤트목적선택.html" class="bottom-nav__item bottom-nav__item--active bottom-nav__item--primary">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M12 6v12M6 12h12" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">만들기</span>
|
||||
</a>
|
||||
<a href="21-실시간대시보드.html" class="bottom-nav__item">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">분석</span>
|
||||
</a>
|
||||
<a href="26-마이페이지.html" class="bottom-nav__item">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">MY</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="js/common.js"></script>
|
||||
<script src="js/navigation.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const { storage, createElement } = window.KTEvent;
|
||||
|
||||
// Get event draft data
|
||||
const eventDraft = storage.get('eventDraft', {});
|
||||
const storeData = storage.get('storeData', {});
|
||||
const purpose = eventDraft.purpose || 'new-customers';
|
||||
|
||||
// Purpose-specific data
|
||||
const trendData = {
|
||||
'new-customers': {
|
||||
subtitle: '신규 고객 유치를 위한 트렌드를 분석했어요',
|
||||
insights: {
|
||||
engagement: '42%',
|
||||
duration: '7일',
|
||||
roi: '3.2배'
|
||||
},
|
||||
trends: [
|
||||
{
|
||||
rank: 1,
|
||||
title: '친구 초대 이벤트',
|
||||
description: 'SNS 공유를 통한 바이럴 효과가 뛰어나며, 참여자의 평균 68%가 친구와 함께 방문합니다.',
|
||||
engagementRate: '68%',
|
||||
avgParticipants: '1,200명',
|
||||
roi: '3.5배'
|
||||
},
|
||||
{
|
||||
rank: 2,
|
||||
title: '첫 방문 할인',
|
||||
description: '신규 고객 대상 할인으로 방문 장벽을 낮추고, 재방문율도 42%로 높은 편입니다.',
|
||||
engagementRate: '52%',
|
||||
avgParticipants: '980명',
|
||||
roi: '2.8배'
|
||||
},
|
||||
{
|
||||
rank: 3,
|
||||
title: 'SNS 인증샷 이벤트',
|
||||
description: '해시태그를 활용한 바이럴 마케팅으로 브랜드 노출도가 평균 5배 증가합니다.',
|
||||
engagementRate: '45%',
|
||||
avgParticipants: '850명',
|
||||
roi: '3.1배'
|
||||
}
|
||||
],
|
||||
recommendation: '현재 트렌드를 분석한 결과, "친구 초대 이벤트"가 가장 효과적입니다. SNS 공유를 통한 바이럴 효과가 뛰어나며, 신규 고객 유치율이 평균 3.2배 높습니다.',
|
||||
tags: ['#신규고객유치', '#바이럴효과', '#SNS마케팅']
|
||||
},
|
||||
'repeat-visits': {
|
||||
subtitle: '재방문 유도를 위한 트렌드를 분석했어요',
|
||||
insights: {
|
||||
engagement: '58%',
|
||||
duration: '30일',
|
||||
roi: '4.1배'
|
||||
},
|
||||
trends: [
|
||||
{
|
||||
rank: 1,
|
||||
title: '스탬프 적립 이벤트',
|
||||
description: '방문할 때마다 스탬프를 적립하여 혜택을 제공하는 방식으로, 재방문율이 75%에 달합니다.',
|
||||
engagementRate: '75%',
|
||||
avgParticipants: '2,100명',
|
||||
roi: '4.5배'
|
||||
},
|
||||
{
|
||||
rank: 2,
|
||||
title: '회원 등급제',
|
||||
description: '방문 횟수에 따라 등급이 올라가며, 고객 충성도가 3.8배 증가합니다.',
|
||||
engagementRate: '62%',
|
||||
avgParticipants: '1,850명',
|
||||
roi: '4.2배'
|
||||
},
|
||||
{
|
||||
rank: 3,
|
||||
title: '재방문 쿠폰',
|
||||
description: '첫 방문 후 재방문 시 사용 가능한 쿠폰으로, 1주일 내 재방문율이 48%입니다.',
|
||||
engagementRate: '48%',
|
||||
avgParticipants: '1,650명',
|
||||
roi: '3.6배'
|
||||
}
|
||||
],
|
||||
recommendation: '스탬프 적립 이벤트가 재방문 유도에 가장 효과적입니다. 고객의 재방문율이 75%에 달하며, 장기적인 고객 관계 형성에 유리합니다.',
|
||||
tags: ['#재방문유도', '#고객충성도', '#리워드프로그램']
|
||||
},
|
||||
'sales-boost': {
|
||||
subtitle: '매출 증대를 위한 트렌드를 분석했어요',
|
||||
insights: {
|
||||
engagement: '51%',
|
||||
duration: '3일',
|
||||
roi: '5.3배'
|
||||
},
|
||||
trends: [
|
||||
{
|
||||
rank: 1,
|
||||
title: '세트 할인',
|
||||
description: '여러 상품을 묶어서 할인하는 방식으로, 평균 객단가가 38% 증가합니다.',
|
||||
engagementRate: '65%',
|
||||
avgParticipants: '1,450명',
|
||||
roi: '5.8배'
|
||||
},
|
||||
{
|
||||
rank: 2,
|
||||
title: '한정 수량 특가',
|
||||
description: '수량을 제한하여 긴급성을 강조하면, 구매 전환율이 52% 증가합니다.',
|
||||
engagementRate: '58%',
|
||||
avgParticipants: '1,320명',
|
||||
roi: '5.2배'
|
||||
},
|
||||
{
|
||||
rank: 3,
|
||||
title: '타임 세일',
|
||||
description: '시간대별 특가로 고객 집중도를 높이며, 매출이 평균 45% 증가합니다.',
|
||||
engagementRate: '51%',
|
||||
avgParticipants: '1,280명',
|
||||
roi: '4.9배'
|
||||
}
|
||||
],
|
||||
recommendation: '세트 할인 이벤트가 매출 증대에 가장 효과적입니다. 평균 객단가가 38% 증가하며, ROI가 5.8배로 가장 높습니다.',
|
||||
tags: ['#매출증대', '#객단가상승', '#구매전환']
|
||||
}
|
||||
};
|
||||
|
||||
const currentData = trendData[purpose] || trendData['new-customers'];
|
||||
|
||||
// Loading animation
|
||||
const loadingState = document.getElementById('loadingState');
|
||||
const analysisResults = document.getElementById('analysisResults');
|
||||
const ctaSection = document.getElementById('ctaSection');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const progressText = document.getElementById('progressText');
|
||||
|
||||
const progressSteps = [
|
||||
{ progress: 20, text: '데이터 수집 중...' },
|
||||
{ progress: 40, text: '업종별 트렌드 분석 중...' },
|
||||
{ progress: 60, text: '성공 사례 분석 중...' },
|
||||
{ progress: 80, text: '맞춤형 인사이트 생성 중...' },
|
||||
{ progress: 100, text: '분석 완료!' }
|
||||
];
|
||||
|
||||
let currentStep = 0;
|
||||
|
||||
function updateProgress() {
|
||||
if (currentStep >= progressSteps.length) {
|
||||
// Show results
|
||||
setTimeout(() => {
|
||||
loadingState.style.display = 'none';
|
||||
analysisResults.style.display = 'block';
|
||||
ctaSection.style.display = 'block';
|
||||
}, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
const step = progressSteps[currentStep];
|
||||
progressBar.style.width = step.progress + '%';
|
||||
progressText.textContent = step.text;
|
||||
|
||||
currentStep++;
|
||||
setTimeout(updateProgress, 800);
|
||||
}
|
||||
|
||||
// Start loading animation
|
||||
setTimeout(updateProgress, 500);
|
||||
|
||||
// Populate results
|
||||
document.getElementById('resultsSubtitle').textContent = currentData.subtitle;
|
||||
document.getElementById('insightEngagement').textContent = currentData.insights.engagement;
|
||||
document.getElementById('insightDuration').textContent = currentData.insights.duration;
|
||||
document.getElementById('insightROI').textContent = currentData.insights.roi;
|
||||
document.getElementById('aiRecommendation').textContent = currentData.recommendation;
|
||||
|
||||
// Populate trends
|
||||
const trendList = document.getElementById('trendList');
|
||||
currentData.trends.forEach(trend => {
|
||||
const trendItem = createElement('div', 'trend-item');
|
||||
trendItem.innerHTML = `
|
||||
<div class="trend-item__header">
|
||||
<div class="trend-item__rank">${trend.rank}</div>
|
||||
<div class="trend-item__content">
|
||||
<h4 class="trend-item__title">${trend.title}</h4>
|
||||
<p class="trend-item__description">${trend.description}</p>
|
||||
<div class="trend-item__stats">
|
||||
<div class="trend-stat">
|
||||
<span class="trend-stat__label">참여율:</span>
|
||||
<span class="trend-stat__value">${trend.engagementRate}</span>
|
||||
</div>
|
||||
<div class="trend-stat">
|
||||
<span class="trend-stat__label">평균 참여자:</span>
|
||||
<span class="trend-stat__value">${trend.avgParticipants}</span>
|
||||
</div>
|
||||
<div class="trend-stat">
|
||||
<span class="trend-stat__label">ROI:</span>
|
||||
<span class="trend-stat__value">${trend.roi}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
trendList.appendChild(trendItem);
|
||||
});
|
||||
|
||||
// Populate tags
|
||||
const tagsContainer = document.querySelector('.ai-recommendation__tags');
|
||||
tagsContainer.innerHTML = '';
|
||||
currentData.tags.forEach(tag => {
|
||||
const tagEl = createElement('span', 'ai-recommendation__tag');
|
||||
tagEl.textContent = tag;
|
||||
tagsContainer.appendChild(tagEl);
|
||||
});
|
||||
|
||||
// Handle next button
|
||||
document.getElementById('nextButton').addEventListener('click', () => {
|
||||
// Save trend analysis to event draft
|
||||
eventDraft.trendAnalysis = {
|
||||
purpose,
|
||||
insights: currentData.insights,
|
||||
topTrend: currentData.trends[0].title,
|
||||
recommendation: currentData.recommendation,
|
||||
analyzedAt: new Date().toISOString()
|
||||
};
|
||||
storage.set('eventDraft', eventDraft);
|
||||
|
||||
window.location.href = '06-AI이벤트상품추천.html';
|
||||
});
|
||||
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,602 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<!-- Pretendard Font -->
|
||||
<link rel="stylesheet" as="style" crossorigin
|
||||
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable.min.css">
|
||||
|
||||
<!-- Stylesheets -->
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/reset.css">
|
||||
<link rel="stylesheet" href="css/layout.css">
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<link rel="stylesheet" href="css/components/buttons.css">
|
||||
<link rel="stylesheet" href="css/components/cards.css">
|
||||
<link rel="stylesheet" href="css/components/navigation.css">
|
||||
|
||||
<style>
|
||||
/* AI Prize Recommendation Specific Styles */
|
||||
.prize-recommendation {
|
||||
padding: var(--spacing-l);
|
||||
padding-bottom: calc(var(--bottom-nav-height) + 80px);
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.intro-section {
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.intro-section__icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.intro-section__title {
|
||||
font-size: var(--font-size-h1);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-black);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.intro-section__subtitle {
|
||||
font-size: var(--font-size-body-medium);
|
||||
color: var(--color-gray-600);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-black);
|
||||
margin-bottom: var(--spacing-l);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.section-title__icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
font-size: var(--font-size-body-medium);
|
||||
color: var(--color-gray-600);
|
||||
margin-top: calc(-1 * var(--spacing-m));
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.prize-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.prize-card {
|
||||
position: relative;
|
||||
background-color: var(--color-white);
|
||||
border: 2px solid var(--color-gray-300);
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--spacing-l);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.prize-card:hover {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.prize-card--selected {
|
||||
border-color: var(--color-primary);
|
||||
background-color: #FFF5F5;
|
||||
box-shadow: var(--shadow-primary);
|
||||
}
|
||||
|
||||
.prize-card--recommended {
|
||||
border-color: var(--color-secondary);
|
||||
background: linear-gradient(135deg, #F0F7FF 0%, #FFFFFF 100%);
|
||||
}
|
||||
|
||||
.prize-card__badge {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
right: var(--spacing-l);
|
||||
padding: 6px 16px;
|
||||
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-dark) 100%);
|
||||
color: var(--color-white);
|
||||
font-size: var(--font-size-body-small);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
border-radius: var(--radius-s);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.prize-card__header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.prize-card__icon {
|
||||
font-size: 48px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.prize-card__content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.prize-card__title {
|
||||
font-size: var(--font-size-h2);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-black);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.prize-card__type {
|
||||
font-size: var(--font-size-body-small);
|
||||
color: var(--color-gray-600);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.prize-card__description {
|
||||
font-size: var(--font-size-body-medium);
|
||||
color: var(--color-gray-700);
|
||||
line-height: 1.6;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.prize-card__stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--spacing-m);
|
||||
padding: var(--spacing-m);
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: var(--radius-s);
|
||||
}
|
||||
|
||||
.prize-stat {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.prize-stat__value {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-primary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.prize-stat__label {
|
||||
font-size: var(--font-size-body-small);
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
.prize-card__pros {
|
||||
margin-top: var(--spacing-m);
|
||||
padding: var(--spacing-m);
|
||||
background-color: rgba(0, 200, 83, 0.05);
|
||||
border-left: 3px solid var(--color-success);
|
||||
border-radius: var(--radius-s);
|
||||
}
|
||||
|
||||
.prize-card__pros-title {
|
||||
font-size: var(--font-size-body-small);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-success);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.prize-card__pros-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.prize-card__pros-list li {
|
||||
font-size: var(--font-size-body-small);
|
||||
color: var(--color-gray-700);
|
||||
padding-left: var(--spacing-m);
|
||||
position: relative;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.prize-card__pros-list li::before {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--color-success);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.custom-prize-section {
|
||||
background-color: var(--color-gray-100);
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--spacing-l);
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.custom-prize-section__title {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-black);
|
||||
margin-bottom: var(--spacing-m);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.custom-prize-input {
|
||||
width: 100%;
|
||||
padding: var(--spacing-m);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
border-radius: var(--radius-s);
|
||||
font-size: var(--font-size-body-medium);
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.custom-prize-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-secondary);
|
||||
box-shadow: 0 0 0 4px rgba(0, 102, 255, 0.1);
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
position: fixed;
|
||||
bottom: var(--bottom-nav-height);
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--color-white);
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
border-top: 1px solid var(--color-gray-300);
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.cta-section__inner {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<!-- App Bar -->
|
||||
<header class="app-bar">
|
||||
<div class="app-bar__container">
|
||||
<div class="app-bar__left">
|
||||
<svg class="app-bar__back" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="M19 12H5M12 19l-7-7 7-7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="app-bar__title">AI 상품 추천</h1>
|
||||
<div class="app-bar__right"></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="page-main">
|
||||
<div class="prize-recommendation">
|
||||
<!-- Intro Section -->
|
||||
<div class="intro-section">
|
||||
<div class="intro-section__icon">🎁</div>
|
||||
<h2 class="intro-section__title">어떤 혜택을 제공하시겠어요?</h2>
|
||||
<p class="intro-section__subtitle">
|
||||
AI가 분석한 결과를 바탕으로<br>
|
||||
가장 효과적인 이벤트 상품을 추천해드립니다
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Prize Options -->
|
||||
<h3 class="section-title">
|
||||
<span class="section-title__icon">✨</span>
|
||||
<span>AI 추천 혜택</span>
|
||||
</h3>
|
||||
<p class="section-subtitle">트렌드 분석을 바탕으로 가장 효과적인 혜택을 선별했어요</p>
|
||||
|
||||
<div class="prize-options" id="prizeOptions">
|
||||
<!-- AI Recommended -->
|
||||
<label class="prize-card prize-card--recommended" data-prize="friend-invite">
|
||||
<span class="prize-card__badge">🤖 AI 추천</span>
|
||||
<div class="prize-card__header">
|
||||
<span class="prize-card__icon">👥</span>
|
||||
<div class="prize-card__content">
|
||||
<h4 class="prize-card__title">친구 초대 특전</h4>
|
||||
<p class="prize-card__type">추천형 · 바이럴 효과</p>
|
||||
</div>
|
||||
<input type="radio" name="prize" value="friend-invite" class="prize-card__radio" style="display: none;">
|
||||
</div>
|
||||
<p class="prize-card__description">
|
||||
친구를 초대한 고객과 초대받은 친구 모두에게 혜택을 제공하는 방식입니다. SNS 공유를 통한 바이럴 효과가 뛰어나며, 신규 고객 유치에 가장 효과적입니다.
|
||||
</p>
|
||||
<div class="prize-card__stats">
|
||||
<div class="prize-stat">
|
||||
<div class="prize-stat__value">68%</div>
|
||||
<div class="prize-stat__label">참여율</div>
|
||||
</div>
|
||||
<div class="prize-stat">
|
||||
<div class="prize-stat__value">3.5배</div>
|
||||
<div class="prize-stat__label">ROI</div>
|
||||
</div>
|
||||
<div class="prize-stat">
|
||||
<div class="prize-stat__value">1,200명</div>
|
||||
<div class="prize-stat__label">평균 참여자</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prize-card__pros">
|
||||
<div class="prize-card__pros-title">✓ 주요 장점</div>
|
||||
<ul class="prize-card__pros-list">
|
||||
<li>바이럴 효과로 빠른 확산 가능</li>
|
||||
<li>신규 고객 유치율 평균 3.2배</li>
|
||||
<li>고객 획득 비용 40% 절감</li>
|
||||
</ul>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<!-- Option 2 -->
|
||||
<label class="prize-card" data-prize="discount">
|
||||
<div class="prize-card__header">
|
||||
<span class="prize-card__icon">💰</span>
|
||||
<div class="prize-card__content">
|
||||
<h4 class="prize-card__title">할인 쿠폰</h4>
|
||||
<p class="prize-card__type">즉시 혜택형 · 전환율 높음</p>
|
||||
</div>
|
||||
<input type="radio" name="prize" value="discount" class="prize-card__radio" style="display: none;">
|
||||
</div>
|
||||
<p class="prize-card__description">
|
||||
즉시 사용 가능한 할인 쿠폰을 제공합니다. 구매 전환율이 높으며, 객단가 상승 효과도 기대할 수 있습니다.
|
||||
</p>
|
||||
<div class="prize-card__stats">
|
||||
<div class="prize-stat">
|
||||
<div class="prize-stat__value">52%</div>
|
||||
<div class="prize-stat__label">참여율</div>
|
||||
</div>
|
||||
<div class="prize-stat">
|
||||
<div class="prize-stat__value">2.8배</div>
|
||||
<div class="prize-stat__label">ROI</div>
|
||||
</div>
|
||||
<div class="prize-stat">
|
||||
<div class="prize-stat__value">980명</div>
|
||||
<div class="prize-stat__label">평균 참여자</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prize-card__pros">
|
||||
<div class="prize-card__pros-title">✓ 주요 장점</div>
|
||||
<ul class="prize-card__pros-list">
|
||||
<li>즉시 구매 전환 유도</li>
|
||||
<li>객단가 평균 28% 상승</li>
|
||||
<li>설정 및 관리 간편</li>
|
||||
</ul>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<!-- Option 3 -->
|
||||
<label class="prize-card" data-prize="freebie">
|
||||
<div class="prize-card__header">
|
||||
<span class="prize-card__icon">🎉</span>
|
||||
<div class="prize-card__content">
|
||||
<h4 class="prize-card__title">무료 증정</h4>
|
||||
<p class="prize-card__type">체험형 · 고객 만족도 높음</p>
|
||||
</div>
|
||||
<input type="radio" name="prize" value="freebie" class="prize-card__radio" style="display: none;">
|
||||
</div>
|
||||
<p class="prize-card__description">
|
||||
인기 상품이나 신메뉴를 무료로 체험할 수 있는 기회를 제공합니다. 고객 만족도가 높고 재방문율 증가에 효과적입니다.
|
||||
</p>
|
||||
<div class="prize-card__stats">
|
||||
<div class="prize-stat">
|
||||
<div class="prize-stat__value">48%</div>
|
||||
<div class="prize-stat__label">참여율</div>
|
||||
</div>
|
||||
<div class="prize-stat">
|
||||
<div class="prize-stat__value">3.1배</div>
|
||||
<div class="prize-stat__label">ROI</div>
|
||||
</div>
|
||||
<div class="prize-stat">
|
||||
<div class="prize-stat__value">850명</div>
|
||||
<div class="prize-stat__label">평균 참여자</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prize-card__pros">
|
||||
<div class="prize-card__pros-title">✓ 주요 장점</div>
|
||||
<ul class="prize-card__pros-list">
|
||||
<li>고객 만족도 매우 높음</li>
|
||||
<li>재방문율 평균 42% 증가</li>
|
||||
<li>긍정적 브랜드 이미지 형성</li>
|
||||
</ul>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Custom Prize Section -->
|
||||
<div class="custom-prize-section">
|
||||
<h3 class="custom-prize-section__title">
|
||||
<span>✏️</span>
|
||||
<span>직접 입력하기</span>
|
||||
</h3>
|
||||
<input
|
||||
type="text"
|
||||
id="customPrize"
|
||||
class="custom-prize-input"
|
||||
placeholder="예: 아메리카노 1잔 무료, 10% 할인권, 케이크 증정"
|
||||
maxlength="100"
|
||||
>
|
||||
<span class="form-helper mt-s">원하는 혜택을 직접 입력할 수도 있어요</span>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- CTA Section -->
|
||||
<div class="cta-section">
|
||||
<div class="cta-section__inner">
|
||||
<button
|
||||
type="button"
|
||||
id="nextButton"
|
||||
class="btn btn-primary btn-large btn-block"
|
||||
disabled
|
||||
>
|
||||
참여 방법 설계하기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav">
|
||||
<a href="03-홈화면.html" class="bottom-nav__item">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">홈</span>
|
||||
</a>
|
||||
<a href="25-이벤트목록.html" class="bottom-nav__item">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm2 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">이벤트</span>
|
||||
</a>
|
||||
<a href="04-이벤트목적선택.html" class="bottom-nav__item bottom-nav__item--active bottom-nav__item--primary">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M12 6v12M6 12h12" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">만들기</span>
|
||||
</a>
|
||||
<a href="21-실시간대시보드.html" class="bottom-nav__item">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">분석</span>
|
||||
</a>
|
||||
<a href="26-마이페이지.html" class="bottom-nav__item">
|
||||
<svg class="bottom-nav__icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
|
||||
</svg>
|
||||
<span class="bottom-nav__label">MY</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="js/common.js"></script>
|
||||
<script src="js/navigation.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const { storage, addClass, removeClass } = window.KTEvent;
|
||||
|
||||
const prizeCards = document.querySelectorAll('.prize-card');
|
||||
const prizeOptions = document.querySelectorAll('input[name="prize"]');
|
||||
const customPrizeInput = document.getElementById('customPrize');
|
||||
const nextButton = document.getElementById('nextButton');
|
||||
|
||||
let selectedPrize = null;
|
||||
let customPrize = null;
|
||||
|
||||
// Handle prize card selection
|
||||
prizeCards.forEach(card => {
|
||||
card.addEventListener('click', () => {
|
||||
// Clear custom input
|
||||
customPrizeInput.value = '';
|
||||
customPrize = null;
|
||||
|
||||
// Remove selected class from all cards
|
||||
prizeCards.forEach(c => removeClass(c, 'prize-card--selected'));
|
||||
|
||||
// Add selected class to clicked card
|
||||
addClass(card, 'prize-card--selected');
|
||||
|
||||
// Get the selected prize
|
||||
const prize = card.getAttribute('data-prize');
|
||||
selectedPrize = prize;
|
||||
|
||||
// Update radio button
|
||||
const radio = card.querySelector('input[type="radio"]');
|
||||
radio.checked = true;
|
||||
|
||||
// Enable next button
|
||||
nextButton.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
// Handle radio button change (for keyboard navigation)
|
||||
prizeOptions.forEach(option => {
|
||||
option.addEventListener('change', () => {
|
||||
const prize = option.value;
|
||||
const card = option.closest('.prize-card');
|
||||
|
||||
customPrizeInput.value = '';
|
||||
customPrize = null;
|
||||
|
||||
prizeCards.forEach(c => removeClass(c, 'prize-card--selected'));
|
||||
addClass(card, 'prize-card--selected');
|
||||
|
||||
selectedPrize = prize;
|
||||
nextButton.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
// Handle custom prize input
|
||||
customPrizeInput.addEventListener('input', (e) => {
|
||||
const value = e.target.value.trim();
|
||||
|
||||
if (value) {
|
||||
// Clear card selections
|
||||
prizeCards.forEach(c => removeClass(c, 'prize-card--selected'));
|
||||
prizeOptions.forEach(o => o.checked = false);
|
||||
|
||||
selectedPrize = null;
|
||||
customPrize = value;
|
||||
|
||||
// Enable next button
|
||||
nextButton.disabled = false;
|
||||
} else {
|
||||
customPrize = null;
|
||||
if (!selectedPrize) {
|
||||
nextButton.disabled = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle next button click
|
||||
nextButton.addEventListener('click', () => {
|
||||
if (!selectedPrize && !customPrize) return;
|
||||
|
||||
// Show loading state
|
||||
nextButton.disabled = true;
|
||||
nextButton.classList.add('btn-loading');
|
||||
|
||||
// Save selected prize
|
||||
const eventDraft = storage.get('eventDraft', {});
|
||||
eventDraft.prize = {
|
||||
type: selectedPrize,
|
||||
custom: customPrize,
|
||||
selectedAt: new Date().toISOString()
|
||||
};
|
||||
storage.set('eventDraft', eventDraft);
|
||||
|
||||
// Navigate to next screen
|
||||
setTimeout(() => {
|
||||
window.location.href = '07-AI참여방법설계.html';
|
||||
}, 800);
|
||||
});
|
||||
|
||||
// Auto-select AI recommended option
|
||||
setTimeout(() => {
|
||||
const recommendedCard = document.querySelector('[data-prize="friend-invite"]');
|
||||
if (recommendedCard) {
|
||||
recommendedCard.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
}, 500);
|
||||
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,859 +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 AI 이벤트 플랫폼</title>
|
||||
<style>
|
||||
/* CSS Reset */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Design System Variables */
|
||||
:root {
|
||||
/* Colors */
|
||||
--color-primary: #E31E24;
|
||||
--color-primary-hover: #C71820;
|
||||
--color-primary-light: #FF4D52;
|
||||
--color-secondary: #FF6B6B;
|
||||
--color-secondary-hover: #FF5252;
|
||||
--color-secondary-light: #FF8787;
|
||||
|
||||
--color-success: #00C853;
|
||||
--color-warning: #FFA000;
|
||||
--color-error: #D32F2F;
|
||||
--color-info: #FF9800;
|
||||
|
||||
--color-gray-50: #F8F9FA;
|
||||
--color-gray-100: #F1F3F5;
|
||||
--color-gray-200: #E9ECEF;
|
||||
--color-gray-300: #DEE2E6;
|
||||
--color-gray-400: #CED4DA;
|
||||
--color-gray-500: #ADB5BD;
|
||||
--color-gray-600: #868E96;
|
||||
--color-gray-700: #495057;
|
||||
--color-gray-800: #343A40;
|
||||
--color-gray-900: #212529;
|
||||
|
||||
--color-white: #FFFFFF;
|
||||
--color-black: #000000;
|
||||
|
||||
/* Typography */
|
||||
--font-family: 'Pretendard Variable', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
||||
|
||||
/* Spacing (4px grid) */
|
||||
--spacing-xs: 4px;
|
||||
--spacing-s: 8px;
|
||||
--spacing-m: 16px;
|
||||
--spacing-l: 24px;
|
||||
--spacing-xl: 32px;
|
||||
--spacing-2xl: 48px;
|
||||
|
||||
/* Border Radius */
|
||||
--radius-s: 4px;
|
||||
--radius-m: 8px;
|
||||
--radius-l: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-s: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-m: 0 4px 6px rgba(0, 0, 0, 0.07);
|
||||
--shadow-l: 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15);
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 150ms ease-in-out;
|
||||
--transition-base: 250ms ease-in-out;
|
||||
--transition-slow: 350ms ease-in-out;
|
||||
|
||||
/* Z-index */
|
||||
--z-header: 100;
|
||||
--z-modal: 200;
|
||||
--z-toast: 300;
|
||||
}
|
||||
|
||||
/* Base Styles */
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: var(--color-gray-900);
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-hover) 100%);
|
||||
color: var(--color-white);
|
||||
padding: var(--spacing-xl) var(--spacing-m);
|
||||
box-shadow: var(--shadow-l);
|
||||
}
|
||||
|
||||
.header__back {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-white);
|
||||
font-size: 24px;
|
||||
padding: 0;
|
||||
margin-bottom: var(--spacing-m);
|
||||
cursor: pointer;
|
||||
transition: opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.header__back:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.header__title {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.header__subtitle {
|
||||
font-size: 15px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Main */
|
||||
.main {
|
||||
padding: var(--spacing-l) var(--spacing-m);
|
||||
padding-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
/* AI Tip */
|
||||
.ai-tip {
|
||||
background: linear-gradient(135deg, #FFF9E6 0%, #FFFFFF 100%);
|
||||
border-left: 4px solid #FFB800;
|
||||
padding: var(--spacing-m);
|
||||
border-radius: var(--radius-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.ai-tip__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.ai-tip__icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.ai-tip__title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
.ai-tip__content {
|
||||
font-size: 14px;
|
||||
color: var(--color-gray-700);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Methods Grid */
|
||||
.methods-grid {
|
||||
display: grid;
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
/* Method Card */
|
||||
.method-card {
|
||||
background: var(--color-white);
|
||||
border: 2px solid var(--color-gray-200);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-base);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.method-card:hover {
|
||||
border-color: var(--color-secondary);
|
||||
box-shadow: var(--shadow-m);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.method-card--selected {
|
||||
border-color: var(--color-secondary);
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
box-shadow: var(--shadow-l);
|
||||
}
|
||||
|
||||
.method-card--recommended {
|
||||
border-color: var(--color-secondary);
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
}
|
||||
|
||||
.method-card__badge {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
right: var(--spacing-l);
|
||||
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-hover) 100%);
|
||||
color: var(--color-white);
|
||||
padding: var(--spacing-xs) var(--spacing-m);
|
||||
border-radius: var(--radius-s);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
box-shadow: var(--shadow-m);
|
||||
}
|
||||
|
||||
.method-card__header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.method-card__radio {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: 2px;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.method-card__icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: linear-gradient(135deg, var(--color-secondary-light) 0%, var(--color-secondary) 100%);
|
||||
border-radius: var(--radius-m);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.method-card__info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.method-card__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.method-card__type {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
color: var(--color-primary);
|
||||
background: var(--color-primary-50);
|
||||
padding: 2px var(--spacing-s);
|
||||
border-radius: var(--radius-s);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.method-card__description {
|
||||
font-size: 14px;
|
||||
color: var(--color-gray-600);
|
||||
line-height: 1.5;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
/* Stats */
|
||||
.method-card__stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-m);
|
||||
padding-top: var(--spacing-m);
|
||||
border-top: 1px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
.method-stat {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.method-stat__value {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--color-primary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.method-stat__label {
|
||||
font-size: 11px;
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
/* Features */
|
||||
.method-card__features {
|
||||
background: linear-gradient(135deg, #F0FFF4 0%, #FFFFFF 100%);
|
||||
border-left: 3px solid var(--color-success);
|
||||
padding: var(--spacing-s);
|
||||
border-radius: var(--radius-s);
|
||||
}
|
||||
|
||||
.method-card__features-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.method-card__features-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.method-card__features-list li {
|
||||
font-size: 12px;
|
||||
color: var(--color-gray-700);
|
||||
padding-left: var(--spacing-m);
|
||||
position: relative;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.method-card__features-list li:before {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--color-success);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.method-card__features-list li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Custom Method */
|
||||
.custom-method {
|
||||
background: var(--color-white);
|
||||
border: 2px dashed var(--color-gray-200);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.custom-method__label {
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.custom-method__input {
|
||||
width: 100%;
|
||||
padding: var(--spacing-m);
|
||||
border: 1px solid var(--color-gray-200);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: 15px;
|
||||
font-family: var(--font-family);
|
||||
transition: all var(--transition-base);
|
||||
}
|
||||
|
||||
.custom-method__input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-secondary);
|
||||
box-shadow: 0 0 0 3px rgba(255, 107, 107, 0.15);
|
||||
}
|
||||
|
||||
.custom-method__hint {
|
||||
font-size: 12px;
|
||||
color: var(--color-gray-500);
|
||||
margin-top: var(--spacing-xs);
|
||||
}
|
||||
|
||||
/* Next Button */
|
||||
.next-button {
|
||||
width: 100%;
|
||||
padding: var(--spacing-m);
|
||||
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-hover) 100%);
|
||||
color: var(--color-white);
|
||||
border: none;
|
||||
border-radius: var(--radius-m);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-base);
|
||||
box-shadow: var(--shadow-m);
|
||||
}
|
||||
|
||||
.next-button:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-l);
|
||||
}
|
||||
|
||||
.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); }
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (min-width: 768px) {
|
||||
.header {
|
||||
padding: var(--spacing-2xl) var(--spacing-xl);
|
||||
}
|
||||
|
||||
.main {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.methods-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.main {
|
||||
max-width: 960px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<button class="header__back" onclick="history.back()">←</button>
|
||||
<h1 class="header__title">AI 참여 방법 설계</h1>
|
||||
<p class="header__subtitle">고객이 이벤트에 참여하는 방법을 선택해 주세요</p>
|
||||
</header>
|
||||
|
||||
<!-- Main -->
|
||||
<main class="main">
|
||||
<!-- AI Tip -->
|
||||
<div class="ai-tip">
|
||||
<div class="ai-tip__header">
|
||||
<span class="ai-tip__icon">💡</span>
|
||||
<h2 class="ai-tip__title">AI 추천</h2>
|
||||
</div>
|
||||
<p class="ai-tip__content">
|
||||
<strong id="aiTipText">SNS 공유하기</strong>는 바이럴 효과가 뛰어나 신규 고객 유치에 가장 효과적입니다.
|
||||
평균 2.3배의 참여자 증가 효과가 있으며, 설정도 간단합니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Methods Grid -->
|
||||
<div class="methods-grid">
|
||||
<!-- SNS Share - AI Recommended -->
|
||||
<div class="method-card method-card--recommended" data-method="sns-share">
|
||||
<div class="method-card__badge">🤖 AI 추천</div>
|
||||
<div class="method-card__header">
|
||||
<input type="radio" name="method" value="sns-share" class="method-card__radio">
|
||||
<div class="method-card__icon">📱</div>
|
||||
<div class="method-card__info">
|
||||
<h3 class="method-card__title">SNS 공유하기</h3>
|
||||
<span class="method-card__type">바이럴형 · 확산 효과 우수</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="method-card__description">
|
||||
고객이 이벤트를 SNS에 공유하면 참여 완료. 인스타그램, 페이스북 등 주요 SNS 플랫폼 지원
|
||||
</p>
|
||||
<div class="method-card__stats">
|
||||
<div class="method-stat">
|
||||
<div class="method-stat__value">75%</div>
|
||||
<div class="method-stat__label">참여율</div>
|
||||
</div>
|
||||
<div class="method-stat">
|
||||
<div class="method-stat__value">2.3배</div>
|
||||
<div class="method-stat__label">바이럴 계수</div>
|
||||
</div>
|
||||
<div class="method-stat">
|
||||
<div class="method-stat__value">쉬움</div>
|
||||
<div class="method-stat__label">난이도</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="method-card__features">
|
||||
<h4 class="method-card__features-title">주요 장점</h4>
|
||||
<ul class="method-card__features-list">
|
||||
<li>자연스러운 바이럴 효과로 신규 고객 유입</li>
|
||||
<li>추가 비용 없이 홍보 효과 극대화</li>
|
||||
<li>간편한 참여로 높은 전환율</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- QR Code Scan -->
|
||||
<div class="method-card" data-method="qr-scan">
|
||||
<div class="method-card__header">
|
||||
<input type="radio" name="method" value="qr-scan" class="method-card__radio">
|
||||
<div class="method-card__icon">📷</div>
|
||||
<div class="method-card__info">
|
||||
<h3 class="method-card__title">QR 코드 스캔</h3>
|
||||
<span class="method-card__type">오프라인형 · 매장 방문 유도</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="method-card__description">
|
||||
매장 내 QR 코드를 스캔하여 참여. 포스터, 테이블 스티커 등 다양한 형태로 활용 가능
|
||||
</p>
|
||||
<div class="method-card__stats">
|
||||
<div class="method-stat">
|
||||
<div class="method-stat__value">68%</div>
|
||||
<div class="method-stat__label">참여율</div>
|
||||
</div>
|
||||
<div class="method-stat">
|
||||
<div class="method-stat__value">1.2배</div>
|
||||
<div class="method-stat__label">바이럴 계수</div>
|
||||
</div>
|
||||
<div class="method-stat">
|
||||
<div class="method-stat__value">쉬움</div>
|
||||
<div class="method-stat__label">난이도</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="method-card__features">
|
||||
<h4 class="method-card__features-title">주요 장점</h4>
|
||||
<ul class="method-card__features-list">
|
||||
<li>오프라인 매장 방문 고객 타겟팅</li>
|
||||
<li>즉각적인 참여와 혜택 제공</li>
|
||||
<li>누구나 쉽게 참여 가능</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Coupon Download -->
|
||||
<div class="method-card" data-method="coupon-download">
|
||||
<div class="method-card__header">
|
||||
<input type="radio" name="method" value="coupon-download" class="method-card__radio">
|
||||
<div class="method-card__icon">🎫</div>
|
||||
<div class="method-card__info">
|
||||
<h3 class="method-card__title">쿠폰 다운로드</h3>
|
||||
<span class="method-card__type">즉시 혜택형 · 전환율 높음</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="method-card__description">
|
||||
간단한 정보 입력 후 쿠폰 다운로드. 모바일 쿠폰으로 즉시 사용 가능
|
||||
</p>
|
||||
<div class="method-card__stats">
|
||||
<div class="method-stat">
|
||||
<div class="method-stat__value">82%</div>
|
||||
<div class="method-stat__label">참여율</div>
|
||||
</div>
|
||||
<div class="method-stat">
|
||||
<div class="method-stat__value">1.1배</div>
|
||||
<div class="method-stat__label">바이럴 계수</div>
|
||||
</div>
|
||||
<div class="method-stat">
|
||||
<div class="method-stat__value">쉬움</div>
|
||||
<div class="method-stat__label">난이도</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="method-card__features">
|
||||
<h4 class="method-card__features-title">주요 장점</h4>
|
||||
<ul class="method-card__features-list">
|
||||
<li>즉시 혜택으로 높은 전환율</li>
|
||||
<li>고객 정보 수집 용이</li>
|
||||
<li>재방문 유도 효과</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- In-store Visit -->
|
||||
<div class="method-card" data-method="store-visit">
|
||||
<div class="method-card__header">
|
||||
<input type="radio" name="method" value="store-visit" class="method-card__radio">
|
||||
<div class="method-card__icon">🏪</div>
|
||||
<div class="method-card__info">
|
||||
<h3 class="method-card__title">매장 방문 인증</h3>
|
||||
<span class="method-card__type">오프라인형 · 방문 유도</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="method-card__description">
|
||||
매장 방문 시 스탬프나 인증 사진 업로드로 참여. 실제 방문 고객 확보에 효과적
|
||||
</p>
|
||||
<div class="method-card__stats">
|
||||
<div class="method-stat">
|
||||
<div class="method-stat__value">58%</div>
|
||||
<div class="method-stat__label">참여율</div>
|
||||
</div>
|
||||
<div class="method-stat">
|
||||
<div class="method-stat__value">1.0배</div>
|
||||
<div class="method-stat__label">바이럴 계수</div>
|
||||
</div>
|
||||
<div class="method-stat">
|
||||
<div class="method-stat__value">보통</div>
|
||||
<div class="method-stat__label">난이도</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="method-card__features">
|
||||
<h4 class="method-card__features-title">주요 장점</h4>
|
||||
<ul class="method-card__features-list">
|
||||
<li>확실한 매장 방문 고객 확보</li>
|
||||
<li>직접적인 매출 증대 효과</li>
|
||||
<li>단골 고객 형성에 유리</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Survey Participation -->
|
||||
<div class="method-card" data-method="survey">
|
||||
<div class="method-card__header">
|
||||
<input type="radio" name="method" value="survey" class="method-card__radio">
|
||||
<div class="method-card__icon">📝</div>
|
||||
<div class="method-card__info">
|
||||
<h3 class="method-card__title">설문 참여</h3>
|
||||
<span class="method-card__type">데이터 수집형 · 인사이트 확보</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="method-card__description">
|
||||
간단한 설문 작성 후 참여 완료. 고객 니즈 파악과 맞춤형 마케팅에 활용
|
||||
</p>
|
||||
<div class="method-card__stats">
|
||||
<div class="method-stat">
|
||||
<div class="method-stat__value">45%</div>
|
||||
<div class="method-stat__label">참여율</div>
|
||||
</div>
|
||||
<div class="method-stat">
|
||||
<div class="method-stat__value">0.8배</div>
|
||||
<div class="method-stat__label">바이럴 계수</div>
|
||||
</div>
|
||||
<div class="method-stat">
|
||||
<div class="method-stat__value">보통</div>
|
||||
<div class="method-stat__label">난이도</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="method-card__features">
|
||||
<h4 class="method-card__features-title">주요 장점</h4>
|
||||
<ul class="method-card__features-list">
|
||||
<li>고객 선호도 및 니즈 파악</li>
|
||||
<li>데이터 기반 의사결정 가능</li>
|
||||
<li>타겟 마케팅 최적화</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Friend Invite -->
|
||||
<div class="method-card" data-method="friend-invite">
|
||||
<div class="method-card__header">
|
||||
<input type="radio" name="method" value="friend-invite" class="method-card__radio">
|
||||
<div class="method-card__icon">👥</div>
|
||||
<div class="method-card__info">
|
||||
<h3 class="method-card__title">친구 초대</h3>
|
||||
<span class="method-card__type">추천형 · 신규 고객 확보</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="method-card__description">
|
||||
친구를 초대하고 함께 혜택 받기. 추천인과 피추천인 모두 혜택 제공
|
||||
</p>
|
||||
<div class="method-card__stats">
|
||||
<div class="method-stat">
|
||||
<div class="method-stat__value">62%</div>
|
||||
<div class="method-stat__label">참여율</div>
|
||||
</div>
|
||||
<div class="method-stat">
|
||||
<div class="method-stat__value">3.5배</div>
|
||||
<div class="method-stat__label">바이럴 계수</div>
|
||||
</div>
|
||||
<div class="method-stat">
|
||||
<div class="method-stat__value">보통</div>
|
||||
<div class="method-stat__label">난이도</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="method-card__features">
|
||||
<h4 class="method-card__features-title">주요 장점</h4>
|
||||
<ul class="method-card__features-list">
|
||||
<li>검증된 신규 고객 확보</li>
|
||||
<li>강력한 추천 효과</li>
|
||||
<li>Win-Win 혜택 구조</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom Method Input -->
|
||||
<div class="custom-method">
|
||||
<label for="customMethod" class="custom-method__label">
|
||||
💭 직접 입력하기
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="customMethod"
|
||||
class="custom-method__input"
|
||||
placeholder="예: 리뷰 작성하기, 인스타그램 팔로우, 해시태그 이벤트"
|
||||
maxlength="100"
|
||||
>
|
||||
<p class="custom-method__hint">
|
||||
원하는 참여 방법을 직접 입력할 수 있어요 (최대 100자)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Next Button -->
|
||||
<button class="next-button" id="nextButton" disabled>
|
||||
홍보 문구 생성하기
|
||||
</button>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// Utils
|
||||
const $ = (selector) => document.querySelector(selector);
|
||||
const $$ = (selector) => document.querySelectorAll(selector);
|
||||
|
||||
const addClass = (el, className) => el?.classList.add(className);
|
||||
const removeClass = (el, className) => el?.classList.remove(className);
|
||||
|
||||
// LocalStorage wrapper
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Elements
|
||||
const methodCards = $$('.method-card');
|
||||
const methodRadios = $$('input[name="method"]');
|
||||
const customMethodInput = $('#customMethod');
|
||||
const nextButton = $('#nextButton');
|
||||
const aiTipText = $('#aiTipText');
|
||||
|
||||
// State
|
||||
let selectedMethod = null;
|
||||
let customMethod = null;
|
||||
|
||||
// AI Tips for different methods
|
||||
const aiTips = {
|
||||
'sns-share': 'SNS 공유하기는 바이럴 효과가 뛰어나 신규 고객 유치에 가장 효과적입니다. 평균 2.3배의 참여자 증가 효과가 있으며, 설정도 간단합니다.',
|
||||
'qr-scan': 'QR 코드 스캔은 오프라인 매장 방문 고객을 타겟팅하기에 적합합니다. 즉각적인 참여와 혜택 제공이 가능합니다.',
|
||||
'coupon-download': '쿠폰 다운로드는 즉시 혜택 제공으로 가장 높은 전환율을 보입니다. 고객 정보 수집과 재방문 유도에도 효과적입니다.',
|
||||
'store-visit': '매장 방문 인증은 실제 방문 고객을 확보하여 직접적인 매출 증대에 효과적입니다. 단골 고객 형성에도 유리합니다.',
|
||||
'survey': '설문 참여는 고객 니즈를 파악하고 데이터 기반 의사결정을 할 수 있는 기회를 제공합니다.',
|
||||
'friend-invite': '친구 초대는 강력한 바이럴 효과와 함께 검증된 신규 고객을 확보할 수 있습니다.'
|
||||
};
|
||||
|
||||
const methodNames = {
|
||||
'sns-share': 'SNS 공유하기',
|
||||
'qr-scan': 'QR 코드 스캔',
|
||||
'coupon-download': '쿠폰 다운로드',
|
||||
'store-visit': '매장 방문 인증',
|
||||
'survey': '설문 참여',
|
||||
'friend-invite': '친구 초대'
|
||||
};
|
||||
|
||||
// Handle card click
|
||||
methodCards.forEach(card => {
|
||||
card.addEventListener('click', () => {
|
||||
// Clear custom input
|
||||
customMethodInput.value = '';
|
||||
customMethod = null;
|
||||
|
||||
// Remove selected class from all cards
|
||||
methodCards.forEach(c => removeClass(c, 'method-card--selected'));
|
||||
|
||||
// Add selected class to clicked card
|
||||
addClass(card, 'method-card--selected');
|
||||
|
||||
const method = card.getAttribute('data-method');
|
||||
selectedMethod = method;
|
||||
|
||||
// Update radio button
|
||||
const radio = card.querySelector('input[type="radio"]');
|
||||
radio.checked = true;
|
||||
|
||||
// Update AI tip
|
||||
if (aiTips[method]) {
|
||||
aiTipText.textContent = methodNames[method] || '';
|
||||
}
|
||||
|
||||
nextButton.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
// Handle custom method input
|
||||
customMethodInput.addEventListener('input', (e) => {
|
||||
const value = e.target.value.trim();
|
||||
|
||||
if (value) {
|
||||
// Clear card selection
|
||||
methodCards.forEach(c => removeClass(c, 'method-card--selected'));
|
||||
methodRadios.forEach(r => r.checked = false);
|
||||
|
||||
selectedMethod = null;
|
||||
customMethod = value;
|
||||
|
||||
nextButton.disabled = false;
|
||||
} else {
|
||||
customMethod = null;
|
||||
if (!selectedMethod) {
|
||||
nextButton.disabled = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle next button
|
||||
nextButton.addEventListener('click', () => {
|
||||
// Get event draft
|
||||
const eventDraft = storage.get('eventDraft') || {};
|
||||
|
||||
// Save participation method
|
||||
eventDraft.participationMethod = {
|
||||
type: selectedMethod,
|
||||
custom: customMethod,
|
||||
selectedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
storage.set('eventDraft', eventDraft);
|
||||
|
||||
// Show loading state
|
||||
addClass(nextButton, 'next-button--loading');
|
||||
nextButton.disabled = true;
|
||||
|
||||
// Navigate to next screen
|
||||
setTimeout(() => {
|
||||
window.location.href = '08-AI홍보문구생성.html';
|
||||
}, 800);
|
||||
});
|
||||
|
||||
// Auto-scroll to recommended card on load
|
||||
window.addEventListener('load', () => {
|
||||
const recommendedCard = document.querySelector('.method-card--recommended');
|
||||
if (recommendedCard) {
|
||||
setTimeout(() => {
|
||||
recommendedCard.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,931 +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>
|
||||
<style>
|
||||
/* CSS Variables */
|
||||
:root {
|
||||
/* Colors */
|
||||
--color-primary: #E31E24;
|
||||
--color-primary-dark: #C41E24;
|
||||
--color-primary-light: #FFE9EA;
|
||||
--color-secondary: #FF6B6B;
|
||||
--color-secondary-dark: #FF5252;
|
||||
--color-secondary-light: #FFE9E9;
|
||||
--color-success: #00C853;
|
||||
--color-success-light: #E8F5E9;
|
||||
--color-error: #FF3B30;
|
||||
--color-error-light: #FFE9E9;
|
||||
--color-warning: #FFB800;
|
||||
--color-white: #FFFFFF;
|
||||
--color-black: #000000;
|
||||
|
||||
/* Text Colors */
|
||||
--color-text-primary: #1A1A1A;
|
||||
--color-text-secondary: #666666;
|
||||
--color-text-tertiary: #999999;
|
||||
|
||||
/* Border Colors */
|
||||
--color-border-light: #E0E0E0;
|
||||
--color-border: #CCCCCC;
|
||||
--color-border-dark: #999999;
|
||||
|
||||
/* Background Colors */
|
||||
--color-surface: #F5F5F5;
|
||||
--color-surface-light: #FAFAFA;
|
||||
|
||||
/* Spacing */
|
||||
--spacing-xs: 4px;
|
||||
--spacing-s: 8px;
|
||||
--spacing-m: 16px;
|
||||
--spacing-l: 24px;
|
||||
--spacing-xl: 32px;
|
||||
--spacing-2xl: 48px;
|
||||
|
||||
/* Font Sizes */
|
||||
--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 Weights */
|
||||
--font-weight-regular: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
|
||||
/* Border Radius */
|
||||
--radius-s: 4px;
|
||||
--radius-m: 8px;
|
||||
--radius-l: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.15);
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 150ms ease-in-out;
|
||||
--transition-normal: 300ms ease-in-out;
|
||||
--transition-slow: 500ms ease-in-out;
|
||||
|
||||
/* Font Family */
|
||||
--font-family: -apple-system, BlinkMacSystemFont, 'Pretendard Variable', 'Pretendard', 'Segoe UI', 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
/* Reset */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
font-size: var(--font-size-m);
|
||||
color: var(--color-text-primary);
|
||||
background: #FFFFFF;
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
.copy-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
padding-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.copy-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);
|
||||
}
|
||||
|
||||
.copy-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;
|
||||
}
|
||||
|
||||
.copy-header__title {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.copy-header__subtitle {
|
||||
font-size: var(--font-size-m);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Loading State */
|
||||
.loading-state {
|
||||
padding: var(--spacing-2xl) var(--spacing-m);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.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(255, 107, 107, 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__title {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.loading-state__text {
|
||||
font-size: var(--font-size-m);
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.loading-state__progress {
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
background: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: var(--color-border-light);
|
||||
border-radius: var(--radius-s);
|
||||
overflow: hidden;
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.progress-bar__fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--color-secondary) 0%, var(--color-secondary-light) 100%);
|
||||
border-radius: var(--radius-s);
|
||||
transition: width var(--transition-slow);
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Copy Results */
|
||||
.copy-results {
|
||||
display: none;
|
||||
padding: var(--spacing-l) var(--spacing-m);
|
||||
}
|
||||
|
||||
.copy-results--visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* AI Tip */
|
||||
.ai-tip {
|
||||
background: linear-gradient(135deg, #FFF9E6 0%, #FFFFFF 100%);
|
||||
border-left: 4px solid #FFB800;
|
||||
padding: var(--spacing-m);
|
||||
border-radius: var(--radius-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.ai-tip__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.ai-tip__icon {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
.ai-tip__title {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.ai-tip__content {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Copy Variations */
|
||||
.copy-variations {
|
||||
display: grid;
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
/* Copy Card */
|
||||
.copy-card {
|
||||
background: var(--color-white);
|
||||
border: 2px solid var(--color-border-light);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-normal);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.copy-card:hover {
|
||||
border-color: var(--color-secondary);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.copy-card--selected {
|
||||
border-color: var(--color-secondary);
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.copy-card--recommended {
|
||||
border-color: var(--color-secondary);
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
}
|
||||
|
||||
.copy-card__badge {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
right: var(--spacing-l);
|
||||
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-dark) 100%);
|
||||
color: var(--color-white);
|
||||
padding: var(--spacing-xs) var(--spacing-m);
|
||||
border-radius: var(--radius-s);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.copy-card__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.copy-card__label {
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.copy-card__radio {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.copy-card__headline {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-m);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.copy-card__body {
|
||||
font-size: var(--font-size-m);
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.6;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.copy-card__cta {
|
||||
display: inline-block;
|
||||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
|
||||
color: var(--color-white);
|
||||
padding: var(--spacing-s) var(--spacing-l);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.copy-card__hashtags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-xs);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.hashtag {
|
||||
background: var(--color-primary-light);
|
||||
color: var(--color-primary);
|
||||
padding: var(--spacing-xs) var(--spacing-s);
|
||||
border-radius: var(--radius-s);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.copy-card__actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
padding-top: var(--spacing-m);
|
||||
border-top: 1px solid var(--color-border-light);
|
||||
}
|
||||
|
||||
.copy-card__action-btn {
|
||||
flex: 1;
|
||||
padding: var(--spacing-s);
|
||||
background: var(--color-white);
|
||||
border: 1px solid var(--color-border-light);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.copy-card__action-btn:hover {
|
||||
border-color: var(--color-secondary);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
/* Edit Modal */
|
||||
.edit-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
padding: var(--spacing-m);
|
||||
}
|
||||
|
||||
.edit-modal--active {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.edit-modal__content {
|
||||
background: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-l);
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.edit-modal__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.edit-modal__title {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.edit-modal__close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: var(--font-size-2xl);
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.edit-modal__form {
|
||||
display: grid;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: grid;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.form-group__label {
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.form-group__input,
|
||||
.form-group__textarea {
|
||||
width: 100%;
|
||||
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);
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.form-group__textarea {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.form-group__input:focus,
|
||||
.form-group__textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-secondary);
|
||||
box-shadow: 0 0 0 3px rgba(255, 107, 107, 0.15);
|
||||
}
|
||||
|
||||
.edit-modal__actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-s);
|
||||
margin-top: var(--spacing-l);
|
||||
}
|
||||
|
||||
.modal-btn {
|
||||
padding: var(--spacing-m);
|
||||
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);
|
||||
}
|
||||
|
||||
.modal-btn--cancel {
|
||||
background: var(--color-white);
|
||||
border: 1px solid var(--color-border-light);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.modal-btn--save {
|
||||
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-dark) 100%);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
.copy-header {
|
||||
padding: var(--spacing-2xl) var(--spacing-xl);
|
||||
}
|
||||
|
||||
.copy-results {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop */
|
||||
@media (min-width: 1024px) {
|
||||
.copy-results {
|
||||
max-width: 960px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="copy-container">
|
||||
<!-- Header -->
|
||||
<header class="copy-header">
|
||||
<button class="copy-header__back" onclick="history.back()">
|
||||
←
|
||||
</button>
|
||||
<h1 class="copy-header__title">AI 홍보 문구 생성</h1>
|
||||
<p class="copy-header__subtitle">AI가 맞춤형 홍보 문구를 생성해 드려요</p>
|
||||
</header>
|
||||
|
||||
<!-- 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>
|
||||
<h2 class="loading-state__title">AI가 홍보 문구를 생성하고 있어요</h2>
|
||||
<p class="loading-state__text" id="loadingText">이벤트 정보를 분석하고 있어요...</p>
|
||||
<div class="loading-state__progress">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- Copy Results -->
|
||||
<div class="copy-results" id="copyResults">
|
||||
<!-- AI Tip -->
|
||||
<div class="ai-tip">
|
||||
<div class="ai-tip__header">
|
||||
<span class="ai-tip__icon">💡</span>
|
||||
<h2 class="ai-tip__title">AI 추천</h2>
|
||||
</div>
|
||||
<p class="ai-tip__content">
|
||||
<strong>버전 1</strong>은 감성적인 톤으로 고객의 공감을 이끌어내기에 가장 적합합니다.
|
||||
SNS 공유 시 참여율이 평균 35% 높게 나타났어요.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Copy Variations -->
|
||||
<div class="copy-variations" id="copyVariations">
|
||||
<!-- Copy cards will be inserted here -->
|
||||
</div>
|
||||
|
||||
<!-- Next Button -->
|
||||
<button class="next-button" id="nextButton" disabled>
|
||||
이벤트 기획안 확인하기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div class="edit-modal" id="editModal">
|
||||
<div class="edit-modal__content">
|
||||
<div class="edit-modal__header">
|
||||
<h2 class="edit-modal__title">홍보 문구 수정</h2>
|
||||
<button class="edit-modal__close" id="closeModal">×</button>
|
||||
</div>
|
||||
<form class="edit-modal__form" id="editForm">
|
||||
<div class="form-group">
|
||||
<label class="form-group__label">헤드라인</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-group__input"
|
||||
id="editHeadline"
|
||||
maxlength="100"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-group__label">본문</label>
|
||||
<textarea
|
||||
class="form-group__textarea"
|
||||
id="editBody"
|
||||
maxlength="300"
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-group__label">행동 유도 문구 (CTA)</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-group__input"
|
||||
id="editCTA"
|
||||
maxlength="50"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-group__label">해시태그 (쉼표로 구분)</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-group__input"
|
||||
id="editHashtags"
|
||||
placeholder="이벤트, 할인, 초대"
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
<div class="edit-modal__actions">
|
||||
<button class="modal-btn modal-btn--cancel" id="cancelEdit">취소</button>
|
||||
<button class="modal-btn modal-btn--save" id="saveEdit">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Utility functions
|
||||
const $ = (selector) => document.querySelector(selector);
|
||||
const $$ = (selector) => document.querySelectorAll(selector);
|
||||
|
||||
const addClass = (el, className) => el?.classList.add(className);
|
||||
const removeClass = (el, className) => el?.classList.remove(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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Elements
|
||||
const loadingState = $('#loadingState');
|
||||
const copyResults = $('#copyResults');
|
||||
const loadingText = $('#loadingText');
|
||||
const progressBar = $('#progressBar');
|
||||
const progressText = $('#progressText');
|
||||
const copyVariations = $('#copyVariations');
|
||||
const nextButton = $('#nextButton');
|
||||
const editModal = $('#editModal');
|
||||
const closeModal = $('#closeModal');
|
||||
const cancelEdit = $('#cancelEdit');
|
||||
const saveEdit = $('#saveEdit');
|
||||
|
||||
// Edit form elements
|
||||
const editHeadline = $('#editHeadline');
|
||||
const editBody = $('#editBody');
|
||||
const editCTA = $('#editCTA');
|
||||
const editHashtags = $('#editHashtags');
|
||||
|
||||
// State
|
||||
let selectedCopy = null;
|
||||
let currentEditIndex = null;
|
||||
const copyOptions = [
|
||||
{
|
||||
version: '버전 1',
|
||||
recommended: true,
|
||||
headline: '🎉 친구 초대하고 함께 받는 특별한 혜택!',
|
||||
body: '맛있는 순간을 소중한 사람과 함께하세요. 친구를 초대하면 둘 다 특별한 선물을 드려요. 지금 바로 참여하고 행복을 나누세요!',
|
||||
cta: '지금 친구 초대하고 혜택 받기',
|
||||
hashtags: ['친구초대이벤트', '함께할인', '소상공인응원', 'SNS이벤트']
|
||||
},
|
||||
{
|
||||
version: '버전 2',
|
||||
recommended: false,
|
||||
headline: '💝 지금 초대하면 특별 혜택 2배!',
|
||||
body: '단 3일간! 친구를 초대하고 최대 50% 할인 쿠폰을 받아가세요. 초대한 친구도 동일한 혜택을 받을 수 있어요.',
|
||||
cta: '3일 한정 특가 혜택 받기',
|
||||
hashtags: ['한정이벤트', '친구초대', '특별할인', '이벤트']
|
||||
},
|
||||
{
|
||||
version: '버전 3',
|
||||
recommended: false,
|
||||
headline: '🎁 우리 가게 단골이 되어주세요!',
|
||||
body: '매일 찾아주시는 고객님들을 위한 특별한 이벤트를 준비했어요. 친구와 함께 방문하면 더 큰 혜택이 기다립니다.',
|
||||
cta: '단골 고객 혜택 확인하기',
|
||||
hashtags: ['단골이벤트', '매장방문', '친구와함께', '로컬맛집']
|
||||
}
|
||||
];
|
||||
|
||||
// Loading animation
|
||||
let currentStep = 0;
|
||||
const progressSteps = [
|
||||
{ progress: 20, text: '이벤트 정보를 분석하고 있어요...' },
|
||||
{ progress: 40, text: '타겟 고객 성향을 파악하고 있어요...' },
|
||||
{ progress: 60, text: '감성적인 키워드를 추출하고 있어요...' },
|
||||
{ progress: 80, text: '다양한 문구를 생성하고 있어요...' },
|
||||
{ progress: 100, text: '최적의 문구를 선별하고 있어요...' }
|
||||
];
|
||||
|
||||
function updateProgress() {
|
||||
if (currentStep >= progressSteps.length) {
|
||||
// Show results
|
||||
loadingState.style.display = 'none';
|
||||
addClass(copyResults, 'copy-results--visible');
|
||||
renderCopyVariations();
|
||||
return;
|
||||
}
|
||||
|
||||
const step = progressSteps[currentStep];
|
||||
progressBar.style.width = step.progress + '%';
|
||||
progressText.textContent = step.progress + '%';
|
||||
loadingText.textContent = step.text;
|
||||
|
||||
currentStep++;
|
||||
setTimeout(updateProgress, 800);
|
||||
}
|
||||
|
||||
// Render copy variations
|
||||
function renderCopyVariations() {
|
||||
copyVariations.innerHTML = copyOptions.map((copy, index) => `
|
||||
<div class="copy-card ${copy.recommended ? 'copy-card--recommended' : ''}" data-index="${index}">
|
||||
${copy.recommended ? '<div class="copy-card__badge">🤖 AI 추천</div>' : ''}
|
||||
<div class="copy-card__header">
|
||||
<span class="copy-card__label">${copy.version}</span>
|
||||
<input type="radio" name="copy" value="${index}" class="copy-card__radio">
|
||||
</div>
|
||||
<h3 class="copy-card__headline">${copy.headline}</h3>
|
||||
<p class="copy-card__body">${copy.body}</p>
|
||||
<div class="copy-card__cta">${copy.cta}</div>
|
||||
<div class="copy-card__hashtags">
|
||||
${copy.hashtags.map(tag => `<span class="hashtag">#${tag}</span>`).join('')}
|
||||
</div>
|
||||
<div class="copy-card__actions">
|
||||
<button class="copy-card__action-btn" onclick="editCopy(${index})">✏️ 수정</button>
|
||||
<button class="copy-card__action-btn" onclick="copyCopy(${index})">📋 복사</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// Add click handlers
|
||||
const cards = document.querySelectorAll('.copy-card');
|
||||
cards.forEach(card => {
|
||||
card.addEventListener('click', (e) => {
|
||||
// Don't trigger if clicking action buttons
|
||||
if (e.target.closest('.copy-card__action-btn')) return;
|
||||
|
||||
const index = parseInt(card.getAttribute('data-index'));
|
||||
selectCopy(index);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Select copy
|
||||
function selectCopy(index) {
|
||||
selectedCopy = copyOptions[index];
|
||||
|
||||
// Update UI
|
||||
const cards = document.querySelectorAll('.copy-card');
|
||||
cards.forEach(card => removeClass(card, 'copy-card--selected'));
|
||||
addClass(cards[index], 'copy-card--selected');
|
||||
|
||||
// Update radio
|
||||
const radio = cards[index].querySelector('.copy-card__radio');
|
||||
radio.checked = true;
|
||||
|
||||
nextButton.disabled = false;
|
||||
}
|
||||
|
||||
// Edit copy
|
||||
window.editCopy = function(index) {
|
||||
currentEditIndex = index;
|
||||
const copy = copyOptions[index];
|
||||
|
||||
editHeadline.value = copy.headline;
|
||||
editBody.value = copy.body;
|
||||
editCTA.value = copy.cta;
|
||||
editHashtags.value = copy.hashtags.join(', ');
|
||||
|
||||
addClass(editModal, 'edit-modal--active');
|
||||
};
|
||||
|
||||
// Copy to clipboard
|
||||
window.copyCopy = function(index) {
|
||||
const copy = copyOptions[index];
|
||||
const text = `${copy.headline}\n\n${copy.body}\n\n${copy.cta}\n\n${copy.hashtags.map(t => '#' + t).join(' ')}`;
|
||||
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
alert('홍보 문구가 클립보드에 복사되었습니다.');
|
||||
}).catch(() => {
|
||||
alert('복사에 실패했습니다. 다시 시도해 주세요.');
|
||||
});
|
||||
};
|
||||
|
||||
// Close modal
|
||||
closeModal.addEventListener('click', () => {
|
||||
removeClass(editModal, 'edit-modal--active');
|
||||
});
|
||||
|
||||
cancelEdit.addEventListener('click', () => {
|
||||
removeClass(editModal, 'edit-modal--active');
|
||||
});
|
||||
|
||||
// Save edit
|
||||
saveEdit.addEventListener('click', () => {
|
||||
if (currentEditIndex !== null) {
|
||||
copyOptions[currentEditIndex] = {
|
||||
...copyOptions[currentEditIndex],
|
||||
headline: editHeadline.value,
|
||||
body: editBody.value,
|
||||
cta: editCTA.value,
|
||||
hashtags: editHashtags.value.split(',').map(t => t.trim()).filter(t => t)
|
||||
};
|
||||
|
||||
renderCopyVariations();
|
||||
|
||||
// Re-select if it was selected
|
||||
if (selectedCopy && selectedCopy.version === copyOptions[currentEditIndex].version) {
|
||||
selectCopy(currentEditIndex);
|
||||
}
|
||||
}
|
||||
|
||||
removeClass(editModal, 'edit-modal--active');
|
||||
});
|
||||
|
||||
// Close modal on background click
|
||||
editModal.addEventListener('click', (e) => {
|
||||
if (e.target === editModal) {
|
||||
removeClass(editModal, 'edit-modal--active');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle next button
|
||||
nextButton.addEventListener('click', () => {
|
||||
if (!selectedCopy) return;
|
||||
|
||||
// Get event draft
|
||||
const eventDraft = storage.get('eventDraft') || {};
|
||||
|
||||
// Save promotional copy
|
||||
eventDraft.promotionalCopy = {
|
||||
...selectedCopy,
|
||||
selectedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
storage.set('eventDraft', eventDraft);
|
||||
|
||||
// Show loading state
|
||||
addClass(nextButton, 'next-button--loading');
|
||||
nextButton.disabled = true;
|
||||
|
||||
// Navigate to next screen
|
||||
setTimeout(() => {
|
||||
window.location.href = '09-AI이미지생성.html';
|
||||
}, 800);
|
||||
});
|
||||
|
||||
// Start loading animation on page load
|
||||
window.addEventListener('load', () => {
|
||||
setTimeout(updateProgress, 500);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -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>
|
||||
@ -1,719 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>이벤트 기획안 승인 - KT 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<style>
|
||||
/* Layout */
|
||||
.approval-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
padding-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.approval-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);
|
||||
}
|
||||
|
||||
.approval-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;
|
||||
}
|
||||
|
||||
.approval-header__badge {
|
||||
display: inline-block;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: var(--spacing-xs) var(--spacing-m);
|
||||
border-radius: var(--radius-s);
|
||||
font-size: var(--font-size-s);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.approval-header__title {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.approval-header__subtitle {
|
||||
font-size: var(--font-size-m);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.approval-content {
|
||||
padding: var(--spacing-l) var(--spacing-m);
|
||||
}
|
||||
|
||||
/* Success Message */
|
||||
.success-message {
|
||||
background: linear-gradient(135deg, #F0FFF4 0%, #FFFFFF 100%);
|
||||
border-left: 4px solid var(--color-success);
|
||||
padding: var(--spacing-m);
|
||||
border-radius: var(--radius-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.success-message__icon {
|
||||
font-size: 48px;
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.success-message__title {
|
||||
font-size: var(--font-size-l);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.success-message__text {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* AI Prediction */
|
||||
.ai-prediction {
|
||||
background: var(--color-white);
|
||||
border: 2px solid var(--color-secondary-light);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.ai-prediction__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.ai-prediction__icon {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
.ai-prediction__title {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.ai-prediction__stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.prediction-stat {
|
||||
text-align: center;
|
||||
padding: var(--spacing-s);
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
border-radius: var(--radius-m);
|
||||
}
|
||||
|
||||
.prediction-stat__value {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-primary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.prediction-stat__label {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Event Preview */
|
||||
.event-preview {
|
||||
background: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-l);
|
||||
box-shadow: var(--shadow-md);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.event-preview__label {
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: var(--spacing-m);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.event-preview__card {
|
||||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
|
||||
color: var(--color-white);
|
||||
padding: var(--spacing-l);
|
||||
border-radius: var(--radius-l);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.event-preview__card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
right: -10%;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.event-preview__headline {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: var(--spacing-m);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.event-preview__body {
|
||||
font-size: var(--font-size-m);
|
||||
line-height: 1.6;
|
||||
margin-bottom: var(--spacing-m);
|
||||
opacity: 0.95;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.event-preview__hashtags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-xs);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.event-preview__hashtag {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: var(--spacing-xs) var(--spacing-s);
|
||||
border-radius: var(--radius-s);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
/* Summary Sections */
|
||||
.summary-sections {
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.summary-section {
|
||||
background: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
margin-bottom: var(--spacing-m);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.summary-section__header {
|
||||
padding: var(--spacing-m);
|
||||
background: linear-gradient(135deg, #F8F9FF 0%, #FFFFFF 100%);
|
||||
border-bottom: 1px solid var(--color-border-light);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.summary-section__title {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.summary-section__icon {
|
||||
font-size: var(--font-size-l);
|
||||
}
|
||||
|
||||
.summary-section__edit {
|
||||
background: none;
|
||||
border: 1px solid var(--color-border-light);
|
||||
color: var(--color-text-secondary);
|
||||
padding: var(--spacing-xs) var(--spacing-s);
|
||||
border-radius: var(--radius-s);
|
||||
font-size: var(--font-size-xs);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.summary-section__edit:hover {
|
||||
border-color: var(--color-secondary);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.summary-section__content {
|
||||
padding: var(--spacing-m);
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.summary-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.summary-item__label {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.summary-item__value {
|
||||
font-size: var(--font-size-m);
|
||||
color: var(--color-text-primary);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.summary-item__badge {
|
||||
display: inline-block;
|
||||
background: var(--color-primary-50);
|
||||
color: var(--color-primary);
|
||||
padding: 2px var(--spacing-s);
|
||||
border-radius: var(--radius-s);
|
||||
font-size: var(--font-size-xs);
|
||||
margin-right: var(--spacing-xs);
|
||||
}
|
||||
|
||||
/* Action Buttons */
|
||||
.action-buttons {
|
||||
display: grid;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.action-button {
|
||||
width: 100%;
|
||||
padding: var(--spacing-m);
|
||||
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);
|
||||
}
|
||||
|
||||
.action-button--primary {
|
||||
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-dark) 100%);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.action-button--primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.action-button--secondary {
|
||||
background: var(--color-white);
|
||||
border: 2px solid var(--color-border-light);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.action-button--secondary:hover {
|
||||
border-color: var(--color-secondary);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.action-button--loading {
|
||||
position: relative;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.action-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) {
|
||||
.approval-header {
|
||||
padding: var(--spacing-2xl) var(--spacing-xl);
|
||||
}
|
||||
|
||||
.approval-content {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop */
|
||||
@media (min-width: 1024px) {
|
||||
.approval-content {
|
||||
max-width: 960px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="approval-container">
|
||||
<!-- Header -->
|
||||
<header class="approval-header">
|
||||
<button class="approval-header__back" onclick="history.back()">
|
||||
←
|
||||
</button>
|
||||
<div class="approval-header__badge">✨ AI 기획 완료</div>
|
||||
<h1 class="approval-header__title">이벤트 기획안 승인</h1>
|
||||
<p class="approval-header__subtitle">AI가 생성한 이벤트 기획안을 확인하고 승인해 주세요</p>
|
||||
</header>
|
||||
|
||||
<!-- Content -->
|
||||
<main class="approval-content">
|
||||
<!-- Success Message -->
|
||||
<div class="success-message">
|
||||
<div class="success-message__icon">🎉</div>
|
||||
<h2 class="success-message__title">완벽한 이벤트 기획안이 완성되었어요!</h2>
|
||||
<p class="success-message__text">
|
||||
AI가 분석한 데이터를 바탕으로 최적의 이벤트를 설계했습니다.
|
||||
아래 내용을 확인하고 승인하시면 다음 단계로 진행됩니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- AI Prediction -->
|
||||
<div class="ai-prediction">
|
||||
<div class="ai-prediction__header">
|
||||
<span class="ai-prediction__icon">🤖</span>
|
||||
<h3 class="ai-prediction__title">AI 예상 성과</h3>
|
||||
</div>
|
||||
<div class="ai-prediction__stats">
|
||||
<div class="prediction-stat">
|
||||
<div class="prediction-stat__value">1,200명</div>
|
||||
<div class="prediction-stat__label">예상 참여자</div>
|
||||
</div>
|
||||
<div class="prediction-stat">
|
||||
<div class="prediction-stat__value">75%</div>
|
||||
<div class="prediction-stat__label">참여율</div>
|
||||
</div>
|
||||
<div class="prediction-stat">
|
||||
<div class="prediction-stat__value">3.5배</div>
|
||||
<div class="prediction-stat__label">ROI</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Event Preview -->
|
||||
<div class="event-preview">
|
||||
<div class="event-preview__label">📱 이벤트 미리보기</div>
|
||||
<div class="event-preview__card">
|
||||
<h3 class="event-preview__headline" id="previewHeadline">
|
||||
🎉 친구 초대하고 함께 받는 특별한 혜택!
|
||||
</h3>
|
||||
<p class="event-preview__body" id="previewBody">
|
||||
맛있는 순간을 소중한 사람과 함께하세요. 친구를 초대하면 둘 다 특별한 선물을 드려요.
|
||||
지금 바로 참여하고 행복을 나누세요!
|
||||
</p>
|
||||
<div class="event-preview__hashtags" id="previewHashtags">
|
||||
<span class="event-preview__hashtag">#친구초대이벤트</span>
|
||||
<span class="event-preview__hashtag">#함께할인</span>
|
||||
<span class="event-preview__hashtag">#소상공인응원</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary Sections -->
|
||||
<div class="summary-sections">
|
||||
<!-- Purpose -->
|
||||
<div class="summary-section">
|
||||
<div class="summary-section__header">
|
||||
<h3 class="summary-section__title">
|
||||
<span class="summary-section__icon">🎯</span>
|
||||
이벤트 목적
|
||||
</h3>
|
||||
<button class="summary-section__edit" onclick="window.location.href='04-이벤트목적선택.html'">
|
||||
수정
|
||||
</button>
|
||||
</div>
|
||||
<div class="summary-section__content">
|
||||
<div class="summary-item">
|
||||
<div class="summary-item__label">선택한 목적</div>
|
||||
<div class="summary-item__value" id="purposeValue">신규 고객 유치</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Trend Analysis -->
|
||||
<div class="summary-section">
|
||||
<div class="summary-section__header">
|
||||
<h3 class="summary-section__title">
|
||||
<span class="summary-section__icon">📊</span>
|
||||
트렌드 분석
|
||||
</h3>
|
||||
<button class="summary-section__edit" onclick="window.location.href='05-AI트렌드분석결과.html'">
|
||||
수정
|
||||
</button>
|
||||
</div>
|
||||
<div class="summary-section__content">
|
||||
<div class="summary-item">
|
||||
<div class="summary-item__label">Top 트렌드</div>
|
||||
<div class="summary-item__value" id="trendValue">친구 초대 이벤트</div>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<div class="summary-item__label">예상 참여율</div>
|
||||
<div class="summary-item__value" id="engagementValue">68%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Prize -->
|
||||
<div class="summary-section">
|
||||
<div class="summary-section__header">
|
||||
<h3 class="summary-section__title">
|
||||
<span class="summary-section__icon">🎁</span>
|
||||
이벤트 경품
|
||||
</h3>
|
||||
<button class="summary-section__edit" onclick="window.location.href='06-AI이벤트상품추천.html'">
|
||||
수정
|
||||
</button>
|
||||
</div>
|
||||
<div class="summary-section__content">
|
||||
<div class="summary-item">
|
||||
<div class="summary-item__label">선택한 경품</div>
|
||||
<div class="summary-item__value" id="prizeValue">
|
||||
<span class="summary-item__badge">추천형</span>
|
||||
친구 초대 특전
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Participation Method -->
|
||||
<div class="summary-section">
|
||||
<div class="summary-section__header">
|
||||
<h3 class="summary-section__title">
|
||||
<span class="summary-section__icon">📱</span>
|
||||
참여 방법
|
||||
</h3>
|
||||
<button class="summary-section__edit" onclick="window.location.href='07-AI참여방법설계.html'">
|
||||
수정
|
||||
</button>
|
||||
</div>
|
||||
<div class="summary-section__content">
|
||||
<div class="summary-item">
|
||||
<div class="summary-item__label">선택한 방법</div>
|
||||
<div class="summary-item__value" id="participationValue">
|
||||
<span class="summary-item__badge">바이럴형</span>
|
||||
SNS 공유하기
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Promotional Copy -->
|
||||
<div class="summary-section">
|
||||
<div class="summary-section__header">
|
||||
<h3 class="summary-section__title">
|
||||
<span class="summary-section__icon">✍️</span>
|
||||
홍보 문구
|
||||
</h3>
|
||||
<button class="summary-section__edit" onclick="window.location.href='08-AI홍보문구생성.html'">
|
||||
수정
|
||||
</button>
|
||||
</div>
|
||||
<div class="summary-section__content">
|
||||
<div class="summary-item">
|
||||
<div class="summary-item__label">헤드라인</div>
|
||||
<div class="summary-item__value" id="copyHeadlineValue">
|
||||
🎉 친구 초대하고 함께 받는 특별한 혜택!
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<div class="summary-item__label">CTA</div>
|
||||
<div class="summary-item__value" id="copyCTAValue">
|
||||
지금 친구 초대하고 혜택 받기
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="action-buttons">
|
||||
<button class="action-button action-button--secondary" id="saveDraftButton">
|
||||
📝 임시저장
|
||||
</button>
|
||||
<button class="action-button action-button--primary" id="approveButton">
|
||||
✅ 승인하고 다음으로
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/common.js"></script>
|
||||
<script>
|
||||
const { $, addClass, storage } = window.CommonUtils;
|
||||
|
||||
// Elements
|
||||
const previewHeadline = $('#previewHeadline');
|
||||
const previewBody = $('#previewBody');
|
||||
const previewHashtags = $('#previewHashtags');
|
||||
const purposeValue = $('#purposeValue');
|
||||
const trendValue = $('#trendValue');
|
||||
const engagementValue = $('#engagementValue');
|
||||
const prizeValue = $('#prizeValue');
|
||||
const participationValue = $('#participationValue');
|
||||
const copyHeadlineValue = $('#copyHeadlineValue');
|
||||
const copyCTAValue = $('#copyCTAValue');
|
||||
const saveDraftButton = $('#saveDraftButton');
|
||||
const approveButton = $('#approveButton');
|
||||
|
||||
// Load event draft data
|
||||
const eventDraft = storage.get('eventDraft') || {};
|
||||
|
||||
// Purpose labels
|
||||
const purposeLabels = {
|
||||
'new-customers': '신규 고객 유치',
|
||||
'repeat-visits': '재방문 유도',
|
||||
'sales-boost': '매출 증대',
|
||||
'brand-awareness': '브랜드 인지도',
|
||||
'engagement': '고객 참여 활성화',
|
||||
'seasonal': '시즌/기념일 마케팅'
|
||||
};
|
||||
|
||||
// Prize labels
|
||||
const prizeLabels = {
|
||||
'friend-invite': '친구 초대 특전',
|
||||
'discount': '할인 쿠폰',
|
||||
'freebie': '무료 증정'
|
||||
};
|
||||
|
||||
// Participation method labels
|
||||
const participationLabels = {
|
||||
'sns-share': 'SNS 공유하기',
|
||||
'qr-scan': 'QR 코드 스캔',
|
||||
'coupon-download': '쿠폰 다운로드',
|
||||
'store-visit': '매장 방문 인증',
|
||||
'survey': '설문 참여',
|
||||
'friend-invite': '친구 초대'
|
||||
};
|
||||
|
||||
// Update summary with saved data
|
||||
function updateSummary() {
|
||||
// Purpose
|
||||
if (eventDraft.purpose) {
|
||||
purposeValue.textContent = purposeLabels[eventDraft.purpose] || eventDraft.purpose;
|
||||
}
|
||||
|
||||
// Trend Analysis
|
||||
if (eventDraft.trendAnalysis) {
|
||||
trendValue.textContent = eventDraft.trendAnalysis.topTrend || '친구 초대 이벤트';
|
||||
engagementValue.textContent = eventDraft.trendAnalysis.insights?.engagement || '68%';
|
||||
}
|
||||
|
||||
// Prize
|
||||
if (eventDraft.prize) {
|
||||
const prizeText = eventDraft.prize.custom || prizeLabels[eventDraft.prize.type] || '친구 초대 특전';
|
||||
const badge = eventDraft.prize.type === 'friend-invite' ? '추천형' :
|
||||
eventDraft.prize.type === 'discount' ? '즉시 혜택형' :
|
||||
eventDraft.prize.type === 'freebie' ? '체험형' : '';
|
||||
|
||||
prizeValue.innerHTML = badge ?
|
||||
`<span class="summary-item__badge">${badge}</span>${prizeText}` :
|
||||
prizeText;
|
||||
}
|
||||
|
||||
// Participation Method
|
||||
if (eventDraft.participationMethod) {
|
||||
const methodText = eventDraft.participationMethod.custom ||
|
||||
participationLabels[eventDraft.participationMethod.type] ||
|
||||
'SNS 공유하기';
|
||||
const badge = eventDraft.participationMethod.type === 'sns-share' ? '바이럴형' :
|
||||
eventDraft.participationMethod.type === 'qr-scan' ? '오프라인형' :
|
||||
eventDraft.participationMethod.type === 'coupon-download' ? '즉시 혜택형' :
|
||||
eventDraft.participationMethod.type === 'store-visit' ? '오프라인형' :
|
||||
eventDraft.participationMethod.type === 'survey' ? '데이터 수집형' :
|
||||
eventDraft.participationMethod.type === 'friend-invite' ? '추천형' : '';
|
||||
|
||||
participationValue.innerHTML = badge ?
|
||||
`<span class="summary-item__badge">${badge}</span>${methodText}` :
|
||||
methodText;
|
||||
}
|
||||
|
||||
// Promotional Copy
|
||||
if (eventDraft.promotionalCopy) {
|
||||
const copy = eventDraft.promotionalCopy;
|
||||
|
||||
copyHeadlineValue.textContent = copy.headline || '🎉 친구 초대하고 함께 받는 특별한 혜택!';
|
||||
copyCTAValue.textContent = copy.cta || '지금 친구 초대하고 혜택 받기';
|
||||
|
||||
// Update preview
|
||||
previewHeadline.textContent = copy.headline || '🎉 친구 초대하고 함께 받는 특별한 혜택!';
|
||||
previewBody.textContent = copy.body || '맛있는 순간을 소중한 사람과 함께하세요.';
|
||||
|
||||
if (copy.hashtags && copy.hashtags.length > 0) {
|
||||
previewHashtags.innerHTML = copy.hashtags
|
||||
.map(tag => `<span class="event-preview__hashtag">#${tag}</span>`)
|
||||
.join('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save draft
|
||||
saveDraftButton.addEventListener('click', () => {
|
||||
// Mark as draft
|
||||
eventDraft.status = 'draft';
|
||||
eventDraft.savedAt = new Date().toISOString();
|
||||
storage.set('eventDraft', eventDraft);
|
||||
|
||||
alert('✅ 이벤트 기획안이 임시저장되었습니다.');
|
||||
});
|
||||
|
||||
// Approve and continue
|
||||
approveButton.addEventListener('click', () => {
|
||||
// Mark as approved
|
||||
eventDraft.status = 'approved';
|
||||
eventDraft.approvedAt = new Date().toISOString();
|
||||
storage.set('eventDraft', eventDraft);
|
||||
|
||||
// Show loading
|
||||
addClass(approveButton, 'action-button--loading');
|
||||
approveButton.disabled = true;
|
||||
|
||||
// Navigate to next phase (AI content generation)
|
||||
setTimeout(() => {
|
||||
window.location.href = '10-AI이미지생성.html';
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Initialize
|
||||
window.addEventListener('load', () => {
|
||||
updateSummary();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,506 +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>
|
||||
<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 */
|
||||
.video-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
padding-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.video-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);
|
||||
}
|
||||
|
||||
.video-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;
|
||||
}
|
||||
|
||||
.video-header__title {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.video-header__subtitle {
|
||||
font-size: var(--font-size-m);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.video-content {
|
||||
padding: var(--spacing-l) var(--spacing-m);
|
||||
}
|
||||
|
||||
/* Info Card */
|
||||
.info-card {
|
||||
background: linear-gradient(135deg, #FFF9E6 0%, #FFFFFF 100%);
|
||||
border-left: 4px solid #FFB800;
|
||||
padding: var(--spacing-m);
|
||||
border-radius: var(--radius-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.info-card__title {
|
||||
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-s);
|
||||
}
|
||||
|
||||
.info-card__text {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Video Options */
|
||||
.video-options {
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.option-card {
|
||||
background: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-m);
|
||||
cursor: pointer;
|
||||
border: 2px solid var(--color-border-light);
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.option-card:hover {
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.option-card--selected {
|
||||
border-color: var(--color-secondary);
|
||||
background: var(--color-secondary-light);
|
||||
}
|
||||
|
||||
.option-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.option-card__icon {
|
||||
font-size: var(--font-size-2xl);
|
||||
}
|
||||
|
||||
.option-card__title {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.option-card__description {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Skip Button */
|
||||
.skip-button {
|
||||
width: 100%;
|
||||
padding: var(--spacing-m);
|
||||
background: var(--color-white);
|
||||
border: 2px solid var(--color-border-light);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-normal);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.skip-button: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) {
|
||||
.video-header {
|
||||
padding: var(--spacing-2xl) var(--spacing-xl);
|
||||
}
|
||||
|
||||
.video-content {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop */
|
||||
@media (min-width: 1024px) {
|
||||
.video-content {
|
||||
max-width: 960px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="video-container">
|
||||
<!-- Header -->
|
||||
<header class="video-header">
|
||||
<button class="video-header__back" onclick="history.back()">
|
||||
←
|
||||
</button>
|
||||
<h1 class="video-header__title">AI 영상 제작</h1>
|
||||
<p class="video-header__subtitle">이벤트 홍보 영상을 자동으로 제작해 드려요</p>
|
||||
</header>
|
||||
|
||||
<!-- Content -->
|
||||
<main class="video-content">
|
||||
<!-- Info Card -->
|
||||
<div class="info-card">
|
||||
<h2 class="info-card__title">
|
||||
💡 선택사항
|
||||
</h2>
|
||||
<p class="info-card__text">
|
||||
영상 제작은 선택사항입니다. 영상이 필요하지 않으시면 "건너뛰기"를 선택하세요.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Video Options -->
|
||||
<div class="video-options">
|
||||
<div class="option-card" data-type="short" id="shortOption">
|
||||
<div class="option-card__header">
|
||||
<div class="option-card__icon">🎬</div>
|
||||
<h3 class="option-card__title">15초 숏폼 영상</h3>
|
||||
</div>
|
||||
<p class="option-card__description">
|
||||
인스타그램 릴스, 유튜브 쇼츠에 최적화된 세로형 15초 영상을 제작합니다.
|
||||
빠르고 강렬한 메시지 전달에 효과적입니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="option-card" data-type="standard" id="standardOption">
|
||||
<div class="option-card__header">
|
||||
<div class="option-card__icon">📹</div>
|
||||
<h3 class="option-card__title">30초 프로모션 영상</h3>
|
||||
</div>
|
||||
<p class="option-card__description">
|
||||
이벤트의 모든 내용을 담은 30초 가로형 영상을 제작합니다.
|
||||
페이스북, 유튜브 등 다양한 플랫폼에 활용할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="option-card" data-type="both" id="bothOption">
|
||||
<div class="option-card__header">
|
||||
<div class="option-card__icon">🎯</div>
|
||||
<h3 class="option-card__title">두 가지 모두 제작</h3>
|
||||
</div>
|
||||
<p class="option-card__description">
|
||||
15초 숏폼과 30초 프로모션 영상을 모두 제작합니다.
|
||||
모든 플랫폼에 최적화된 홍보가 가능합니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Buttons -->
|
||||
<button class="skip-button" id="skipButton">
|
||||
영상 제작 건너뛰기
|
||||
</button>
|
||||
|
||||
<button class="next-button" id="nextButton" disabled>
|
||||
선택 완료
|
||||
</button>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<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 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Elements
|
||||
const shortOption = $('#shortOption');
|
||||
const standardOption = $('#standardOption');
|
||||
const bothOption = $('#bothOption');
|
||||
const skipButton = $('#skipButton');
|
||||
const nextButton = $('#nextButton');
|
||||
|
||||
// State
|
||||
let selectedType = null;
|
||||
|
||||
// Option click handlers
|
||||
[shortOption, standardOption, bothOption].forEach(option => {
|
||||
option.addEventListener('click', () => {
|
||||
const type = option.getAttribute('data-type');
|
||||
selectOption(type, option);
|
||||
});
|
||||
});
|
||||
|
||||
function selectOption(type, element) {
|
||||
// Deselect all
|
||||
[shortOption, standardOption, bothOption].forEach(opt => {
|
||||
removeClass(opt, 'option-card--selected');
|
||||
});
|
||||
|
||||
// Select clicked
|
||||
addClass(element, 'option-card--selected');
|
||||
selectedType = type;
|
||||
nextButton.disabled = false;
|
||||
}
|
||||
|
||||
// Skip button
|
||||
skipButton.addEventListener('click', () => {
|
||||
// Save skip decision
|
||||
const eventContent = storage.get('eventContent') || {};
|
||||
eventContent.video = {
|
||||
skipped: true
|
||||
};
|
||||
storage.set('eventContent', eventContent);
|
||||
|
||||
// Navigate to next screen
|
||||
addClass(skipButton, 'next-button--loading');
|
||||
setTimeout(() => {
|
||||
window.location.href = '11-SNS콘텐츠생성.html';
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// Next button
|
||||
nextButton.addEventListener('click', () => {
|
||||
if (!selectedType) return;
|
||||
|
||||
// Save video selection
|
||||
const eventContent = storage.get('eventContent') || {};
|
||||
eventContent.video = {
|
||||
skipped: false,
|
||||
type: selectedType,
|
||||
createdAt: 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);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,758 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>SNS 콘텐츠 생성 - KT 이벤트 마케팅</title>
|
||||
<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 */
|
||||
.sns-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
padding-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.sns-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);
|
||||
}
|
||||
|
||||
.sns-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;
|
||||
}
|
||||
|
||||
.sns-header__title {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.sns-header__subtitle {
|
||||
font-size: var(--font-size-m);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.sns-content {
|
||||
padding: var(--spacing-l) var(--spacing-m);
|
||||
}
|
||||
|
||||
/* AI Tip */
|
||||
.ai-tip {
|
||||
background: linear-gradient(135deg, #FFF9E6 0%, #FFFFFF 100%);
|
||||
border-left: 4px solid #FFB800;
|
||||
padding: var(--spacing-m);
|
||||
border-radius: var(--radius-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.ai-tip__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.ai-tip__icon {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
.ai-tip__title {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.ai-tip__content {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Platform Grid */
|
||||
.platform-grid {
|
||||
display: grid;
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
/* Platform Card */
|
||||
.platform-card {
|
||||
background: var(--color-white);
|
||||
border: 2px solid var(--color-border-light);
|
||||
border-radius: var(--radius-l);
|
||||
overflow: hidden;
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.platform-card--selected {
|
||||
border-color: var(--color-secondary);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.platform-card__header {
|
||||
padding: var(--spacing-m);
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
border-bottom: 1px solid var(--color-border-light);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.platform-card__info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.platform-card__icon {
|
||||
font-size: var(--font-size-2xl);
|
||||
}
|
||||
|
||||
.platform-card__name {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.platform-card__toggle {
|
||||
width: 48px;
|
||||
height: 28px;
|
||||
background: var(--color-border-light);
|
||||
border-radius: 14px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.platform-card__toggle--active {
|
||||
background: var(--color-secondary);
|
||||
}
|
||||
|
||||
.platform-card__toggle::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: var(--color-white);
|
||||
border-radius: 50%;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
transition: all var(--transition-normal);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.platform-card__toggle--active::after {
|
||||
left: 22px;
|
||||
}
|
||||
|
||||
.platform-card__preview {
|
||||
padding: var(--spacing-m);
|
||||
background: var(--color-white);
|
||||
}
|
||||
|
||||
.post-preview {
|
||||
border: 1px solid var(--color-border-light);
|
||||
border-radius: var(--radius-m);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.post-preview__header {
|
||||
padding: var(--spacing-s);
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
border-bottom: 1px solid var(--color-border-light);
|
||||
}
|
||||
|
||||
.post-preview__avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: var(--color-secondary-light);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--font-size-s);
|
||||
}
|
||||
|
||||
.post-preview__username {
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.post-preview__image {
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
background: linear-gradient(135deg, #FFE9E9 0%, #FFF5F5 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.post-preview__content {
|
||||
padding: var(--spacing-m);
|
||||
}
|
||||
|
||||
.post-preview__caption {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-primary);
|
||||
line-height: 1.5;
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.post-preview__hashtags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-xs);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.hashtag {
|
||||
color: var(--color-secondary);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.post-preview__stats {
|
||||
display: flex;
|
||||
gap: var(--spacing-m);
|
||||
padding-top: var(--spacing-s);
|
||||
border-top: 1px solid var(--color-border-light);
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.platform-card__actions {
|
||||
padding: var(--spacing-m);
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
border-top: 1px solid var(--color-border-light);
|
||||
}
|
||||
|
||||
.platform-card__action-btn {
|
||||
flex: 1;
|
||||
padding: var(--spacing-s);
|
||||
background: var(--color-white);
|
||||
border: 1px solid var(--color-border-light);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.platform-card__action-btn:hover {
|
||||
border-color: var(--color-secondary);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
/* Generate All Button */
|
||||
.generate-all-button {
|
||||
width: 100%;
|
||||
padding: var(--spacing-m);
|
||||
background: var(--color-white);
|
||||
border: 2px dashed var(--color-border-light);
|
||||
border-radius: var(--radius-l);
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-normal);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.generate-all-button: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) {
|
||||
.sns-header {
|
||||
padding: var(--spacing-2xl) var(--spacing-xl);
|
||||
}
|
||||
|
||||
.sns-content {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop */
|
||||
@media (min-width: 1024px) {
|
||||
.sns-content {
|
||||
max-width: 960px;
|
||||
}
|
||||
|
||||
.platform-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sns-container">
|
||||
<!-- Header -->
|
||||
<header class="sns-header">
|
||||
<button class="sns-header__back" onclick="history.back()">
|
||||
←
|
||||
</button>
|
||||
<h1 class="sns-header__title">SNS 콘텐츠 생성</h1>
|
||||
<p class="sns-header__subtitle">각 SNS 플랫폼에 최적화된 콘텐츠를 생성해 드려요</p>
|
||||
</header>
|
||||
|
||||
<!-- Content -->
|
||||
<main class="sns-content">
|
||||
<!-- AI Tip -->
|
||||
<div class="ai-tip">
|
||||
<div class="ai-tip__header">
|
||||
<span class="ai-tip__icon">💡</span>
|
||||
<h2 class="ai-tip__title">AI 추천</h2>
|
||||
</div>
|
||||
<p class="ai-tip__content">
|
||||
<strong>인스타그램</strong>과 <strong>페이스북</strong>에서 가장 높은 참여율이 예상됩니다.
|
||||
두 플랫폼을 선택하시면 최대 효과를 얻으실 수 있어요.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Generate All Button -->
|
||||
<button class="generate-all-button" id="generateAllButton">
|
||||
✨ 모든 플랫폼 콘텐츠 한번에 생성하기
|
||||
</button>
|
||||
|
||||
<!-- Platform Grid -->
|
||||
<div class="platform-grid" id="platformGrid">
|
||||
<!-- Platform cards will be inserted here -->
|
||||
</div>
|
||||
|
||||
<!-- Next Button -->
|
||||
<button class="next-button" id="nextButton" disabled>
|
||||
다음 단계로
|
||||
</button>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Utility functions
|
||||
const $ = (selector) => document.querySelector(selector);
|
||||
const $$ = (selector) => document.querySelectorAll(selector);
|
||||
|
||||
const addClass = (el, className) => el?.classList.add(className);
|
||||
const removeClass = (el, className) => el?.classList.remove(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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Elements
|
||||
const platformGrid = $('#platformGrid');
|
||||
const generateAllButton = $('#generateAllButton');
|
||||
const nextButton = $('#nextButton');
|
||||
|
||||
// State
|
||||
const selectedPlatforms = new Set();
|
||||
|
||||
// Platform data
|
||||
const platforms = [
|
||||
{
|
||||
id: 'instagram',
|
||||
name: '인스타그램',
|
||||
icon: '📷',
|
||||
username: '@mystore_official',
|
||||
caption: '🎉 친구 초대하고 함께 받는 특별한 혜택!\n\n맛있는 순간을 소중한 사람과 함께하세요. 친구를 초대하면 둘 다 특별한 선물을 드려요. 💝',
|
||||
hashtags: ['친구초대이벤트', '함께할인', '소상공인응원', 'SNS이벤트'],
|
||||
stats: { likes: '1,234', comments: '156', shares: '89' },
|
||||
recommended: true
|
||||
},
|
||||
{
|
||||
id: 'facebook',
|
||||
name: '페이스북',
|
||||
icon: '👥',
|
||||
username: '우리가게',
|
||||
caption: '🎉 친구 초대 이벤트 진행 중!\n\n친구를 초대하고 함께 특별한 혜택을 받아가세요. 간단한 공유만으로 참여 완료!\n\n✅ 참여 방법\n1. 게시글 공유하기\n2. 친구 태그하기\n3. 매장 방문하여 혜택 받기',
|
||||
hashtags: ['친구초대', '이벤트', '할인'],
|
||||
stats: { likes: '892', comments: '67', shares: '234' },
|
||||
recommended: true
|
||||
},
|
||||
{
|
||||
id: 'kakao',
|
||||
name: '카카오톡',
|
||||
icon: '💬',
|
||||
username: '우리가게',
|
||||
caption: '[친구 초대 이벤트]\n친구를 초대하면 둘 다 혜택!\n\n기간: 이번 주 한정\n혜택: 친구 초대 특전\n\n지금 바로 참여하세요!',
|
||||
hashtags: [],
|
||||
stats: { views: '3,456', clicks: '892' },
|
||||
recommended: false
|
||||
},
|
||||
{
|
||||
id: 'twitter',
|
||||
name: '트위터(X)',
|
||||
icon: '🐦',
|
||||
username: '@mystore_official',
|
||||
caption: '🎉 친구 초대하고 함께 받는 특별한 혜택!\n\n맛있는 순간을 소중한 사람과 함께하세요.\n\n#친구초대이벤트 #함께할인 #소상공인응원',
|
||||
hashtags: ['친구초대이벤트', '함께할인', '소상공인응원'],
|
||||
stats: { retweets: '145', likes: '678', replies: '34' },
|
||||
recommended: false
|
||||
}
|
||||
];
|
||||
|
||||
// Render platforms
|
||||
function renderPlatforms() {
|
||||
platformGrid.innerHTML = platforms.map(platform => `
|
||||
<div class="platform-card ${platform.recommended ? 'platform-card--selected' : ''}" data-id="${platform.id}">
|
||||
<div class="platform-card__header">
|
||||
<div class="platform-card__info">
|
||||
<span class="platform-card__icon">${platform.icon}</span>
|
||||
<div>
|
||||
<h3 class="platform-card__name">${platform.name}</h3>
|
||||
${platform.recommended ? '<span style="font-size: 10px; color: var(--color-secondary);">✨ 추천</span>' : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="platform-card__toggle ${platform.recommended ? 'platform-card__toggle--active' : ''}" data-platform="${platform.id}"></div>
|
||||
</div>
|
||||
|
||||
<div class="platform-card__preview">
|
||||
<div class="post-preview">
|
||||
<div class="post-preview__header">
|
||||
<div class="post-preview__avatar">${platform.icon}</div>
|
||||
<div class="post-preview__username">${platform.username}</div>
|
||||
</div>
|
||||
<div class="post-preview__image">🎉</div>
|
||||
<div class="post-preview__content">
|
||||
<p class="post-preview__caption">${platform.caption}</p>
|
||||
${platform.hashtags.length > 0 ? `
|
||||
<div class="post-preview__hashtags">
|
||||
${platform.hashtags.map(tag => `<span class="hashtag">#${tag}</span>`).join(' ')}
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="post-preview__stats">
|
||||
${Object.entries(platform.stats).map(([key, value]) => `
|
||||
<span>${key}: ${value}</span>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="platform-card__actions">
|
||||
<button class="platform-card__action-btn" onclick="editPlatform('${platform.id}')">
|
||||
✏️ 수정
|
||||
</button>
|
||||
<button class="platform-card__action-btn" onclick="previewPlatform('${platform.id}')">
|
||||
👁️ 미리보기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// Add toggle event listeners
|
||||
const toggles = $$('.platform-card__toggle');
|
||||
toggles.forEach(toggle => {
|
||||
toggle.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const platformId = toggle.getAttribute('data-platform');
|
||||
togglePlatform(platformId, toggle);
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize selected platforms with recommended ones
|
||||
platforms.forEach(platform => {
|
||||
if (platform.recommended) {
|
||||
selectedPlatforms.add(platform.id);
|
||||
}
|
||||
});
|
||||
|
||||
updateNextButton();
|
||||
}
|
||||
|
||||
// Toggle platform
|
||||
function togglePlatform(platformId, toggleElement) {
|
||||
const card = toggleElement.closest('.platform-card');
|
||||
|
||||
if (selectedPlatforms.has(platformId)) {
|
||||
selectedPlatforms.delete(platformId);
|
||||
removeClass(toggleElement, 'platform-card__toggle--active');
|
||||
removeClass(card, 'platform-card--selected');
|
||||
} else {
|
||||
selectedPlatforms.add(platformId);
|
||||
addClass(toggleElement, 'platform-card__toggle--active');
|
||||
addClass(card, 'platform-card--selected');
|
||||
}
|
||||
|
||||
updateNextButton();
|
||||
}
|
||||
|
||||
// Update next button state
|
||||
function updateNextButton() {
|
||||
nextButton.disabled = selectedPlatforms.size === 0;
|
||||
}
|
||||
|
||||
// Edit platform
|
||||
window.editPlatform = function(platformId) {
|
||||
alert(`${platformId} 콘텐츠 편집 기능은 다음 버전에서 제공됩니다.`);
|
||||
};
|
||||
|
||||
// Preview platform
|
||||
window.previewPlatform = function(platformId) {
|
||||
alert(`${platformId} 미리보기 기능은 다음 버전에서 제공됩니다.`);
|
||||
};
|
||||
|
||||
// Generate all platforms
|
||||
generateAllButton.addEventListener('click', () => {
|
||||
// Select all platforms
|
||||
platforms.forEach(platform => {
|
||||
selectedPlatforms.add(platform.id);
|
||||
});
|
||||
|
||||
// Update all toggles
|
||||
const toggles = $$('.platform-card__toggle');
|
||||
toggles.forEach(toggle => {
|
||||
addClass(toggle, 'platform-card__toggle--active');
|
||||
const card = toggle.closest('.platform-card');
|
||||
addClass(card, 'platform-card--selected');
|
||||
});
|
||||
|
||||
updateNextButton();
|
||||
|
||||
// Show success message
|
||||
alert('✅ 모든 플랫폼의 콘텐츠가 생성되었습니다!');
|
||||
});
|
||||
|
||||
// Handle next button
|
||||
nextButton.addEventListener('click', () => {
|
||||
if (selectedPlatforms.size === 0) return;
|
||||
|
||||
// Save selected platforms and content
|
||||
const eventContent = storage.get('eventContent') || {};
|
||||
eventContent.snsContent = {
|
||||
selectedPlatforms: Array.from(selectedPlatforms),
|
||||
platforms: platforms.filter(p => selectedPlatforms.has(p.id)),
|
||||
createdAt: 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 = '12-QR포스터생성.html';
|
||||
}, 800);
|
||||
});
|
||||
|
||||
// Initialize
|
||||
window.addEventListener('load', () => {
|
||||
renderPlatforms();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,805 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>QR 포스터 생성 - KT 이벤트 마케팅</title>
|
||||
<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 */
|
||||
.qr-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
padding-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.qr-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);
|
||||
}
|
||||
|
||||
.qr-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;
|
||||
}
|
||||
|
||||
.qr-header__title {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.qr-header__subtitle {
|
||||
font-size: var(--font-size-m);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.qr-content {
|
||||
padding: var(--spacing-l) var(--spacing-m);
|
||||
}
|
||||
|
||||
/* Loading State */
|
||||
.loading-state {
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding: var(--spacing-2xl) 0;
|
||||
}
|
||||
|
||||
.loading-state--hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.loading-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: var(--spacing-l);
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: 0.7; transform: scale(0.95); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: var(--font-size-l);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
max-width: 300px;
|
||||
margin: var(--spacing-l) auto 0;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 8px;
|
||||
background: var(--color-gray-200);
|
||||
border-radius: var(--radius-full);
|
||||
overflow: hidden;
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-dark) 100%);
|
||||
border-radius: var(--radius-full);
|
||||
transition: width 0.3s ease-out;
|
||||
width: 0%;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* QR Results */
|
||||
.qr-results {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.qr-results--visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Info Card */
|
||||
.info-card {
|
||||
background: linear-gradient(135deg, #FFE9E9 0%, #FFFFFF 100%);
|
||||
border-left: 4px solid var(--color-secondary);
|
||||
padding: var(--spacing-m);
|
||||
border-radius: var(--radius-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.info-card__title {
|
||||
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-s);
|
||||
}
|
||||
|
||||
.info-card__text {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Template Selection */
|
||||
.template-section {
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: var(--font-size-l);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.template-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.template-card {
|
||||
background: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
border: 2px solid var(--color-border-light);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.template-card:hover {
|
||||
border-color: var(--color-secondary);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.template-card--selected {
|
||||
border-color: var(--color-secondary);
|
||||
background: var(--color-primary-light);
|
||||
}
|
||||
|
||||
.template-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.template-card__icon {
|
||||
font-size: var(--font-size-2xl);
|
||||
}
|
||||
|
||||
.template-card__title {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.template-card__description {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.template-card__preview {
|
||||
margin-top: var(--spacing-m);
|
||||
padding: var(--spacing-m);
|
||||
background: var(--color-gray-50);
|
||||
border-radius: var(--radius-m);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.qr-preview {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background: var(--color-white);
|
||||
border: 2px solid var(--color-border-light);
|
||||
border-radius: var(--radius-m);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto var(--spacing-s);
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.preview-text {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Customization Options */
|
||||
.customization-section {
|
||||
background: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.customization-section--visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.option-group {
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.option-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.option-label {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-s);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.size-options {
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.size-button {
|
||||
flex: 1;
|
||||
padding: var(--spacing-m);
|
||||
background: var(--color-white);
|
||||
border: 2px solid var(--color-border-light);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-primary);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.size-button:hover {
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.size-button--selected {
|
||||
border-color: var(--color-secondary);
|
||||
background: var(--color-primary-light);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.text-input {
|
||||
width: 100%;
|
||||
padding: var(--spacing-m);
|
||||
border: 2px solid var(--color-border-light);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: var(--font-size-m);
|
||||
font-family: inherit;
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.text-input:focus {
|
||||
outline: none;
|
||||
border-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) {
|
||||
.qr-header {
|
||||
padding: var(--spacing-2xl) var(--spacing-xl);
|
||||
}
|
||||
|
||||
.qr-content {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.template-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop */
|
||||
@media (min-width: 1024px) {
|
||||
.qr-content {
|
||||
max-width: 960px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="qr-container">
|
||||
<!-- Header -->
|
||||
<header class="qr-header">
|
||||
<button class="qr-header__back" onclick="history.back()">
|
||||
←
|
||||
</button>
|
||||
<h1 class="qr-header__title">QR 포스터 생성</h1>
|
||||
<p class="qr-header__subtitle">이벤트 QR 코드와 포스터를 자동으로 생성해 드려요</p>
|
||||
</header>
|
||||
|
||||
<!-- Content -->
|
||||
<main class="qr-content">
|
||||
<!-- Loading State -->
|
||||
<div class="loading-state" id="loadingState">
|
||||
<div class="loading-icon">🎨</div>
|
||||
<div class="loading-text" id="loadingText">AI가 QR 포스터를 생성하고 있습니다...</div>
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progressFill"></div>
|
||||
</div>
|
||||
<div class="progress-text" id="progressText">0%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- QR Results -->
|
||||
<div class="qr-results" id="qrResults">
|
||||
<!-- Info Card -->
|
||||
<div class="info-card">
|
||||
<h2 class="info-card__title">
|
||||
✨ QR 포스터 완성
|
||||
</h2>
|
||||
<p class="info-card__text">
|
||||
이벤트 참여를 위한 QR 코드와 홍보 포스터가 생성되었습니다.
|
||||
원하시는 템플릿을 선택하고 커스터마이징하세요.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Template Selection -->
|
||||
<div class="template-section">
|
||||
<h2 class="section-title">포스터 템플릿 선택</h2>
|
||||
<div class="template-grid">
|
||||
<div class="template-card" data-template="modern" id="modernTemplate">
|
||||
<div class="template-card__header">
|
||||
<div class="template-card__icon">🎯</div>
|
||||
<h3 class="template-card__title">모던 스타일</h3>
|
||||
</div>
|
||||
<p class="template-card__description">
|
||||
깔끔하고 현대적인 디자인으로 모든 연령층에게 어필할 수 있는 템플릿입니다.
|
||||
</p>
|
||||
<div class="template-card__preview">
|
||||
<div class="qr-preview">📱</div>
|
||||
<div class="preview-text">모던 스타일 포스터</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="template-card" data-template="colorful" id="colorfulTemplate">
|
||||
<div class="template-card__header">
|
||||
<div class="template-card__icon">🌈</div>
|
||||
<h3 class="template-card__title">컬러풀 스타일</h3>
|
||||
</div>
|
||||
<p class="template-card__description">
|
||||
화려한 색상과 역동적인 디자인으로 시선을 사로잡는 템플릿입니다.
|
||||
</p>
|
||||
<div class="template-card__preview">
|
||||
<div class="qr-preview">🎨</div>
|
||||
<div class="preview-text">컬러풀 스타일 포스터</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="template-card" data-template="minimal" id="minimalTemplate">
|
||||
<div class="template-card__header">
|
||||
<div class="template-card__icon">⚪</div>
|
||||
<h3 class="template-card__title">미니멀 스타일</h3>
|
||||
</div>
|
||||
<p class="template-card__description">
|
||||
단순하고 세련된 디자인으로 전문적인 느낌을 전달하는 템플릿입니다.
|
||||
</p>
|
||||
<div class="template-card__preview">
|
||||
<div class="qr-preview">◻️</div>
|
||||
<div class="preview-text">미니멀 스타일 포스터</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="template-card" data-template="premium" id="premiumTemplate">
|
||||
<div class="template-card__header">
|
||||
<div class="template-card__icon">⭐</div>
|
||||
<h3 class="template-card__title">프리미엄 스타일</h3>
|
||||
</div>
|
||||
<p class="template-card__description">
|
||||
고급스러운 디자인으로 특별한 이벤트를 더욱 돋보이게 하는 템플릿입니다.
|
||||
</p>
|
||||
<div class="template-card__preview">
|
||||
<div class="qr-preview">💎</div>
|
||||
<div class="preview-text">프리미엄 스타일 포스터</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Customization Options -->
|
||||
<div class="customization-section" id="customizationSection">
|
||||
<h2 class="section-title">포스터 커스터마이징</h2>
|
||||
|
||||
<div class="option-group">
|
||||
<label class="option-label">포스터 크기</label>
|
||||
<div class="size-options">
|
||||
<button class="size-button size-button--selected" data-size="A4" id="sizeA4">A4</button>
|
||||
<button class="size-button" data-size="A3" id="sizeA3">A3</button>
|
||||
<button class="size-button" data-size="B4" id="sizeB4">B4</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option-group">
|
||||
<label class="option-label" for="posterTitle">포스터 제목 (선택사항)</label>
|
||||
<input
|
||||
type="text"
|
||||
id="posterTitle"
|
||||
class="text-input"
|
||||
placeholder="예: 신규 고객 환영 이벤트"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="option-group">
|
||||
<label class="option-label" for="posterSubtitle">부제목 (선택사항)</label>
|
||||
<input
|
||||
type="text"
|
||||
id="posterSubtitle"
|
||||
class="text-input"
|
||||
placeholder="예: QR 코드를 스캔하고 특별 혜택을 받으세요"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Next Button -->
|
||||
<button class="next-button" id="nextButton" disabled>
|
||||
선택 완료
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Utility functions
|
||||
const $ = (selector) => document.querySelector(selector);
|
||||
const $$ = (selector) => document.querySelectorAll(selector);
|
||||
|
||||
const addClass = (el, className) => el?.classList.add(className);
|
||||
const removeClass = (el, className) => el?.classList.remove(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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Elements
|
||||
const loadingState = $('#loadingState');
|
||||
const loadingText = $('#loadingText');
|
||||
const progressFill = $('#progressFill');
|
||||
const progressText = $('#progressText');
|
||||
const qrResults = $('#qrResults');
|
||||
const modernTemplate = $('#modernTemplate');
|
||||
const colorfulTemplate = $('#colorfulTemplate');
|
||||
const minimalTemplate = $('#minimalTemplate');
|
||||
const premiumTemplate = $('#premiumTemplate');
|
||||
const customizationSection = $('#customizationSection');
|
||||
const sizeA4 = $('#sizeA4');
|
||||
const sizeA3 = $('#sizeA3');
|
||||
const sizeB4 = $('#sizeB4');
|
||||
const posterTitle = $('#posterTitle');
|
||||
const posterSubtitle = $('#posterSubtitle');
|
||||
const nextButton = $('#nextButton');
|
||||
|
||||
// State
|
||||
let selectedTemplate = null;
|
||||
let selectedSize = 'A4';
|
||||
let currentProgress = 0;
|
||||
|
||||
// Templates
|
||||
const templates = [modernTemplate, colorfulTemplate, minimalTemplate, premiumTemplate];
|
||||
const sizeButtons = [sizeA4, sizeA3, sizeB4];
|
||||
|
||||
// Simulate QR generation
|
||||
function updateProgress(progress) {
|
||||
progressFill.style.width = progress + '%';
|
||||
progressText.textContent = progress + '%';
|
||||
}
|
||||
|
||||
function showResults() {
|
||||
addClass(loadingState, 'loading-state--hidden');
|
||||
addClass(qrResults, 'qr-results--visible');
|
||||
}
|
||||
|
||||
function simulateGeneration() {
|
||||
const steps = [
|
||||
{ progress: 0, text: 'QR 코드 생성 중...' },
|
||||
{ progress: 25, text: '포스터 레이아웃 설계 중...' },
|
||||
{ progress: 50, text: '디자인 템플릿 적용 중...' },
|
||||
{ progress: 75, text: '최적화 진행 중...' },
|
||||
{ progress: 100, text: 'QR 포스터 생성 완료!' }
|
||||
];
|
||||
|
||||
let stepIndex = 0;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (stepIndex >= steps.length) {
|
||||
clearInterval(interval);
|
||||
setTimeout(showResults, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
const step = steps[stepIndex];
|
||||
loadingText.textContent = step.text;
|
||||
updateProgress(step.progress);
|
||||
stepIndex++;
|
||||
}, 600);
|
||||
}
|
||||
|
||||
// Template selection
|
||||
templates.forEach(template => {
|
||||
template.addEventListener('click', () => {
|
||||
const templateType = template.getAttribute('data-template');
|
||||
selectTemplate(templateType, template);
|
||||
});
|
||||
});
|
||||
|
||||
function selectTemplate(type, element) {
|
||||
// Deselect all
|
||||
templates.forEach(tpl => {
|
||||
removeClass(tpl, 'template-card--selected');
|
||||
});
|
||||
|
||||
// Select clicked
|
||||
addClass(element, 'template-card--selected');
|
||||
selectedTemplate = type;
|
||||
|
||||
// Show customization options
|
||||
addClass(customizationSection, 'customization-section--visible');
|
||||
nextButton.disabled = false;
|
||||
}
|
||||
|
||||
// Size selection
|
||||
sizeButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
const size = button.getAttribute('data-size');
|
||||
selectSize(size, button);
|
||||
});
|
||||
});
|
||||
|
||||
function selectSize(size, element) {
|
||||
// Deselect all
|
||||
sizeButtons.forEach(btn => {
|
||||
removeClass(btn, 'size-button--selected');
|
||||
});
|
||||
|
||||
// Select clicked
|
||||
addClass(element, 'size-button--selected');
|
||||
selectedSize = size;
|
||||
}
|
||||
|
||||
// Next button
|
||||
nextButton.addEventListener('click', () => {
|
||||
if (!selectedTemplate) return;
|
||||
|
||||
// Save QR poster data
|
||||
const eventContent = storage.get('eventContent') || {};
|
||||
eventContent.qrPoster = {
|
||||
template: selectedTemplate,
|
||||
size: selectedSize,
|
||||
title: posterTitle.value || '',
|
||||
subtitle: posterSubtitle.value || '',
|
||||
createdAt: 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 = '13-콘텐츠편집.html';
|
||||
}, 800);
|
||||
});
|
||||
|
||||
// Start generation on load
|
||||
window.addEventListener('load', () => {
|
||||
setTimeout(simulateGeneration, 500);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,561 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>QR 포스터 템플릿 선택 - KT 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<style>
|
||||
/* Layout */
|
||||
.poster-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
padding-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.poster-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);
|
||||
}
|
||||
|
||||
.poster-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;
|
||||
}
|
||||
|
||||
.poster-header__title {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.poster-header__subtitle {
|
||||
font-size: var(--font-size-m);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.poster-content {
|
||||
padding: var(--spacing-l) var(--spacing-m);
|
||||
}
|
||||
|
||||
/* Size Selection */
|
||||
.size-section {
|
||||
background: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.size-section__label {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-m);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.size-chips {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.size-chip {
|
||||
padding: var(--spacing-m);
|
||||
border: 2px solid var(--color-border-light);
|
||||
border-radius: var(--radius-m);
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.size-chip:hover {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.size-chip--selected {
|
||||
border-color: var(--color-primary);
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
}
|
||||
|
||||
.size-chip__name {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.size-chip__dimensions {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Template Grid */
|
||||
.template-grid {
|
||||
display: grid;
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
/* Template Card */
|
||||
.template-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;
|
||||
}
|
||||
|
||||
.template-card:hover {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.template-card--selected {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.template-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;
|
||||
}
|
||||
|
||||
.template-card__radio {
|
||||
position: absolute;
|
||||
top: var(--spacing-s);
|
||||
left: var(--spacing-s);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.template-card__preview {
|
||||
aspect-ratio: 3 / 4;
|
||||
background: linear-gradient(135deg, #F8F9FF 0%, #FFFFFF 100%);
|
||||
padding: var(--spacing-l);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-m);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.template-preview__header {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.template-preview__title {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.template-preview__subtitle {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.template-preview__qr {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background: var(--color-white);
|
||||
border: 2px solid var(--color-border-light);
|
||||
border-radius: var(--radius-m);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.template-preview__footer {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
padding: var(--spacing-s);
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
border-radius: var(--radius-m);
|
||||
}
|
||||
|
||||
.template-preview__cta {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.template-card__info {
|
||||
padding: var(--spacing-m);
|
||||
background: var(--color-white);
|
||||
}
|
||||
|
||||
.template-card__name {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.template-card__description {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.5;
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.template-card__features {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.feature-tag {
|
||||
background: var(--color-primary-light);
|
||||
color: var(--color-primary);
|
||||
padding: 2px var(--spacing-s);
|
||||
border-radius: var(--radius-s);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
.poster-header {
|
||||
padding: var(--spacing-2xl) var(--spacing-xl);
|
||||
}
|
||||
|
||||
.poster-content {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.template-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.size-chips {
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop */
|
||||
@media (min-width: 1024px) {
|
||||
.poster-content {
|
||||
max-width: 960px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="poster-container">
|
||||
<!-- Header -->
|
||||
<header class="poster-header">
|
||||
<button class="poster-header__back" onclick="history.back()">
|
||||
←
|
||||
</button>
|
||||
<h1 class="poster-header__title">QR 포스터 생성</h1>
|
||||
<p class="poster-header__subtitle">오프라인 홍보용 QR 포스터를 만들어 보세요</p>
|
||||
</header>
|
||||
|
||||
<!-- Content -->
|
||||
<main class="poster-content">
|
||||
<!-- Size Selection -->
|
||||
<div class="size-section">
|
||||
<h2 class="size-section__label">
|
||||
📐 포스터 크기 선택
|
||||
</h2>
|
||||
<div class="size-chips">
|
||||
<div class="size-chip size-chip--selected" data-size="A4">
|
||||
<div class="size-chip__name">A4</div>
|
||||
<div class="size-chip__dimensions">210×297mm</div>
|
||||
</div>
|
||||
<div class="size-chip" data-size="A3">
|
||||
<div class="size-chip__name">A3</div>
|
||||
<div class="size-chip__dimensions">297×420mm</div>
|
||||
</div>
|
||||
<div class="size-chip" data-size="A2">
|
||||
<div class="size-chip__name">A2</div>
|
||||
<div class="size-chip__dimensions">420×594mm</div>
|
||||
</div>
|
||||
<div class="size-chip" data-size="B4">
|
||||
<div class="size-chip__name">B4</div>
|
||||
<div class="size-chip__dimensions">257×364mm</div>
|
||||
</div>
|
||||
<div class="size-chip" data-size="B3">
|
||||
<div class="size-chip__name">B3</div>
|
||||
<div class="size-chip__dimensions">364×515mm</div>
|
||||
</div>
|
||||
<div class="size-chip" data-size="custom">
|
||||
<div class="size-chip__name">사용자정의</div>
|
||||
<div class="size-chip__dimensions">직접 입력</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Template Grid -->
|
||||
<div class="template-grid" id="templateGrid">
|
||||
<!-- Template 1 - Classic -->
|
||||
<div class="template-card template-card--selected" data-template="classic">
|
||||
<div class="template-card__badge">🤖 AI 추천</div>
|
||||
<input type="radio" name="template" value="classic" class="template-card__radio" checked>
|
||||
<div class="template-card__preview">
|
||||
<div class="template-preview__header">
|
||||
<h3 class="template-preview__title">🎉 친구 초대 이벤트</h3>
|
||||
<p class="template-preview__subtitle">함께하면 더 큰 혜택!</p>
|
||||
</div>
|
||||
<div class="template-preview__qr">📱</div>
|
||||
<div class="template-preview__footer">
|
||||
<p class="template-preview__cta">QR 코드를 스캔하고 참여하세요!</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="template-card__info">
|
||||
<h4 class="template-card__name">클래식 템플릿</h4>
|
||||
<p class="template-card__description">
|
||||
깔끔하고 전통적인 레이아웃. 모든 업종에 잘 어울리는 기본 템플릿입니다.
|
||||
</p>
|
||||
<div class="template-card__features">
|
||||
<span class="feature-tag">심플</span>
|
||||
<span class="feature-tag">범용</span>
|
||||
<span class="feature-tag">가독성 높음</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Template 2 - Modern -->
|
||||
<div class="template-card" data-template="modern">
|
||||
<input type="radio" name="template" value="modern" class="template-card__radio">
|
||||
<div class="template-card__preview" style="background: linear-gradient(135deg, #667EEA 0%, #764BA2 100%); color: white;">
|
||||
<div class="template-preview__header">
|
||||
<h3 class="template-preview__title" style="color: white;">🎉 친구 초대 이벤트</h3>
|
||||
<p class="template-preview__subtitle" style="color: rgba(255,255,255,0.9);">함께하면 더 큰 혜택!</p>
|
||||
</div>
|
||||
<div class="template-preview__qr" style="background: white;">📱</div>
|
||||
<div class="template-preview__footer" style="background: rgba(255,255,255,0.2);">
|
||||
<p class="template-preview__cta" style="color: white;">QR 코드를 스캔하고 참여하세요!</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="template-card__info">
|
||||
<h4 class="template-card__name">모던 템플릿</h4>
|
||||
<p class="template-card__description">
|
||||
그라데이션 배경과 세련된 타이포그래피로 젊은 층에게 어필합니다.
|
||||
</p>
|
||||
<div class="template-card__features">
|
||||
<span class="feature-tag">트렌디</span>
|
||||
<span class="feature-tag">그라데이션</span>
|
||||
<span class="feature-tag">젊은 층</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Template 3 - Minimal -->
|
||||
<div class="template-card" data-template="minimal">
|
||||
<input type="radio" name="template" value="minimal" class="template-card__radio">
|
||||
<div class="template-card__preview" style="background: white; border: 1px solid #e5e7eb;">
|
||||
<div class="template-preview__header">
|
||||
<h3 class="template-preview__title" style="font-size: 18px;">친구 초대 이벤트</h3>
|
||||
<p class="template-preview__subtitle">함께하면 더 큰 혜택</p>
|
||||
</div>
|
||||
<div class="template-preview__qr" style="border-color: #000;">📱</div>
|
||||
<div class="template-preview__footer" style="background: white; border-top: 1px solid #e5e7eb;">
|
||||
<p class="template-preview__cta" style="color: #000; font-size: 14px;">스캔하고 참여하세요</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="template-card__info">
|
||||
<h4 class="template-card__name">미니멀 템플릿</h4>
|
||||
<p class="template-card__description">
|
||||
여백을 활용한 미니멀한 디자인. 고급스러운 브랜드 이미지에 적합합니다.
|
||||
</p>
|
||||
<div class="template-card__features">
|
||||
<span class="feature-tag">미니멀</span>
|
||||
<span class="feature-tag">고급</span>
|
||||
<span class="feature-tag">여백 활용</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Template 4 - Vibrant -->
|
||||
<div class="template-card" data-template="vibrant">
|
||||
<input type="radio" name="template" value="vibrant" class="template-card__radio">
|
||||
<div class="template-card__preview" style="background: linear-gradient(135deg, #FA709A 0%, #FEE140 100%); color: white;">
|
||||
<div class="template-preview__header">
|
||||
<h3 class="template-preview__title" style="color: white; text-shadow: 0 2px 4px rgba(0,0,0,0.2);">🎉 친구 초대 이벤트</h3>
|
||||
<p class="template-preview__subtitle" style="color: white;">함께하면 더 큰 혜택!</p>
|
||||
</div>
|
||||
<div class="template-preview__qr" style="background: white; border: 4px solid white;">📱</div>
|
||||
<div class="template-preview__footer" style="background: rgba(255,255,255,0.3);">
|
||||
<p class="template-preview__cta" style="color: white; font-weight: bold;">QR 코드를 스캔하고 참여하세요!</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="template-card__info">
|
||||
<h4 class="template-card__name">생동감 템플릿</h4>
|
||||
<p class="template-card__description">
|
||||
화사한 컬러와 역동적인 레이아웃으로 시선을 사로잡습니다.
|
||||
</p>
|
||||
<div class="template-card__features">
|
||||
<span class="feature-tag">생동감</span>
|
||||
<span class="feature-tag">눈에 띄는</span>
|
||||
<span class="feature-tag">이벤트용</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Next Button -->
|
||||
<button class="next-button" id="nextButton">
|
||||
상세 설정하기
|
||||
</button>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/common.js"></script>
|
||||
<script>
|
||||
const { $, addClass, removeClass, storage } = window.CommonUtils;
|
||||
|
||||
// Elements
|
||||
const sizeChips = document.querySelectorAll('.size-chip');
|
||||
const templateCards = document.querySelectorAll('.template-card');
|
||||
const templateRadios = document.querySelectorAll('input[name="template"]');
|
||||
const nextButton = $('#nextButton');
|
||||
|
||||
// State
|
||||
let selectedSize = 'A4';
|
||||
let selectedTemplate = 'classic';
|
||||
|
||||
// Handle size selection
|
||||
sizeChips.forEach(chip => {
|
||||
chip.addEventListener('click', () => {
|
||||
sizeChips.forEach(c => removeClass(c, 'size-chip--selected'));
|
||||
addClass(chip, 'size-chip--selected');
|
||||
selectedSize = chip.getAttribute('data-size');
|
||||
});
|
||||
});
|
||||
|
||||
// Handle template selection
|
||||
templateCards.forEach(card => {
|
||||
card.addEventListener('click', (e) => {
|
||||
// Don't trigger if clicking radio directly
|
||||
if (e.target.type === 'radio') return;
|
||||
|
||||
const template = card.getAttribute('data-template');
|
||||
selectTemplate(template, card);
|
||||
});
|
||||
});
|
||||
|
||||
templateRadios.forEach(radio => {
|
||||
radio.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const template = radio.value;
|
||||
const card = radio.closest('.template-card');
|
||||
selectTemplate(template, card);
|
||||
});
|
||||
});
|
||||
|
||||
function selectTemplate(template, card) {
|
||||
templateCards.forEach(c => removeClass(c, 'template-card--selected'));
|
||||
addClass(card, 'template-card--selected');
|
||||
|
||||
const radio = card.querySelector('input[type="radio"]');
|
||||
radio.checked = true;
|
||||
|
||||
selectedTemplate = template;
|
||||
}
|
||||
|
||||
// Handle next button
|
||||
nextButton.addEventListener('click', () => {
|
||||
// Save selections
|
||||
const eventContent = storage.get('eventContent') || {};
|
||||
eventContent.qrPoster = {
|
||||
size: selectedSize,
|
||||
template: selectedTemplate,
|
||||
selectedAt: 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 = '13-QR포스터상세설정.html';
|
||||
}, 800);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,628 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>QR 포스터 상세 설정 - KT 이벤트 마케팅</title>
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<style>
|
||||
/* Layout */
|
||||
.detail-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #F8F9FF 0%, #FFFFFF 100%);
|
||||
padding-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.detail-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);
|
||||
}
|
||||
|
||||
.detail-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;
|
||||
}
|
||||
|
||||
.detail-header__title {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.detail-header__subtitle {
|
||||
font-size: var(--font-size-m);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.detail-content {
|
||||
padding: var(--spacing-l) var(--spacing-m);
|
||||
display: grid;
|
||||
gap: var(--spacing-l);
|
||||
}
|
||||
|
||||
/* Preview Section */
|
||||
.preview-section {
|
||||
background: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-l);
|
||||
box-shadow: var(--shadow-md);
|
||||
position: sticky;
|
||||
top: var(--spacing-m);
|
||||
}
|
||||
|
||||
.preview-section__label {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.poster-preview {
|
||||
aspect-ratio: 3 / 4;
|
||||
background: linear-gradient(135deg, #F8F9FF 0%, #FFFFFF 100%);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-xl);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.poster-preview__header {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.poster-preview__title {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-s);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.poster-preview__subtitle {
|
||||
font-size: var(--font-size-m);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.poster-preview__qr {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
background: var(--color-white);
|
||||
border: 2px solid var(--color-border-light);
|
||||
border-radius: var(--radius-m);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 64px;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.poster-preview__footer {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
padding: var(--spacing-m);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: var(--radius-m);
|
||||
}
|
||||
|
||||
.poster-preview__cta {
|
||||
font-size: var(--font-size-l);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-primary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.poster-preview__url {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Form Sections */
|
||||
.form-section {
|
||||
background: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.form-section__title {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-m);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.form-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-group__label {
|
||||
display: block;
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.form-group__input,
|
||||
.form-group__textarea {
|
||||
width: 100%;
|
||||
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);
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.form-group__textarea {
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.form-group__input:focus,
|
||||
.form-group__textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-secondary);
|
||||
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1);
|
||||
}
|
||||
|
||||
.form-group__hint {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-tertiary);
|
||||
margin-top: var(--spacing-xs);
|
||||
}
|
||||
|
||||
/* Color Picker */
|
||||
.color-picker {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.color-option {
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
border: 2px solid var(--color-border-light);
|
||||
border-radius: var(--radius-m);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-normal);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.color-option:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.color-option--selected {
|
||||
border-color: var(--color-secondary);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.color-option--selected::after {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: var(--color-white);
|
||||
font-weight: var(--font-weight-bold);
|
||||
font-size: var(--font-size-l);
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Action Buttons */
|
||||
.action-buttons {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.action-button {
|
||||
padding: var(--spacing-m);
|
||||
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);
|
||||
}
|
||||
|
||||
.action-button--preview {
|
||||
background: var(--color-white);
|
||||
border: 2px solid var(--color-border-light);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.action-button--preview:hover {
|
||||
border-color: var(--color-secondary);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.action-button--generate {
|
||||
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-dark) 100%);
|
||||
color: var(--color-white);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.action-button--generate:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.action-button--loading {
|
||||
position: relative;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.action-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) {
|
||||
.detail-header {
|
||||
padding: var(--spacing-2xl) var(--spacing-xl);
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl);
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.preview-section {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.form-sections {
|
||||
order: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop */
|
||||
@media (min-width: 1024px) {
|
||||
.detail-content {
|
||||
grid-template-columns: 2fr 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="detail-container">
|
||||
<!-- Header -->
|
||||
<header class="detail-header">
|
||||
<button class="detail-header__back" onclick="history.back()">
|
||||
←
|
||||
</button>
|
||||
<h1 class="detail-header__title">QR 포스터 상세 설정</h1>
|
||||
<p class="detail-header__subtitle">포스터 내용을 상세하게 편집하세요</p>
|
||||
</header>
|
||||
|
||||
<!-- Content -->
|
||||
<main class="detail-content">
|
||||
<!-- Form Sections -->
|
||||
<div class="form-sections">
|
||||
<!-- Text Content -->
|
||||
<div class="form-section">
|
||||
<h2 class="form-section__title">
|
||||
✍️ 텍스트 내용
|
||||
</h2>
|
||||
<div class="form-group">
|
||||
<label class="form-group__label">제목</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-group__input"
|
||||
id="titleInput"
|
||||
value="🎉 친구 초대 이벤트"
|
||||
maxlength="50"
|
||||
>
|
||||
<p class="form-group__hint">포스터 상단에 표시될 제목입니다</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-group__label">부제목</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-group__input"
|
||||
id="subtitleInput"
|
||||
value="함께하면 더 큰 혜택!"
|
||||
maxlength="100"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-group__label">행동 유도 문구</label>
|
||||
<textarea
|
||||
class="form-group__textarea"
|
||||
id="ctaInput"
|
||||
maxlength="200"
|
||||
>QR 코드를 스캔하고 지금 바로 참여하세요!</textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-group__label">이벤트 URL (선택)</label>
|
||||
<input
|
||||
type="url"
|
||||
class="form-group__input"
|
||||
id="urlInput"
|
||||
placeholder="https://event.mystore.com"
|
||||
>
|
||||
<p class="form-group__hint">QR 코드로 연결될 URL을 입력하세요</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Design Settings -->
|
||||
<div class="form-section">
|
||||
<h2 class="form-section__title">
|
||||
🎨 디자인 설정
|
||||
</h2>
|
||||
<div class="form-group">
|
||||
<label class="form-group__label">배경 색상</label>
|
||||
<div class="color-picker">
|
||||
<div class="color-option color-option--selected" data-color="#F8F9FF" style="background: #F8F9FF;"></div>
|
||||
<div class="color-option" data-color="#FFFFFF" style="background: #FFFFFF;"></div>
|
||||
<div class="color-option" data-color="#667EEA" style="background: linear-gradient(135deg, #667EEA 0%, #764BA2 100%);"></div>
|
||||
<div class="color-option" data-color="#FA709A" style="background: linear-gradient(135deg, #FA709A 0%, #FEE140 100%);"></div>
|
||||
<div class="color-option" data-color="#FF6B6B" style="background: #FF6B6B;"></div>
|
||||
<div class="color-option" data-color="#4ECDC4" style="background: #4ECDC4;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-group__label">텍스트 색상</label>
|
||||
<div class="color-picker">
|
||||
<div class="color-option color-option--selected" data-color-text="#1A202C" style="background: #1A202C;"></div>
|
||||
<div class="color-option" data-color-text="#FFFFFF" style="background: #FFFFFF;"></div>
|
||||
<div class="color-option" data-color-text="#2D3748" style="background: #2D3748;"></div>
|
||||
<div class="color-option" data-color-text="#4A5568" style="background: #4A5568;"></div>
|
||||
<div class="color-option" data-color-text="#E31E24" style="background: #E31E24;"></div>
|
||||
<div class="color-option" data-color-text="#0066FF" style="background: #0066FF;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-group__label">QR 코드 크기</label>
|
||||
<input
|
||||
type="range"
|
||||
class="form-group__input"
|
||||
id="qrSizeInput"
|
||||
min="120"
|
||||
max="240"
|
||||
value="160"
|
||||
step="20"
|
||||
>
|
||||
<p class="form-group__hint" id="qrSizeValue">160px</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Info -->
|
||||
<div class="form-section">
|
||||
<h2 class="form-section__title">
|
||||
ℹ️ 추가 정보
|
||||
</h2>
|
||||
<div class="form-group">
|
||||
<label class="form-group__label">이벤트 기간</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-group__input"
|
||||
id="periodInput"
|
||||
placeholder="예: 2025.01.01 ~ 2025.01.31"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-group__label">매장 정보</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-group__input"
|
||||
id="storeInfoInput"
|
||||
placeholder="예: 강남점 | 02-1234-5678"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-group__label">주의사항 (선택)</label>
|
||||
<textarea
|
||||
class="form-group__textarea"
|
||||
id="noticeInput"
|
||||
placeholder="예: 1인 1회 참여 가능"
|
||||
maxlength="200"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="action-buttons">
|
||||
<button class="action-button action-button--preview" id="previewButton">
|
||||
👁️ 미리보기
|
||||
</button>
|
||||
<button class="action-button action-button--generate" id="generateButton">
|
||||
✅ 포스터 생성하기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preview Section -->
|
||||
<div class="preview-section">
|
||||
<h3 class="preview-section__label">📱 실시간 미리보기</h3>
|
||||
<div class="poster-preview" id="posterPreview">
|
||||
<div class="poster-preview__header">
|
||||
<h1 class="poster-preview__title" id="previewTitle">🎉 친구 초대 이벤트</h1>
|
||||
<p class="poster-preview__subtitle" id="previewSubtitle">함께하면 더 큰 혜택!</p>
|
||||
</div>
|
||||
<div class="poster-preview__qr" id="previewQR">📱</div>
|
||||
<div class="poster-preview__footer">
|
||||
<p class="poster-preview__cta" id="previewCTA">QR 코드를 스캔하고 지금 바로 참여하세요!</p>
|
||||
<p class="poster-preview__url" id="previewURL"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/common.js"></script>
|
||||
<script>
|
||||
const { $, addClass, removeClass, storage } = window.CommonUtils;
|
||||
|
||||
// Elements
|
||||
const titleInput = $('#titleInput');
|
||||
const subtitleInput = $('#subtitleInput');
|
||||
const ctaInput = $('#ctaInput');
|
||||
const urlInput = $('#urlInput');
|
||||
const qrSizeInput = $('#qrSizeInput');
|
||||
const qrSizeValue = $('#qrSizeValue');
|
||||
const periodInput = $('#periodInput');
|
||||
const storeInfoInput = $('#storeInfoInput');
|
||||
const noticeInput = $('#noticeInput');
|
||||
const colorOptions = document.querySelectorAll('[data-color]');
|
||||
const colorTextOptions = document.querySelectorAll('[data-color-text]');
|
||||
const previewButton = $('#previewButton');
|
||||
const generateButton = $('#generateButton');
|
||||
|
||||
// Preview elements
|
||||
const posterPreview = $('#posterPreview');
|
||||
const previewTitle = $('#previewTitle');
|
||||
const previewSubtitle = $('#previewSubtitle');
|
||||
const previewCTA = $('#previewCTA');
|
||||
const previewURL = $('#previewURL');
|
||||
const previewQR = $('#previewQR');
|
||||
|
||||
// State
|
||||
let selectedBgColor = '#F8F9FF';
|
||||
let selectedTextColor = '#1A202C';
|
||||
let qrSize = 160;
|
||||
|
||||
// Update preview in real-time
|
||||
function updatePreview() {
|
||||
previewTitle.textContent = titleInput.value || '제목을 입력하세요';
|
||||
previewSubtitle.textContent = subtitleInput.value || '부제목을 입력하세요';
|
||||
previewCTA.textContent = ctaInput.value || 'CTA를 입력하세요';
|
||||
previewURL.textContent = urlInput.value || '';
|
||||
|
||||
// Update QR size
|
||||
previewQR.style.width = qrSize + 'px';
|
||||
previewQR.style.height = qrSize + 'px';
|
||||
|
||||
// Update colors
|
||||
posterPreview.style.background = selectedBgColor;
|
||||
previewTitle.style.color = selectedTextColor;
|
||||
previewSubtitle.style.color = selectedTextColor;
|
||||
previewCTA.style.color = selectedTextColor;
|
||||
}
|
||||
|
||||
// Input listeners
|
||||
titleInput.addEventListener('input', updatePreview);
|
||||
subtitleInput.addEventListener('input', updatePreview);
|
||||
ctaInput.addEventListener('input', updatePreview);
|
||||
urlInput.addEventListener('input', updatePreview);
|
||||
|
||||
// QR size slider
|
||||
qrSizeInput.addEventListener('input', (e) => {
|
||||
qrSize = parseInt(e.target.value);
|
||||
qrSizeValue.textContent = qrSize + 'px';
|
||||
updatePreview();
|
||||
});
|
||||
|
||||
// Color selection
|
||||
colorOptions.forEach(option => {
|
||||
option.addEventListener('click', () => {
|
||||
colorOptions.forEach(o => removeClass(o, 'color-option--selected'));
|
||||
addClass(option, 'color-option--selected');
|
||||
selectedBgColor = option.getAttribute('data-color');
|
||||
updatePreview();
|
||||
});
|
||||
});
|
||||
|
||||
colorTextOptions.forEach(option => {
|
||||
option.addEventListener('click', () => {
|
||||
colorTextOptions.forEach(o => removeClass(o, 'color-option--selected'));
|
||||
addClass(option, 'color-option--selected');
|
||||
selectedTextColor = option.getAttribute('data-color-text');
|
||||
updatePreview();
|
||||
});
|
||||
});
|
||||
|
||||
// Preview button
|
||||
previewButton.addEventListener('click', () => {
|
||||
alert('전체 화면 미리보기 기능은 다음 버전에서 제공됩니다.');
|
||||
});
|
||||
|
||||
// Generate button
|
||||
generateButton.addEventListener('click', () => {
|
||||
// Collect all settings
|
||||
const posterSettings = {
|
||||
title: titleInput.value,
|
||||
subtitle: subtitleInput.value,
|
||||
cta: ctaInput.value,
|
||||
url: urlInput.value,
|
||||
bgColor: selectedBgColor,
|
||||
textColor: selectedTextColor,
|
||||
qrSize: qrSize,
|
||||
period: periodInput.value,
|
||||
storeInfo: storeInfoInput.value,
|
||||
notice: noticeInput.value
|
||||
};
|
||||
|
||||
// Save to storage
|
||||
const eventContent = storage.get('eventContent') || {};
|
||||
eventContent.qrPoster = {
|
||||
...eventContent.qrPoster,
|
||||
settings: posterSettings,
|
||||
generatedAt: new Date().toISOString()
|
||||
};
|
||||
storage.set('eventContent', eventContent);
|
||||
|
||||
// Show loading
|
||||
addClass(generateButton, 'action-button--loading');
|
||||
generateButton.disabled = true;
|
||||
|
||||
// Navigate to next screen
|
||||
setTimeout(() => {
|
||||
window.location.href = '14-콘텐츠최종승인.html';
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Load saved data
|
||||
window.addEventListener('load', () => {
|
||||
const storeData = storage.get('storeData');
|
||||
if (storeData && storeData.storeName) {
|
||||
storeInfoInput.value = storeData.storeName;
|
||||
}
|
||||
|
||||
updatePreview();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,699 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>콘텐츠 편집 - KT 이벤트 마케팅</title>
|
||||
<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;
|
||||
}
|
||||
|
||||
textarea {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
.edit-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
padding-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.edit-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);
|
||||
}
|
||||
|
||||
.edit-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;
|
||||
}
|
||||
|
||||
.edit-header__title {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.edit-header__subtitle {
|
||||
font-size: var(--font-size-m);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.edit-content {
|
||||
padding: var(--spacing-l) var(--spacing-m);
|
||||
}
|
||||
|
||||
/* Info Card */
|
||||
.info-card {
|
||||
background: linear-gradient(135deg, #FFF9E6 0%, #FFFFFF 100%);
|
||||
border-left: 4px solid #FFB800;
|
||||
padding: var(--spacing-m);
|
||||
border-radius: var(--radius-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.info-card__title {
|
||||
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-s);
|
||||
}
|
||||
|
||||
.info-card__text {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Edit Section */
|
||||
.edit-section {
|
||||
background: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-l);
|
||||
margin-bottom: var(--spacing-m);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-m);
|
||||
padding-bottom: var(--spacing-m);
|
||||
border-bottom: 2px solid var(--color-border-light);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: var(--font-size-l);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
.regenerate-button {
|
||||
padding: var(--spacing-s) var(--spacing-m);
|
||||
background: var(--color-white);
|
||||
border: 2px solid var(--color-secondary);
|
||||
border-radius: var(--radius-m);
|
||||
color: var(--color-secondary);
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.regenerate-button:hover {
|
||||
background: var(--color-secondary-light);
|
||||
}
|
||||
|
||||
.edit-label {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-s);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.edit-textarea {
|
||||
width: 100%;
|
||||
min-height: 120px;
|
||||
padding: var(--spacing-m);
|
||||
border: 2px solid var(--color-border-light);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: var(--font-size-m);
|
||||
line-height: var(--line-height-normal);
|
||||
resize: vertical;
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.edit-textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.char-count {
|
||||
text-align: right;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-secondary);
|
||||
margin-top: var(--spacing-s);
|
||||
}
|
||||
|
||||
.image-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.image-item {
|
||||
position: relative;
|
||||
aspect-ratio: 1;
|
||||
background: var(--color-gray-100);
|
||||
border-radius: var(--radius-m);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 48px;
|
||||
border: 2px solid var(--color-border-light);
|
||||
}
|
||||
|
||||
.poster-preview {
|
||||
background: var(--color-gray-100);
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--spacing-l);
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.poster-preview__icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.poster-preview__text {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Action Buttons */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.save-button {
|
||||
width: 100%;
|
||||
padding: var(--spacing-m);
|
||||
background: var(--color-white);
|
||||
border: 2px solid var(--color-border-light);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.save-button:hover {
|
||||
border-color: var(--color-success);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.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) {
|
||||
.edit-header {
|
||||
padding: var(--spacing-2xl) var(--spacing-xl);
|
||||
}
|
||||
|
||||
.edit-content {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.image-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.next-button {
|
||||
flex: 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop */
|
||||
@media (min-width: 1024px) {
|
||||
.edit-content {
|
||||
max-width: 960px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="edit-container">
|
||||
<!-- Header -->
|
||||
<header class="edit-header">
|
||||
<button class="edit-header__back" onclick="history.back()">
|
||||
←
|
||||
</button>
|
||||
<h1 class="edit-header__title">콘텐츠 편집</h1>
|
||||
<p class="edit-header__subtitle">생성된 콘텐츠를 검토하고 수정하세요</p>
|
||||
</header>
|
||||
|
||||
<!-- Content -->
|
||||
<main class="edit-content">
|
||||
<!-- Info Card -->
|
||||
<div class="info-card">
|
||||
<h2 class="info-card__title">
|
||||
✏️ 편집 가이드
|
||||
</h2>
|
||||
<p class="info-card__text">
|
||||
AI가 생성한 콘텐츠를 자유롭게 수정할 수 있습니다.
|
||||
각 섹션의 "재생성" 버튼을 클릭하면 새로운 콘텐츠를 생성할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Copy Section -->
|
||||
<div class="edit-section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
<span class="section-icon">📝</span>
|
||||
홍보 문구
|
||||
</h2>
|
||||
<button class="regenerate-button" id="regenerateCopy">
|
||||
🔄 재생성
|
||||
</button>
|
||||
</div>
|
||||
<label class="edit-label" for="copyText">메인 홍보 문구</label>
|
||||
<textarea
|
||||
id="copyText"
|
||||
class="edit-textarea"
|
||||
placeholder="홍보 문구를 입력하세요..."
|
||||
>🎉 신규 고객 환영 이벤트 🎉
|
||||
|
||||
지금 가입하시면 특별한 혜택이 기다립니다!
|
||||
✨ 첫 구매 20% 할인
|
||||
✨ 무료 배송 쿠폰 증정
|
||||
✨ 추가 적립금 5,000원
|
||||
|
||||
📅 기간: 2024년 1월 15일 ~ 1월 31일
|
||||
🎁 선착순 100명 한정 특별 선물 증정</textarea>
|
||||
<div class="char-count">
|
||||
<span id="copyCharCount">0</span> / 500자
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Images Section -->
|
||||
<div class="edit-section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
<span class="section-icon">🖼️</span>
|
||||
이미지
|
||||
</h2>
|
||||
<button class="regenerate-button" id="regenerateImages">
|
||||
🔄 재생성
|
||||
</button>
|
||||
</div>
|
||||
<div class="image-grid" id="imageGrid">
|
||||
<div class="image-item">🎨</div>
|
||||
<div class="image-item">🎁</div>
|
||||
<div class="image-item">✨</div>
|
||||
<div class="image-item">🎉</div>
|
||||
</div>
|
||||
<p class="info-card__text" style="margin-top: var(--spacing-s);">
|
||||
각 이미지를 클릭하면 새로운 이미지로 교체할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- QR Poster Section -->
|
||||
<div class="edit-section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
<span class="section-icon">📱</span>
|
||||
QR 포스터
|
||||
</h2>
|
||||
<button class="regenerate-button" id="regeneratePoster">
|
||||
🔄 재생성
|
||||
</button>
|
||||
</div>
|
||||
<div class="poster-preview">
|
||||
<div class="poster-preview__icon">📋</div>
|
||||
<div class="poster-preview__text">QR 포스터 미리보기</div>
|
||||
</div>
|
||||
<label class="edit-label" for="posterTitle">포스터 제목</label>
|
||||
<textarea
|
||||
id="posterTitle"
|
||||
class="edit-textarea"
|
||||
placeholder="포스터 제목을 입력하세요..."
|
||||
style="min-height: 80px;"
|
||||
>신규 고객 환영 이벤트</textarea>
|
||||
<label class="edit-label" for="posterSubtitle" style="margin-top: var(--spacing-m);">포스터 부제목</label>
|
||||
<textarea
|
||||
id="posterSubtitle"
|
||||
class="edit-textarea"
|
||||
placeholder="포스터 부제목을 입력하세요..."
|
||||
style="min-height: 80px;"
|
||||
>QR 코드를 스캔하고 특별 혜택을 받으세요</textarea>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="action-buttons">
|
||||
<button class="save-button" id="saveButton">
|
||||
💾 임시 저장
|
||||
</button>
|
||||
<button class="next-button" id="nextButton">
|
||||
편집 완료하고 승인 요청
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Utility functions
|
||||
const $ = (selector) => document.querySelector(selector);
|
||||
const $$ = (selector) => document.querySelectorAll(selector);
|
||||
|
||||
const addClass = (el, className) => el?.classList.add(className);
|
||||
const removeClass = (el, className) => el?.classList.remove(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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Elements
|
||||
const copyText = $('#copyText');
|
||||
const copyCharCount = $('#copyCharCount');
|
||||
const posterTitle = $('#posterTitle');
|
||||
const posterSubtitle = $('#posterSubtitle');
|
||||
const regenerateCopy = $('#regenerateCopy');
|
||||
const regenerateImages = $('#regenerateImages');
|
||||
const regeneratePoster = $('#regeneratePoster');
|
||||
const saveButton = $('#saveButton');
|
||||
const nextButton = $('#nextButton');
|
||||
|
||||
// Update character count
|
||||
function updateCharCount() {
|
||||
const count = copyText.value.length;
|
||||
copyCharCount.textContent = count;
|
||||
|
||||
if (count > 500) {
|
||||
copyCharCount.style.color = 'var(--color-error)';
|
||||
} else {
|
||||
copyCharCount.style.color = 'var(--color-text-secondary)';
|
||||
}
|
||||
}
|
||||
|
||||
copyText.addEventListener('input', updateCharCount);
|
||||
|
||||
// Load saved data
|
||||
function loadSavedData() {
|
||||
const eventContent = storage.get('eventContent') || {};
|
||||
|
||||
if (eventContent.editedContent) {
|
||||
if (eventContent.editedContent.copy) {
|
||||
copyText.value = eventContent.editedContent.copy;
|
||||
}
|
||||
if (eventContent.editedContent.posterTitle) {
|
||||
posterTitle.value = eventContent.editedContent.posterTitle;
|
||||
}
|
||||
if (eventContent.editedContent.posterSubtitle) {
|
||||
posterSubtitle.value = eventContent.editedContent.posterSubtitle;
|
||||
}
|
||||
}
|
||||
|
||||
updateCharCount();
|
||||
}
|
||||
|
||||
// Save edited content
|
||||
function saveContent() {
|
||||
const eventContent = storage.get('eventContent') || {};
|
||||
eventContent.editedContent = {
|
||||
copy: copyText.value,
|
||||
posterTitle: posterTitle.value,
|
||||
posterSubtitle: posterSubtitle.value,
|
||||
savedAt: new Date().toISOString()
|
||||
};
|
||||
storage.set('eventContent', eventContent);
|
||||
}
|
||||
|
||||
// Regenerate buttons
|
||||
regenerateCopy.addEventListener('click', () => {
|
||||
regenerateCopy.textContent = '⏳ 생성 중...';
|
||||
regenerateCopy.disabled = true;
|
||||
|
||||
setTimeout(() => {
|
||||
copyText.value = `🎊 특별 이벤트 🎊\n\n새로운 혜택이 가득합니다!\n✨ 할인 혜택\n✨ 무료 배송\n✨ 포인트 적립\n\n지금 참여하세요!`;
|
||||
updateCharCount();
|
||||
regenerateCopy.textContent = '🔄 재생성';
|
||||
regenerateCopy.disabled = false;
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
regenerateImages.addEventListener('click', () => {
|
||||
regenerateImages.textContent = '⏳ 생성 중...';
|
||||
regenerateImages.disabled = true;
|
||||
|
||||
setTimeout(() => {
|
||||
alert('새로운 이미지가 생성되었습니다!');
|
||||
regenerateImages.textContent = '🔄 재생성';
|
||||
regenerateImages.disabled = false;
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
regeneratePoster.addEventListener('click', () => {
|
||||
regeneratePoster.textContent = '⏳ 생성 중...';
|
||||
regeneratePoster.disabled = true;
|
||||
|
||||
setTimeout(() => {
|
||||
alert('새로운 QR 포스터가 생성되었습니다!');
|
||||
regeneratePoster.textContent = '🔄 재생성';
|
||||
regeneratePoster.disabled = false;
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
// Save button
|
||||
saveButton.addEventListener('click', () => {
|
||||
saveContent();
|
||||
alert('✅ 임시 저장되었습니다!');
|
||||
});
|
||||
|
||||
// Next button
|
||||
nextButton.addEventListener('click', () => {
|
||||
saveContent();
|
||||
|
||||
const eventContent = storage.get('eventContent') || {};
|
||||
eventContent.status = 'content_edited';
|
||||
eventContent.editedAt = new Date().toISOString();
|
||||
storage.set('eventContent', eventContent);
|
||||
|
||||
addClass(nextButton, 'next-button--loading');
|
||||
nextButton.disabled = true;
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.href = '14-콘텐츠최종승인.html';
|
||||
}, 800);
|
||||
});
|
||||
|
||||
// Initialize
|
||||
window.addEventListener('load', () => {
|
||||
loadSavedData();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,733 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>콘텐츠 최종 승인 - KT 이벤트 마케팅</title>
|
||||
<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 */
|
||||
.approval-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
padding-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.approval-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);
|
||||
}
|
||||
|
||||
.approval-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;
|
||||
}
|
||||
|
||||
.approval-header__badge {
|
||||
display: inline-block;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: var(--spacing-xs) var(--spacing-m);
|
||||
border-radius: var(--radius-s);
|
||||
font-size: var(--font-size-s);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.approval-header__title {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.approval-header__subtitle {
|
||||
font-size: var(--font-size-m);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.approval-content {
|
||||
padding: var(--spacing-l) var(--spacing-m);
|
||||
}
|
||||
|
||||
/* Success Message */
|
||||
.success-message {
|
||||
background: linear-gradient(135deg, #F0FFF4 0%, #FFFFFF 100%);
|
||||
border-left: 4px solid var(--color-success);
|
||||
padding: var(--spacing-m);
|
||||
border-radius: var(--radius-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.success-message__icon {
|
||||
font-size: 48px;
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.success-message__title {
|
||||
font-size: var(--font-size-l);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.success-message__text {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Content Cards */
|
||||
.content-cards {
|
||||
display: grid;
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.content-card {
|
||||
background: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.content-card__header {
|
||||
padding: var(--spacing-m);
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
border-bottom: 1px solid var(--color-border-light);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.content-card__title {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.content-card__count {
|
||||
background: var(--color-primary-light);
|
||||
color: var(--color-primary);
|
||||
padding: 2px var(--spacing-s);
|
||||
border-radius: var(--radius-s);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.content-card__edit {
|
||||
background: none;
|
||||
border: 1px solid var(--color-border-light);
|
||||
color: var(--color-text-secondary);
|
||||
padding: var(--spacing-xs) var(--spacing-s);
|
||||
border-radius: var(--radius-s);
|
||||
font-size: var(--font-size-xs);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.content-card__edit:hover {
|
||||
border-color: var(--color-secondary);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.content-card__body {
|
||||
padding: var(--spacing-m);
|
||||
}
|
||||
|
||||
/* Image Grid */
|
||||
.image-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.image-item {
|
||||
aspect-ratio: 1;
|
||||
background: linear-gradient(135deg, #FFE9E9 0%, #FFF5F5 100%);
|
||||
border-radius: var(--radius-m);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32px;
|
||||
border: 1px solid var(--color-border-light);
|
||||
}
|
||||
|
||||
/* SNS List */
|
||||
.sns-list {
|
||||
display: grid;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.sns-item {
|
||||
padding: var(--spacing-s);
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
border-radius: var(--radius-m);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
border: 1px solid var(--color-border-light);
|
||||
}
|
||||
|
||||
.sns-item__icon {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
.sns-item__info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sns-item__name {
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.sns-item__preview {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-secondary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sns-item__badge {
|
||||
background: var(--color-success-light);
|
||||
color: var(--color-success);
|
||||
padding: 2px var(--spacing-xs);
|
||||
border-radius: var(--radius-s);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
/* Poster Preview */
|
||||
.poster-preview {
|
||||
aspect-ratio: 3 / 4;
|
||||
background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--spacing-l);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 1px solid var(--color-border-light);
|
||||
}
|
||||
|
||||
.poster-preview__title {
|
||||
font-size: var(--font-size-l);
|
||||
font-weight: var(--font-weight-bold);
|
||||
text-align: center;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.poster-preview__qr {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: var(--color-white);
|
||||
border: 2px solid var(--color-border-light);
|
||||
border-radius: var(--radius-m);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.poster-preview__cta {
|
||||
font-size: var(--font-size-s);
|
||||
text-align: center;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Stats */
|
||||
.content-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--color-white);
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--spacing-m);
|
||||
text-align: center;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.stat-card__value {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-secondary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.stat-card__label {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Action Buttons */
|
||||
.action-buttons {
|
||||
display: grid;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.action-button {
|
||||
width: 100%;
|
||||
padding: var(--spacing-m);
|
||||
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);
|
||||
}
|
||||
|
||||
.action-button--primary {
|
||||
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-dark) 100%);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.action-button--primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.action-button--secondary {
|
||||
background: var(--color-white);
|
||||
border: 2px solid var(--color-border-light);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.action-button--secondary:hover {
|
||||
border-color: var(--color-secondary);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.action-button--loading {
|
||||
position: relative;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.action-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) {
|
||||
.approval-header {
|
||||
padding: var(--spacing-2xl) var(--spacing-xl);
|
||||
}
|
||||
|
||||
.approval-content {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.image-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop */
|
||||
@media (min-width: 1024px) {
|
||||
.approval-content {
|
||||
max-width: 960px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="approval-container">
|
||||
<!-- Header -->
|
||||
<header class="approval-header">
|
||||
<button class="approval-header__back" onclick="history.back()">
|
||||
←
|
||||
</button>
|
||||
<div class="approval-header__badge">✨ AI 콘텐츠 생성 완료</div>
|
||||
<h1 class="approval-header__title">콘텐츠 최종 승인</h1>
|
||||
<p class="approval-header__subtitle">생성된 모든 콘텐츠를 확인하고 승인해 주세요</p>
|
||||
</header>
|
||||
|
||||
<!-- Content -->
|
||||
<main class="approval-content">
|
||||
<!-- Success Message -->
|
||||
<div class="success-message">
|
||||
<div class="success-message__icon">🎉</div>
|
||||
<h2 class="success-message__title">완벽한 이벤트 콘텐츠가 준비되었어요!</h2>
|
||||
<p class="success-message__text">
|
||||
AI가 생성한 고품질 콘텐츠로 이벤트를 홍보할 준비가 완료되었습니다.
|
||||
아래 내용을 최종 확인하고 승인하시면 바로 배포 단계로 이동합니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="content-stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__value" id="imageCount">4개</div>
|
||||
<div class="stat-card__label">생성된 이미지</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__value" id="snsCount">2개</div>
|
||||
<div class="stat-card__label">SNS 콘텐츠</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__value" id="posterCount">1개</div>
|
||||
<div class="stat-card__label">QR 포스터</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Cards -->
|
||||
<div class="content-cards">
|
||||
<!-- Images -->
|
||||
<div class="content-card">
|
||||
<div class="content-card__header">
|
||||
<div class="content-card__title">
|
||||
🎨 AI 생성 이미지
|
||||
<span class="content-card__count">4개</span>
|
||||
</div>
|
||||
<button class="content-card__edit" onclick="window.location.href='09-AI이미지생성.html'">
|
||||
수정
|
||||
</button>
|
||||
</div>
|
||||
<div class="content-card__body">
|
||||
<div class="image-grid">
|
||||
<div class="image-item">🎉</div>
|
||||
<div class="image-item">🎊</div>
|
||||
<div class="image-item">🎈</div>
|
||||
<div class="image-item">🎁</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SNS Content -->
|
||||
<div class="content-card">
|
||||
<div class="content-card__header">
|
||||
<div class="content-card__title">
|
||||
📱 SNS 콘텐츠
|
||||
<span class="content-card__count" id="snsContentCount">2개</span>
|
||||
</div>
|
||||
<button class="content-card__edit" onclick="window.location.href='11-SNS콘텐츠생성.html'">
|
||||
수정
|
||||
</button>
|
||||
</div>
|
||||
<div class="content-card__body">
|
||||
<div class="sns-list" id="snsList">
|
||||
<div class="sns-item">
|
||||
<div class="sns-item__icon">📷</div>
|
||||
<div class="sns-item__info">
|
||||
<div class="sns-item__name">인스타그램</div>
|
||||
<div class="sns-item__preview">🎉 친구 초대하고 함께 받는 특별한 혜택!</div>
|
||||
</div>
|
||||
<div class="sns-item__badge">생성완료</div>
|
||||
</div>
|
||||
<div class="sns-item">
|
||||
<div class="sns-item__icon">👥</div>
|
||||
<div class="sns-item__info">
|
||||
<div class="sns-item__name">페이스북</div>
|
||||
<div class="sns-item__preview">🎉 친구 초대 이벤트 진행 중!</div>
|
||||
</div>
|
||||
<div class="sns-item__badge">생성완료</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- QR Poster -->
|
||||
<div class="content-card">
|
||||
<div class="content-card__header">
|
||||
<div class="content-card__title">
|
||||
📄 QR 포스터
|
||||
<span class="content-card__count">1개</span>
|
||||
</div>
|
||||
<button class="content-card__edit" onclick="window.location.href='12-QR포스터생성.html'">
|
||||
수정
|
||||
</button>
|
||||
</div>
|
||||
<div class="content-card__body">
|
||||
<div class="poster-preview">
|
||||
<h3 class="poster-preview__title">🎉 친구 초대 이벤트</h3>
|
||||
<div class="poster-preview__qr">📱</div>
|
||||
<p class="poster-preview__cta">QR 코드를 스캔하고 참여하세요!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="action-buttons">
|
||||
<button class="action-button action-button--secondary" id="saveButton">
|
||||
📝 임시저장
|
||||
</button>
|
||||
<button class="action-button action-button--primary" id="approveButton">
|
||||
✅ 승인하고 배포하기
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Utility functions
|
||||
const $ = (selector) => document.querySelector(selector);
|
||||
|
||||
const addClass = (el, className) => el?.classList.add(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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Elements
|
||||
const imageCount = $('#imageCount');
|
||||
const snsCount = $('#snsCount');
|
||||
const posterCount = $('#posterCount');
|
||||
const snsContentCount = $('#snsContentCount');
|
||||
const snsList = $('#snsList');
|
||||
const saveButton = $('#saveButton');
|
||||
const approveButton = $('#approveButton');
|
||||
|
||||
// Load content data
|
||||
const eventContent = storage.get('eventContent') || {};
|
||||
|
||||
// Update stats
|
||||
function updateStats() {
|
||||
// Images
|
||||
if (eventContent.generatedImages && eventContent.generatedImages.selectedImages) {
|
||||
const count = eventContent.generatedImages.selectedImages.length;
|
||||
imageCount.textContent = count + '개';
|
||||
}
|
||||
|
||||
// SNS
|
||||
if (eventContent.snsContent && eventContent.snsContent.selectedPlatforms) {
|
||||
const count = eventContent.snsContent.selectedPlatforms.length;
|
||||
snsCount.textContent = count + '개';
|
||||
snsContentCount.textContent = count + '개';
|
||||
|
||||
// Render SNS list
|
||||
const platforms = eventContent.snsContent.platforms || [];
|
||||
snsList.innerHTML = platforms.map(platform => `
|
||||
<div class="sns-item">
|
||||
<div class="sns-item__icon">${platform.icon}</div>
|
||||
<div class="sns-item__info">
|
||||
<div class="sns-item__name">${platform.name}</div>
|
||||
<div class="sns-item__preview">${platform.caption.substring(0, 50)}...</div>
|
||||
</div>
|
||||
<div class="sns-item__badge">생성완료</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Poster
|
||||
if (eventContent.qrPoster) {
|
||||
posterCount.textContent = '1개';
|
||||
}
|
||||
}
|
||||
|
||||
// Save draft
|
||||
saveButton.addEventListener('click', () => {
|
||||
eventContent.status = 'content_draft';
|
||||
eventContent.savedAt = new Date().toISOString();
|
||||
storage.set('eventContent', eventContent);
|
||||
|
||||
alert('✅ 콘텐츠가 임시저장되었습니다.');
|
||||
});
|
||||
|
||||
// Approve and continue
|
||||
approveButton.addEventListener('click', () => {
|
||||
// Mark as approved
|
||||
eventContent.status = 'content_approved';
|
||||
eventContent.approvedAt = new Date().toISOString();
|
||||
storage.set('eventContent', eventContent);
|
||||
|
||||
// Show loading
|
||||
addClass(approveButton, 'action-button--loading');
|
||||
approveButton.disabled = true;
|
||||
|
||||
// Navigate to Phase 4 (Distribution)
|
||||
setTimeout(() => {
|
||||
window.location.href = '15-배포채널선택.html';
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Initialize
|
||||
window.addEventListener('load', () => {
|
||||
updateStats();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,993 +0,0 @@
|
||||
<!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>
|
||||
<style>
|
||||
/* ========================================
|
||||
CSS Variables & Base Styles
|
||||
======================================== */
|
||||
|
||||
:root {
|
||||
/* Colors */
|
||||
--color-primary: #E31E24;
|
||||
--color-primary-dark: #B71C1C;
|
||||
--color-primary-light: #FFEBEE;
|
||||
--color-secondary: #0066FF;
|
||||
--color-secondary-dark: #0052CC;
|
||||
--color-secondary-light: #E3F2FD;
|
||||
--color-success: #4CAF50;
|
||||
--color-success-dark: #388E3C;
|
||||
--color-success-light: #E8F5E9;
|
||||
--color-warning: #FF9800;
|
||||
--color-warning-dark: #F57C00;
|
||||
--color-warning-light: #FFF3E0;
|
||||
--color-error: #F44336;
|
||||
--color-error-dark: #D32F2F;
|
||||
--color-error-light: #FFEBEE;
|
||||
|
||||
/* 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: #E0E0E0;
|
||||
--color-border-light: #F5F5F5;
|
||||
--color-border-dark: #BDBDBD;
|
||||
|
||||
/* Background Colors */
|
||||
--color-bg-primary: #FFFFFF;
|
||||
--color-bg-secondary: #F5F5F5;
|
||||
--color-bg-tertiary: #FAFAFA;
|
||||
|
||||
/* Surface Colors */
|
||||
--color-surface: #FFFFFF;
|
||||
--color-surface-elevated: #FFFFFF;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* 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-family-secondary: "Noto Sans KR", sans-serif;
|
||||
--font-family-mono: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace;
|
||||
|
||||
--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-round: 9999px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
--shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
--shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 150ms;
|
||||
--transition-base: 200ms;
|
||||
--transition-slow: 300ms;
|
||||
--transition-slower: 500ms;
|
||||
|
||||
/* Durations */
|
||||
--duration-instant: 0ms;
|
||||
--duration-fast: 150ms;
|
||||
--duration-normal: 300ms;
|
||||
--duration-slow: 500ms;
|
||||
|
||||
/* Easings */
|
||||
--easing-linear: linear;
|
||||
--easing-ease: ease;
|
||||
--easing-ease-in: ease-in;
|
||||
--easing-ease-out: ease-out;
|
||||
--easing-ease-in-out: ease-in-out;
|
||||
--easing-smooth: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--easing-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
|
||||
/* 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;
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
/* Header Component */
|
||||
.header {
|
||||
background: var(--color-white);
|
||||
padding: var(--spacing-m);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: var(--shadow-sm);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: var(--z-sticky);
|
||||
}
|
||||
|
||||
.header__back {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-text-primary);
|
||||
transition: color var(--transition-fast) var(--easing-smooth);
|
||||
}
|
||||
|
||||
.header__back:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.header__title {
|
||||
font-size: var(--font-size-l);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.header__actions {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Bottom Actions */
|
||||
.bottom-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--color-white);
|
||||
padding: var(--spacing-m);
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
|
||||
z-index: var(--z-sticky);
|
||||
}
|
||||
|
||||
.bottom-actions__content {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
/* Action Button Component */
|
||||
.action-button {
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
transition: all var(--transition-fast) var(--easing-smooth);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-s);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.action-button--primary {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.action-button--primary:hover:not(:disabled) {
|
||||
background: var(--color-primary-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.action-button--secondary {
|
||||
background: var(--color-white);
|
||||
color: var(--color-primary);
|
||||
border: 2px solid var(--color-primary);
|
||||
}
|
||||
|
||||
.action-button--secondary:hover:not(:disabled) {
|
||||
background: var(--color-primary-light);
|
||||
}
|
||||
|
||||
.action-button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.action-button--loading {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.action-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); }
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
18-이벤트참여.html (Customer-facing)
|
||||
======================================== */
|
||||
|
||||
/* Main Container */
|
||||
.event-participation {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #F0F7FF 0%, #FFFFFF 100%);
|
||||
padding: var(--spacing-l) var(--spacing-m);
|
||||
}
|
||||
|
||||
/* Event Header */
|
||||
.event-header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
animation: slide-down 0.6s var(--easing-smooth);
|
||||
}
|
||||
|
||||
@keyframes slide-down {
|
||||
0% { transform: translateY(-20px); opacity: 0; }
|
||||
100% { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
|
||||
.event-header__icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.event-header__store {
|
||||
font-size: var(--font-size-l);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.event-header__title {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-primary);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
/* Event Info Card */
|
||||
.event-info {
|
||||
background: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-l);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
.event-info__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-m);
|
||||
padding: var(--spacing-m) 0;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.event-info__row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.event-info__icon {
|
||||
font-size: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.event-info__content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.event-info__label {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.event-info__value {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* Participation Methods */
|
||||
.participation-methods {
|
||||
background: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-l);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
.participation-methods__title {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.participation-methods__list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.participation-methods__item {
|
||||
padding: var(--spacing-s) 0;
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.participation-methods__item::before {
|
||||
content: '✓ ';
|
||||
color: var(--color-primary);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-right: var(--spacing-xs);
|
||||
}
|
||||
|
||||
/* Form */
|
||||
.participation-form {
|
||||
background: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-l);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
.participation-form__title {
|
||||
font-size: var(--font-size-l);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.form-field {
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.form-field__label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.form-field__required {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.form-field__input {
|
||||
width: 100%;
|
||||
padding: var(--spacing-m);
|
||||
font-size: var(--font-size-m);
|
||||
border: 2px solid var(--color-border);
|
||||
border-radius: var(--radius-m);
|
||||
background: var(--color-white);
|
||||
transition: all var(--duration-fast);
|
||||
}
|
||||
|
||||
.form-field__input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px var(--color-primary-light);
|
||||
}
|
||||
|
||||
.form-field__input--error {
|
||||
border-color: var(--color-error);
|
||||
}
|
||||
|
||||
.form-field__error {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-error);
|
||||
margin-top: var(--spacing-xs);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-field__error--visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Channel Detection */
|
||||
.channel-detection {
|
||||
background: linear-gradient(135deg, #E3F2FD 0%, #F0F7FF 100%);
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.channel-detection__label {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.channel-detection__value {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
/* Consent */
|
||||
.consent-field {
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.consent-checkbox {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-m);
|
||||
padding: var(--spacing-m);
|
||||
background: var(--color-surface-light);
|
||||
border-radius: var(--radius-m);
|
||||
cursor: pointer;
|
||||
transition: all var(--duration-fast);
|
||||
}
|
||||
|
||||
.consent-checkbox:hover {
|
||||
background: var(--color-primary-light);
|
||||
}
|
||||
|
||||
.consent-checkbox__box {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid var(--color-border-dark);
|
||||
border-radius: var(--radius-s);
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--color-white);
|
||||
transition: all var(--duration-fast);
|
||||
}
|
||||
|
||||
.consent-checkbox--checked .consent-checkbox__box {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.consent-checkbox__box::after {
|
||||
content: '✓';
|
||||
color: var(--color-white);
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-bold);
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
transition: all var(--duration-fast) var(--easing-bounce);
|
||||
}
|
||||
|
||||
.consent-checkbox--checked .consent-checkbox__box::after {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.consent-checkbox__label {
|
||||
flex: 1;
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.consent-checkbox__required {
|
||||
color: var(--color-error);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.consent-checkbox__link {
|
||||
color: var(--color-primary);
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Benefit Badge */
|
||||
.benefit-badge {
|
||||
background: linear-gradient(135deg, #FFF8E1 0%, #FFE082 100%);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-xl);
|
||||
border: 2px dashed #FFA000;
|
||||
}
|
||||
|
||||
.benefit-badge__icon {
|
||||
font-size: 32px;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.benefit-badge__text {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: #F57C00;
|
||||
}
|
||||
|
||||
/* Submit Button */
|
||||
.submit-button {
|
||||
width: 100%;
|
||||
padding: var(--spacing-l);
|
||||
font-size: var(--font-size-l);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-white);
|
||||
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
|
||||
border: none;
|
||||
border-radius: var(--radius-l);
|
||||
cursor: pointer;
|
||||
transition: all var(--duration-fast);
|
||||
box-shadow: var(--shadow-m);
|
||||
}
|
||||
|
||||
.submit-button:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-l);
|
||||
}
|
||||
|
||||
.submit-button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.submit-button--loading {
|
||||
position: relative;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.submit-button--loading::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: -12px 0 0 -12px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||
border-top-color: var(--color-white);
|
||||
border-radius: var(--radius-full);
|
||||
animation: spin var(--duration-slow) linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Tablet */
|
||||
@media (min-width: 768px) {
|
||||
.event-participation {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop */
|
||||
@media (min-width: 1024px) {
|
||||
.event-participation {
|
||||
max-width: 700px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Main Content -->
|
||||
<main class="event-participation">
|
||||
<!-- Event Header -->
|
||||
<div class="event-header">
|
||||
<div class="event-header__icon">🎉</div>
|
||||
<div class="event-header__store">수원 왕갈비통닭</div>
|
||||
<div class="event-header__title">연말 대박 이벤트!</div>
|
||||
</div>
|
||||
|
||||
<!-- Event Info -->
|
||||
<div class="event-info">
|
||||
<div class="event-info__row">
|
||||
<div class="event-info__icon">🎁</div>
|
||||
<div class="event-info__content">
|
||||
<div class="event-info__label">경품</div>
|
||||
<div class="event-info__value">치킨세트 무료교환권</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="event-info__row">
|
||||
<div class="event-info__icon">👥</div>
|
||||
<div class="event-info__content">
|
||||
<div class="event-info__label">당첨 인원</div>
|
||||
<div class="event-info__value">100명</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="event-info__row">
|
||||
<div class="event-info__icon">📅</div>
|
||||
<div class="event-info__content">
|
||||
<div class="event-info__label">참여 기간</div>
|
||||
<div class="event-info__value">~ 2025-12-31</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Participation Methods -->
|
||||
<div class="participation-methods">
|
||||
<div class="participation-methods__title">참여 방법</div>
|
||||
<ul class="participation-methods__list">
|
||||
<li class="participation-methods__item">매장 방문하기</li>
|
||||
<li class="participation-methods__item">QR 코드 스캔하기</li>
|
||||
<li class="participation-methods__item">아래 정보 입력하기</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Participation Form -->
|
||||
<div class="participation-form">
|
||||
<h2 class="participation-form__title">참여 신청</h2>
|
||||
|
||||
<form id="participationForm">
|
||||
<!-- Name -->
|
||||
<div class="form-field">
|
||||
<label class="form-field__label">
|
||||
이름
|
||||
<span class="form-field__required">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-field__input"
|
||||
id="nameInput"
|
||||
placeholder="이름을 입력하세요"
|
||||
required
|
||||
>
|
||||
<div class="form-field__error" id="nameError">이름을 입력해주세요</div>
|
||||
</div>
|
||||
|
||||
<!-- Phone -->
|
||||
<div class="form-field">
|
||||
<label class="form-field__label">
|
||||
전화번호
|
||||
<span class="form-field__required">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
class="form-field__input"
|
||||
id="phoneInput"
|
||||
placeholder="010-0000-0000"
|
||||
required
|
||||
>
|
||||
<div class="form-field__error" id="phoneError">올바른 전화번호를 입력해주세요</div>
|
||||
</div>
|
||||
|
||||
<!-- Channel Detection -->
|
||||
<div class="channel-detection">
|
||||
<div class="channel-detection__label">참여 경로 (자동 감지)</div>
|
||||
<div class="channel-detection__value">
|
||||
<span id="channelIcon">📍</span>
|
||||
<span id="channelText">QR 코드 스캔</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Consent -->
|
||||
<div class="consent-field">
|
||||
<div class="consent-checkbox" id="consentCheckbox">
|
||||
<div class="consent-checkbox__box"></div>
|
||||
<div class="consent-checkbox__label">
|
||||
<span class="consent-checkbox__required">(필수)</span>
|
||||
개인정보 수집 및 이용에 동의합니다
|
||||
<span class="consent-checkbox__link" onclick="showConsentDetail(event)">자세히</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Benefit Badge -->
|
||||
<div class="benefit-badge">
|
||||
<div class="benefit-badge__icon">💡</div>
|
||||
<div class="benefit-badge__text">
|
||||
매장 방문 고객은 당첨 확률 UP! 🎁
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<button class="submit-button" id="submitButton" disabled>
|
||||
참여하기
|
||||
</button>
|
||||
</main>
|
||||
|
||||
<!-- Page Script -->
|
||||
<script>
|
||||
// AppState object with showToast method
|
||||
const AppState = {
|
||||
showToast: (message) => {
|
||||
alert(message);
|
||||
}
|
||||
};
|
||||
|
||||
// Storage utility
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Helper functions
|
||||
function addClass(element, className) {
|
||||
if (element) element.classList.add(className);
|
||||
}
|
||||
|
||||
function removeClass(element, className) {
|
||||
if (element) element.classList.remove(className);
|
||||
}
|
||||
|
||||
// Elements
|
||||
const form = document.getElementById('participationForm');
|
||||
const nameInput = document.getElementById('nameInput');
|
||||
const phoneInput = document.getElementById('phoneInput');
|
||||
const nameError = document.getElementById('nameError');
|
||||
const phoneError = document.getElementById('phoneError');
|
||||
const consentCheckbox = document.getElementById('consentCheckbox');
|
||||
const submitButton = document.getElementById('submitButton');
|
||||
|
||||
// State
|
||||
let isConsentChecked = false;
|
||||
let detectedChannel = 'qr'; // qr, sns, tv, etc.
|
||||
|
||||
// Detect channel from URL params (in real app)
|
||||
function detectChannel() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const source = urlParams.get('source') || 'qr';
|
||||
|
||||
const channelMap = {
|
||||
'qr': { icon: '📍', text: 'QR 코드 스캔' },
|
||||
'instagram': { icon: '📷', text: 'Instagram' },
|
||||
'blog': { icon: '📝', text: 'Naver Blog' },
|
||||
'kakao': { icon: '💬', text: 'Kakao Channel' },
|
||||
'tv': { icon: '📺', text: '우리동네TV' }
|
||||
};
|
||||
|
||||
const channel = channelMap[source] || channelMap['qr'];
|
||||
document.getElementById('channelIcon').textContent = channel.icon;
|
||||
document.getElementById('channelText').textContent = channel.text;
|
||||
|
||||
detectedChannel = source;
|
||||
}
|
||||
|
||||
// Validate phone number
|
||||
function validatePhone(phone) {
|
||||
// Remove hyphens
|
||||
const cleaned = phone.replace(/-/g, '');
|
||||
|
||||
// Check format: 010-xxxx-xxxx or 01x-xxx-xxxx
|
||||
const regex = /^01[0-9]\d{7,8}$/;
|
||||
return regex.test(cleaned);
|
||||
}
|
||||
|
||||
// Format phone number
|
||||
function formatPhone(phone) {
|
||||
const cleaned = phone.replace(/\D/g, '');
|
||||
|
||||
if (cleaned.length <= 3) {
|
||||
return cleaned;
|
||||
} else if (cleaned.length <= 7) {
|
||||
return `${cleaned.slice(0, 3)}-${cleaned.slice(3)}`;
|
||||
} else if (cleaned.length <= 10) {
|
||||
return `${cleaned.slice(0, 3)}-${cleaned.slice(3, 6)}-${cleaned.slice(6)}`;
|
||||
} else {
|
||||
return `${cleaned.slice(0, 3)}-${cleaned.slice(3, 7)}-${cleaned.slice(7, 11)}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Input listeners
|
||||
nameInput.addEventListener('input', () => {
|
||||
if (nameInput.value.trim()) {
|
||||
removeClass(nameInput, 'form-field__input--error');
|
||||
removeClass(nameError, 'form-field__error--visible');
|
||||
}
|
||||
updateSubmitButton();
|
||||
});
|
||||
|
||||
phoneInput.addEventListener('input', (e) => {
|
||||
// Auto-format phone number
|
||||
e.target.value = formatPhone(e.target.value);
|
||||
|
||||
if (validatePhone(e.target.value)) {
|
||||
removeClass(phoneInput, 'form-field__input--error');
|
||||
removeClass(phoneError, 'form-field__error--visible');
|
||||
}
|
||||
updateSubmitButton();
|
||||
});
|
||||
|
||||
phoneInput.addEventListener('blur', () => {
|
||||
if (phoneInput.value && !validatePhone(phoneInput.value)) {
|
||||
addClass(phoneInput, 'form-field__input--error');
|
||||
addClass(phoneError, 'form-field__error--visible');
|
||||
}
|
||||
});
|
||||
|
||||
// Consent checkbox
|
||||
consentCheckbox.addEventListener('click', () => {
|
||||
isConsentChecked = !isConsentChecked;
|
||||
|
||||
if (isConsentChecked) {
|
||||
addClass(consentCheckbox, 'consent-checkbox--checked');
|
||||
} else {
|
||||
removeClass(consentCheckbox, 'consent-checkbox--checked');
|
||||
}
|
||||
|
||||
updateSubmitButton();
|
||||
});
|
||||
|
||||
// Show consent detail
|
||||
function showConsentDetail(event) {
|
||||
event.stopPropagation();
|
||||
alert('개인정보 수집 및 이용 동의\n\n수집 항목: 이름, 전화번호\n수집 목적: 이벤트 참여 및 당첨자 통보\n보유 기간: 이벤트 종료 후 3개월\n\n※ 동의를 거부할 수 있으나, 거부 시 이벤트 참여가 불가능합니다.');
|
||||
}
|
||||
|
||||
// Update submit button
|
||||
function updateSubmitButton() {
|
||||
const isNameValid = nameInput.value.trim().length > 0;
|
||||
const isPhoneValid = validatePhone(phoneInput.value);
|
||||
const isFormValid = isNameValid && isPhoneValid && isConsentChecked;
|
||||
|
||||
submitButton.disabled = !isFormValid;
|
||||
}
|
||||
|
||||
// Submit form
|
||||
submitButton.addEventListener('click', async () => {
|
||||
// Validate
|
||||
let hasError = false;
|
||||
|
||||
if (!nameInput.value.trim()) {
|
||||
addClass(nameInput, 'form-field__input--error');
|
||||
addClass(nameError, 'form-field__error--visible');
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
if (!validatePhone(phoneInput.value)) {
|
||||
addClass(phoneInput, 'form-field__input--error');
|
||||
addClass(phoneError, 'form-field__error--visible');
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
if (!isConsentChecked) {
|
||||
AppState.showToast('개인정보 수집 동의가 필요합니다');
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
if (hasError) return;
|
||||
|
||||
// Show loading
|
||||
addClass(submitButton, 'submit-button--loading');
|
||||
submitButton.disabled = true;
|
||||
|
||||
// Simulate API call
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
|
||||
// Check for duplicate (simulate)
|
||||
const isDuplicate = Math.random() < 0.1; // 10% chance for demo
|
||||
|
||||
if (isDuplicate) {
|
||||
removeClass(submitButton, 'submit-button--loading');
|
||||
submitButton.disabled = false;
|
||||
AppState.showToast('이미 참여하셨습니다 (2025-12-15 14:30)');
|
||||
return;
|
||||
}
|
||||
|
||||
// Save participation data
|
||||
const participationData = {
|
||||
name: nameInput.value.trim(),
|
||||
phone: phoneInput.value,
|
||||
channel: detectedChannel,
|
||||
eventId: 'event-001', // From URL or context
|
||||
participatedAt: new Date().toISOString(),
|
||||
ticketNumber: generateTicketNumber()
|
||||
};
|
||||
|
||||
storage.set('participationData', participationData);
|
||||
|
||||
// Navigate to completion page
|
||||
window.location.href = '19-참여완료.html';
|
||||
});
|
||||
|
||||
// Generate ticket number
|
||||
function generateTicketNumber() {
|
||||
const prefix = 'A';
|
||||
const number = Math.floor(10000000 + Math.random() * 90000000);
|
||||
return `${prefix}-${number}`;
|
||||
}
|
||||
|
||||
// Initialize
|
||||
detectChannel();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,905 +0,0 @@
|
||||
<!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>
|
||||
<style>
|
||||
/* ========================================
|
||||
CSS Variables & Base Styles
|
||||
======================================== */
|
||||
|
||||
:root {
|
||||
/* Colors */
|
||||
--color-primary: #E31E24;
|
||||
--color-primary-dark: #B71C1C;
|
||||
--color-primary-light: #FFEBEE;
|
||||
--color-secondary: #0066FF;
|
||||
--color-secondary-dark: #0052CC;
|
||||
--color-secondary-light: #E3F2FD;
|
||||
--color-success: #4CAF50;
|
||||
--color-success-dark: #388E3C;
|
||||
--color-success-light: #E8F5E9;
|
||||
--color-warning: #FF9800;
|
||||
--color-warning-dark: #F57C00;
|
||||
--color-warning-light: #FFF3E0;
|
||||
--color-error: #F44336;
|
||||
--color-error-dark: #D32F2F;
|
||||
--color-error-light: #FFEBEE;
|
||||
|
||||
/* 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: #E0E0E0;
|
||||
--color-border-light: #F5F5F5;
|
||||
--color-border-dark: #BDBDBD;
|
||||
|
||||
/* Background Colors */
|
||||
--color-bg-primary: #FFFFFF;
|
||||
--color-bg-secondary: #F5F5F5;
|
||||
--color-bg-tertiary: #FAFAFA;
|
||||
|
||||
/* Surface Colors */
|
||||
--color-surface: #FFFFFF;
|
||||
--color-surface-elevated: #FFFFFF;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* 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-family-secondary: "Noto Sans KR", sans-serif;
|
||||
--font-family-mono: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace;
|
||||
|
||||
--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-round: 9999px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
--shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
--shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 150ms;
|
||||
--transition-base: 200ms;
|
||||
--transition-slow: 300ms;
|
||||
--transition-slower: 500ms;
|
||||
|
||||
/* Durations */
|
||||
--duration-instant: 0ms;
|
||||
--duration-fast: 150ms;
|
||||
--duration-normal: 300ms;
|
||||
--duration-slow: 500ms;
|
||||
|
||||
/* Easings */
|
||||
--easing-linear: linear;
|
||||
--easing-ease: ease;
|
||||
--easing-ease-in: ease-in;
|
||||
--easing-ease-out: ease-out;
|
||||
--easing-ease-in-out: ease-in-out;
|
||||
--easing-smooth: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--easing-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
|
||||
/* 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;
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
/* Header Component */
|
||||
.header {
|
||||
background: var(--color-white);
|
||||
padding: var(--spacing-m);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: var(--shadow-sm);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: var(--z-sticky);
|
||||
}
|
||||
|
||||
.header__back {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-text-primary);
|
||||
transition: color var(--transition-fast) var(--easing-smooth);
|
||||
}
|
||||
|
||||
.header__back:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.header__title {
|
||||
font-size: var(--font-size-l);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.header__actions {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Bottom Actions */
|
||||
.bottom-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--color-white);
|
||||
padding: var(--spacing-m);
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
|
||||
z-index: var(--z-sticky);
|
||||
}
|
||||
|
||||
.bottom-actions__content {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
/* Action Button Component */
|
||||
.action-button {
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
transition: all var(--transition-fast) var(--easing-smooth);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-s);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.action-button--primary {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.action-button--primary:hover:not(:disabled) {
|
||||
background: var(--color-primary-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.action-button--secondary {
|
||||
background: var(--color-white);
|
||||
color: var(--color-primary);
|
||||
border: 2px solid var(--color-primary);
|
||||
}
|
||||
|
||||
.action-button--secondary:hover:not(:disabled) {
|
||||
background: var(--color-primary-light);
|
||||
}
|
||||
|
||||
.action-button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.action-button--loading {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.action-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); }
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
19-참여완료.html (Customer-facing)
|
||||
======================================== */
|
||||
|
||||
/* Main Container */
|
||||
.completion-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #F0F7FF 0%, #FFFFFF 100%);
|
||||
padding: var(--spacing-l) var(--spacing-m);
|
||||
}
|
||||
|
||||
/* Success Header */
|
||||
.success-header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
animation: success-pop 0.6s var(--easing-bounce);
|
||||
}
|
||||
|
||||
@keyframes success-pop {
|
||||
0% { transform: scale(0); opacity: 0; }
|
||||
50% { transform: scale(1.1); }
|
||||
100% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
.success-header__icon {
|
||||
font-size: 80px;
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.success-header__title {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.success-header__desc {
|
||||
font-size: var(--font-size-m);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Ticket Card */
|
||||
.ticket-card {
|
||||
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
|
||||
border-radius: var(--radius-xl);
|
||||
padding: var(--spacing-xl);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
box-shadow: var(--shadow-l);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ticket-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
right: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
|
||||
animation: ticket-shine 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes ticket-shine {
|
||||
0%, 100% { transform: translateX(0) translateY(0); }
|
||||
50% { transform: translateX(-30px) translateY(-30px); }
|
||||
}
|
||||
|
||||
.ticket-card__label {
|
||||
font-size: var(--font-size-s);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
margin-bottom: var(--spacing-s);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ticket-card__number {
|
||||
font-size: var(--font-size-3xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-white);
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: var(--spacing-l);
|
||||
font-family: 'Courier New', monospace;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ticket-card__copy {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
padding: var(--spacing-s) var(--spacing-m);
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: var(--font-weight-medium);
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: var(--color-white);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: var(--radius-full);
|
||||
cursor: pointer;
|
||||
transition: all var(--duration-fast);
|
||||
backdrop-filter: blur(10px);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ticket-card__copy:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* Drawing Info */
|
||||
.drawing-info {
|
||||
background: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-l);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
.drawing-info__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-m);
|
||||
padding: var(--spacing-m) 0;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.drawing-info__row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.drawing-info__icon {
|
||||
font-size: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.drawing-info__content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.drawing-info__label {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.drawing-info__value {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* Notice */
|
||||
.notice-card {
|
||||
background: linear-gradient(135deg, #FFF8E1 0%, #FFECB3 100%);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-l);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
border-left: 4px solid #FFA000;
|
||||
}
|
||||
|
||||
.notice-card__title {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: #F57C00;
|
||||
margin-bottom: var(--spacing-s);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.notice-card__text {
|
||||
font-size: var(--font-size-s);
|
||||
color: #E65100;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Store Info */
|
||||
.store-info {
|
||||
background: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-l);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
.store-info__title {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.store-info__item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.store-info__item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.store-info__icon {
|
||||
font-size: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.store-info__content {
|
||||
flex: 1;
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.store-info__button {
|
||||
width: 100%;
|
||||
padding: var(--spacing-m);
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-medium);
|
||||
background: var(--color-white);
|
||||
color: var(--color-primary);
|
||||
border: 2px solid var(--color-primary);
|
||||
border-radius: var(--radius-l);
|
||||
cursor: pointer;
|
||||
transition: all var(--duration-fast);
|
||||
margin-top: var(--spacing-m);
|
||||
}
|
||||
|
||||
.store-info__button:hover {
|
||||
background: var(--color-primary-light);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
/* Share Section */
|
||||
.share-section {
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.share-section__title {
|
||||
font-size: var(--font-size-m);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-m);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.share-buttons {
|
||||
display: flex;
|
||||
gap: var(--spacing-m);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.share-button {
|
||||
flex: 1;
|
||||
max-width: 120px;
|
||||
padding: var(--spacing-m);
|
||||
background: var(--color-white);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-l);
|
||||
cursor: pointer;
|
||||
transition: all var(--duration-fast);
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
.share-button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-m);
|
||||
}
|
||||
|
||||
.share-button__icon {
|
||||
font-size: 32px;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.share-button__label {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Confetti */
|
||||
.confetti {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.confetti-piece {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: var(--color-primary);
|
||||
animation: confetti-fall 3s linear forwards;
|
||||
}
|
||||
|
||||
@keyframes confetti-fall {
|
||||
to {
|
||||
transform: translateY(100vh) rotate(720deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tablet */
|
||||
@media (min-width: 768px) {
|
||||
.completion-page {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop */
|
||||
@media (min-width: 1024px) {
|
||||
.completion-page {
|
||||
max-width: 700px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Confetti Container -->
|
||||
<div class="confetti" id="confetti"></div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="completion-page">
|
||||
<!-- Success Header -->
|
||||
<div class="success-header">
|
||||
<div class="success-header__icon">✅</div>
|
||||
<h1 class="success-header__title">참여 완료!</h1>
|
||||
<p class="success-header__desc">이벤트 참여가 완료되었습니다</p>
|
||||
</div>
|
||||
|
||||
<!-- Ticket Card -->
|
||||
<div class="ticket-card">
|
||||
<div class="ticket-card__label">응모번호</div>
|
||||
<div class="ticket-card__number" id="ticketNumber">A-12345678</div>
|
||||
<button class="ticket-card__copy" onclick="copyTicketNumber()">
|
||||
📋 복사하기
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Drawing Info -->
|
||||
<div class="drawing-info">
|
||||
<div class="drawing-info__row">
|
||||
<div class="drawing-info__icon">📅</div>
|
||||
<div class="drawing-info__content">
|
||||
<div class="drawing-info__label">당첨 발표일</div>
|
||||
<div class="drawing-info__value" id="drawingDate">2025-12-31</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="drawing-info__row">
|
||||
<div class="drawing-info__icon">📱</div>
|
||||
<div class="drawing-info__content">
|
||||
<div class="drawing-info__label">당첨자 통보 방법</div>
|
||||
<div class="drawing-info__value">SMS / 알림톡</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notice -->
|
||||
<div class="notice-card">
|
||||
<div class="notice-card__title">
|
||||
💡 안내사항
|
||||
</div>
|
||||
<div class="notice-card__text">
|
||||
당첨 시 입력하신 전화번호로 SMS/알림톡이 발송됩니다.<br>
|
||||
응모번호는 당첨 확인 시 필요하니 꼭 기억해주세요!
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Store Info -->
|
||||
<div class="store-info">
|
||||
<div class="store-info__title">매장 정보</div>
|
||||
|
||||
<div class="store-info__item">
|
||||
<div class="store-info__icon">🏪</div>
|
||||
<div class="store-info__content" id="storeName">수원 왕갈비통닭</div>
|
||||
</div>
|
||||
|
||||
<div class="store-info__item">
|
||||
<div class="store-info__icon">📍</div>
|
||||
<div class="store-info__content" id="storeAddress">
|
||||
경기도 수원시 팔달구 xxx-xxx
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="store-info__item">
|
||||
<div class="store-info__icon">☎️</div>
|
||||
<div class="store-info__content" id="storePhone">031-XXX-XXXX</div>
|
||||
</div>
|
||||
|
||||
<button class="store-info__button" onclick="openMap()">
|
||||
매장 위치 보기
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Share Section -->
|
||||
<div class="share-section">
|
||||
<div class="share-section__title">
|
||||
💬 친구에게 공유하기
|
||||
</div>
|
||||
|
||||
<div class="share-buttons">
|
||||
<button class="share-button" onclick="shareToSNS('instagram')">
|
||||
<div class="share-button__icon">📷</div>
|
||||
<div class="share-button__label">Instagram</div>
|
||||
</button>
|
||||
|
||||
<button class="share-button" onclick="shareToSNS('kakao')">
|
||||
<div class="share-button__icon">💬</div>
|
||||
<div class="share-button__label">KakaoTalk</div>
|
||||
</button>
|
||||
|
||||
<button class="share-button" onclick="shareToSNS('link')">
|
||||
<div class="share-button__icon">🔗</div>
|
||||
<div class="share-button__label">링크 복사</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Page Script -->
|
||||
<script>
|
||||
// AppState object with showToast method
|
||||
const AppState = {
|
||||
showToast: (message) => {
|
||||
alert(message);
|
||||
}
|
||||
};
|
||||
|
||||
// Storage utility
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Elements
|
||||
const ticketNumber = document.getElementById('ticketNumber');
|
||||
const drawingDate = document.getElementById('drawingDate');
|
||||
const storeName = document.getElementById('storeName');
|
||||
const storeAddress = document.getElementById('storeAddress');
|
||||
const storePhone = document.getElementById('storePhone');
|
||||
const confettiContainer = document.getElementById('confetti');
|
||||
|
||||
// Initialize
|
||||
function init() {
|
||||
// Load participation data
|
||||
const participationData = storage.get('participationData');
|
||||
|
||||
if (participationData && participationData.ticketNumber) {
|
||||
ticketNumber.textContent = participationData.ticketNumber;
|
||||
}
|
||||
|
||||
// Load event data (would come from API in real app)
|
||||
const eventDraft = storage.get('eventDraft');
|
||||
const storeData = storage.get('storeData');
|
||||
|
||||
if (storeData) {
|
||||
storeName.textContent = storeData.businessName || '수원 왕갈비통닭';
|
||||
storeAddress.textContent = storeData.address || '경기도 수원시 팔달구 xxx-xxx';
|
||||
storePhone.textContent = storeData.phone || '031-XXX-XXXX';
|
||||
}
|
||||
|
||||
// Show confetti animation
|
||||
createConfetti();
|
||||
}
|
||||
|
||||
// Create confetti animation
|
||||
function createConfetti() {
|
||||
const colors = ['#E31E24', '#0066FF', '#00C853', '#FFA000', '#9C27B0'];
|
||||
const confettiCount = 50;
|
||||
|
||||
for (let i = 0; i < confettiCount; i++) {
|
||||
const confetti = document.createElement('div');
|
||||
confetti.className = 'confetti-piece';
|
||||
|
||||
// Random positioning
|
||||
confetti.style.left = Math.random() * 100 + '%';
|
||||
confetti.style.animationDelay = Math.random() * 0.5 + 's';
|
||||
confetti.style.animationDuration = (Math.random() * 2 + 2) + 's';
|
||||
|
||||
// Random color
|
||||
confetti.style.background = colors[Math.floor(Math.random() * colors.length)];
|
||||
|
||||
// Random size
|
||||
const size = Math.random() * 5 + 5;
|
||||
confetti.style.width = size + 'px';
|
||||
confetti.style.height = size + 'px';
|
||||
|
||||
confettiContainer.appendChild(confetti);
|
||||
|
||||
// Remove after animation
|
||||
setTimeout(() => {
|
||||
confetti.remove();
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy ticket number
|
||||
function copyTicketNumber() {
|
||||
const number = ticketNumber.textContent;
|
||||
|
||||
// Try using Clipboard API
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(number).then(() => {
|
||||
AppState.showToast('응모번호가 복사되었습니다');
|
||||
}).catch(() => {
|
||||
fallbackCopy(number);
|
||||
});
|
||||
} else {
|
||||
fallbackCopy(number);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback copy method
|
||||
function fallbackCopy(text) {
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.opacity = '0';
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
AppState.showToast('응모번호가 복사되었습니다');
|
||||
} catch (err) {
|
||||
AppState.showToast('복사에 실패했습니다');
|
||||
}
|
||||
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
|
||||
// Open map
|
||||
function openMap() {
|
||||
const address = storeAddress.textContent;
|
||||
|
||||
// In real app, would use actual map service
|
||||
AppState.showToast('매장 위치를 지도 앱에서 엽니다');
|
||||
|
||||
// Example: Open in Kakao Map
|
||||
// const encodedAddress = encodeURIComponent(address);
|
||||
// window.location.href = `kakaomap://search?q=${encodedAddress}`;
|
||||
}
|
||||
|
||||
// Share to SNS
|
||||
function shareToSNS(platform) {
|
||||
const eventUrl = window.location.origin + '/18-이벤트참여.html?source=' + platform;
|
||||
const eventTitle = storeName.textContent + ' 연말 대박 이벤트';
|
||||
const eventText = '친구 초대하고 함께 경품을 받아가세요!';
|
||||
|
||||
switch (platform) {
|
||||
case 'instagram':
|
||||
AppState.showToast('Instagram 스토리로 공유합니다');
|
||||
// In real app: open Instagram share
|
||||
break;
|
||||
|
||||
case 'kakao':
|
||||
AppState.showToast('KakaoTalk으로 공유합니다');
|
||||
// In real app: use Kakao Share SDK
|
||||
// Kakao.Link.sendDefault({ ... });
|
||||
break;
|
||||
|
||||
case 'link':
|
||||
// Copy link
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(eventUrl).then(() => {
|
||||
AppState.showToast('링크가 복사되었습니다');
|
||||
});
|
||||
} else {
|
||||
fallbackCopy(eventUrl);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
AppState.showToast('공유 기능을 준비 중입니다');
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize on load
|
||||
init();
|
||||
|
||||
// Prevent back navigation (optional)
|
||||
history.pushState(null, null, location.href);
|
||||
window.onpopstate = function() {
|
||||
history.go(1);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,939 +0,0 @@
|
||||
<!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>
|
||||
<style>
|
||||
/* CSS Reset */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Design System Variables */
|
||||
:root {
|
||||
/* Colors */
|
||||
--color-primary: #E31E24;
|
||||
--color-primary-hover: #C71820;
|
||||
--color-primary-light: #FF4D52;
|
||||
--color-secondary: #0066FF;
|
||||
--color-secondary-hover: #004DBF;
|
||||
--color-secondary-light: #4D94FF;
|
||||
|
||||
--color-success: #00C853;
|
||||
--color-warning: #FFA000;
|
||||
--color-error: #D32F2F;
|
||||
--color-info: #0288D1;
|
||||
|
||||
--color-gray-50: #F8F9FA;
|
||||
--color-gray-100: #F1F3F5;
|
||||
--color-gray-200: #E9ECEF;
|
||||
--color-gray-300: #DEE2E6;
|
||||
--color-gray-400: #CED4DA;
|
||||
--color-gray-500: #ADB5BD;
|
||||
--color-gray-600: #868E96;
|
||||
--color-gray-700: #495057;
|
||||
--color-gray-800: #343A40;
|
||||
--color-gray-900: #212529;
|
||||
|
||||
--color-white: #FFFFFF;
|
||||
--color-black: #000000;
|
||||
|
||||
/* Typography */
|
||||
--font-family: 'Pretendard Variable', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
||||
|
||||
/* Spacing (4px grid) */
|
||||
--spacing-xs: 4px;
|
||||
--spacing-s: 8px;
|
||||
--spacing-m: 16px;
|
||||
--spacing-l: 24px;
|
||||
--spacing-xl: 32px;
|
||||
--spacing-2xl: 48px;
|
||||
|
||||
/* Border Radius */
|
||||
--radius-s: 4px;
|
||||
--radius-m: 8px;
|
||||
--radius-l: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-s: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-m: 0 4px 6px rgba(0, 0, 0, 0.07);
|
||||
--shadow-l: 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15);
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 150ms ease-in-out;
|
||||
--transition-base: 250ms ease-in-out;
|
||||
--transition-slow: 350ms ease-in-out;
|
||||
|
||||
/* Z-index */
|
||||
--z-header: 100;
|
||||
--z-modal: 200;
|
||||
--z-toast: 300;
|
||||
}
|
||||
|
||||
/* Base Styles */
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: var(--color-gray-900);
|
||||
background-color: var(--color-gray-50);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 56px;
|
||||
background-color: var(--color-white);
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 var(--spacing-m);
|
||||
z-index: var(--z-header);
|
||||
}
|
||||
|
||||
.header__back {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--color-gray-900);
|
||||
font-size: 24px;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.header__back:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.header__title {
|
||||
flex: 1;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
margin: 0 var(--spacing-m);
|
||||
}
|
||||
|
||||
.header__actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.header__action {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--color-gray-900);
|
||||
font-size: 20px;
|
||||
transition: color var(--transition-fast);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header__action:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.refresh-indicator {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: var(--color-success);
|
||||
border-radius: var(--radius-full);
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* Main */
|
||||
.main {
|
||||
padding-top: 56px;
|
||||
padding-bottom: var(--spacing-l);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Section */
|
||||
.section {
|
||||
padding: var(--spacing-l) var(--spacing-m);
|
||||
}
|
||||
|
||||
/* Date Range */
|
||||
.date-range {
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-l);
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.date-range::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.date-range__btn {
|
||||
flex-shrink: 0;
|
||||
padding: var(--spacing-s) var(--spacing-m);
|
||||
background-color: var(--color-white);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
border-radius: var(--radius-full);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--color-gray-700);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.date-range__btn:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.date-range__btn--active {
|
||||
background-color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.date-range__btn--active:hover {
|
||||
background-color: var(--color-primary-hover);
|
||||
border-color: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
/* Stats Grid */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
box-shadow: var(--shadow-s);
|
||||
transition: box-shadow var(--transition-fast);
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
box-shadow: var(--shadow-m);
|
||||
}
|
||||
|
||||
.stat-card__label {
|
||||
font-size: 13px;
|
||||
color: var(--color-gray-600);
|
||||
margin-bottom: var(--spacing-s);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.stat-card__icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.stat-card__value {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.stat-card__value--primary {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.stat-card__value--success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.stat-card__value--info {
|
||||
color: var(--color-info);
|
||||
}
|
||||
|
||||
.stat-card__change {
|
||||
font-size: 12px;
|
||||
color: var(--color-success);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.stat-card__change--negative {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
/* Channel Performance */
|
||||
.channel-performance {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
box-shadow: var(--shadow-s);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.channel-performance__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.channel-performance__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
.channel-performance__link {
|
||||
font-size: 13px;
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.channel-performance__link:hover {
|
||||
color: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.channel-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.channel-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.channel-item__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.channel-item__name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
.channel-item__icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.channel-item__value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
.channel-item__bar {
|
||||
height: 8px;
|
||||
background-color: var(--color-gray-100);
|
||||
border-radius: var(--radius-full);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.channel-item__progress {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--color-primary), var(--color-primary-light));
|
||||
border-radius: var(--radius-full);
|
||||
transition: width var(--transition-slow);
|
||||
}
|
||||
|
||||
/* Recent Activity */
|
||||
.recent-activity {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
box-shadow: var(--shadow-s);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.recent-activity__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.recent-activity__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
.activity-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.activity-item {
|
||||
display: flex;
|
||||
gap: var(--spacing-m);
|
||||
padding: var(--spacing-m) 0;
|
||||
border-bottom: 1px solid var(--color-gray-100);
|
||||
}
|
||||
|
||||
.activity-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.activity-item__icon {
|
||||
flex-shrink: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--color-gray-50);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.activity-item__content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.activity-item__text {
|
||||
font-size: 14px;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.activity-item__time {
|
||||
font-size: 12px;
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
/* Quick Actions */
|
||||
.quick-actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.quick-action {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-s);
|
||||
height: 100px;
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
box-shadow: var(--shadow-s);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
text-decoration: none;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
.quick-action:hover {
|
||||
box-shadow: var(--shadow-m);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.quick-action__icon {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.quick-action__label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Toast */
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: var(--spacing-l);
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(100px);
|
||||
background-color: var(--color-gray-800);
|
||||
color: var(--color-white);
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
box-shadow: var(--shadow-xl);
|
||||
z-index: var(--z-toast);
|
||||
opacity: 0;
|
||||
transition: all var(--transition-base);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toast--visible {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (min-width: 768px) {
|
||||
.header {
|
||||
padding: 0 var(--spacing-l);
|
||||
}
|
||||
|
||||
.section {
|
||||
max-width: 768px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl) var(--spacing-l);
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.section {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<button class="header__back" aria-label="뒤로 가기">←</button>
|
||||
<h1 class="header__title">실시간 대시보드</h1>
|
||||
<div class="header__actions">
|
||||
<button class="header__action" id="refreshBtn" aria-label="새로고침">
|
||||
⟳
|
||||
<span class="refresh-indicator"></span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main -->
|
||||
<main class="main">
|
||||
<div class="section">
|
||||
<!-- Date Range -->
|
||||
<div class="date-range">
|
||||
<button class="date-range__btn" data-range="today">오늘</button>
|
||||
<button class="date-range__btn date-range__btn--active" data-range="week">최근 7일</button>
|
||||
<button class="date-range__btn" data-range="month">최근 30일</button>
|
||||
<button class="date-range__btn" data-range="all">전체</button>
|
||||
</div>
|
||||
|
||||
<!-- Stats Grid -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__label">
|
||||
<span class="stat-card__icon">👥</span>
|
||||
<span>총 참여자</span>
|
||||
</div>
|
||||
<div class="stat-card__value" id="totalParticipants">0</div>
|
||||
<div class="stat-card__change" id="participantsChange">+0%</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__label">
|
||||
<span class="stat-card__icon">🎁</span>
|
||||
<span>당첨자</span>
|
||||
</div>
|
||||
<div class="stat-card__value stat-card__value--primary" id="totalWinners">0</div>
|
||||
<div class="stat-card__change" id="winnersChange">+0%</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__label">
|
||||
<span class="stat-card__icon">📊</span>
|
||||
<span>전환율</span>
|
||||
</div>
|
||||
<div class="stat-card__value stat-card__value--success" id="conversionRate">0%</div>
|
||||
<div class="stat-card__change" id="conversionChange">+0%</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__label">
|
||||
<span class="stat-card__icon">📈</span>
|
||||
<span>조회수</span>
|
||||
</div>
|
||||
<div class="stat-card__value stat-card__value--info" id="totalViews">0</div>
|
||||
<div class="stat-card__change" id="viewsChange">+0%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Channel Performance -->
|
||||
<div class="channel-performance">
|
||||
<div class="channel-performance__header">
|
||||
<h2 class="channel-performance__title">채널별 성과</h2>
|
||||
<a href="22-채널별성과분석.html" class="channel-performance__link">
|
||||
상세보기 →
|
||||
</a>
|
||||
</div>
|
||||
<div class="channel-list" id="channelList">
|
||||
<!-- Channels will be dynamically inserted -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<div class="recent-activity">
|
||||
<div class="recent-activity__header">
|
||||
<h2 class="recent-activity__title">최근 활동</h2>
|
||||
</div>
|
||||
<div class="activity-list" id="activityList">
|
||||
<!-- Activities will be dynamically inserted -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="quick-actions">
|
||||
<a href="20-당첨자명단관리.html" class="quick-action">
|
||||
<span class="quick-action__icon">🎯</span>
|
||||
<span class="quick-action__label">당첨자 관리</span>
|
||||
</a>
|
||||
<a href="22-채널별성과분석.html" class="quick-action">
|
||||
<span class="quick-action__icon">📊</span>
|
||||
<span class="quick-action__label">성과 분석</span>
|
||||
</a>
|
||||
<a href="23-ROI분석.html" class="quick-action">
|
||||
<span class="quick-action__icon">💰</span>
|
||||
<span class="quick-action__label">ROI 분석</span>
|
||||
</a>
|
||||
<a href="24-분석리포트.html" class="quick-action">
|
||||
<span class="quick-action__icon">📄</span>
|
||||
<span class="quick-action__label">리포트</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Toast -->
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
// Utils
|
||||
const $ = (selector) => document.querySelector(selector);
|
||||
const $$ = (selector) => document.querySelectorAll(selector);
|
||||
|
||||
const addClass = (el, className) => el?.classList.add(className);
|
||||
const removeClass = (el, className) => el?.classList.remove(className);
|
||||
const toggleClass = (el, className) => el?.classList.toggle(className);
|
||||
|
||||
// LocalStorage wrapper
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// App State
|
||||
const AppState = {
|
||||
currentRange: 'week',
|
||||
autoRefresh: true,
|
||||
refreshInterval: null,
|
||||
|
||||
showToast(message, duration = 2000) {
|
||||
const toast = $('#toast');
|
||||
toast.textContent = message;
|
||||
addClass(toast, 'toast--visible');
|
||||
|
||||
setTimeout(() => {
|
||||
removeClass(toast, 'toast--visible');
|
||||
}, duration);
|
||||
}
|
||||
};
|
||||
|
||||
// DOM Elements
|
||||
const totalParticipants = $('#totalParticipants');
|
||||
const totalWinners = $('#totalWinners');
|
||||
const conversionRate = $('#conversionRate');
|
||||
const totalViews = $('#totalViews');
|
||||
const participantsChange = $('#participantsChange');
|
||||
const winnersChange = $('#winnersChange');
|
||||
const conversionChange = $('#conversionChange');
|
||||
const viewsChange = $('#viewsChange');
|
||||
const channelList = $('#channelList');
|
||||
const activityList = $('#activityList');
|
||||
const dateRangeBtns = $$('.date-range__btn');
|
||||
const refreshBtn = $('#refreshBtn');
|
||||
const backBtn = $('.header__back');
|
||||
|
||||
// Generate Mock Data
|
||||
function generateMockData(range) {
|
||||
// Base multipliers for different ranges
|
||||
const multipliers = {
|
||||
today: 0.1,
|
||||
week: 0.5,
|
||||
month: 1,
|
||||
all: 1.5
|
||||
};
|
||||
|
||||
const multiplier = multipliers[range] || 1;
|
||||
|
||||
return {
|
||||
totalParticipants: Math.floor(1247 * multiplier),
|
||||
totalWinners: Math.floor(156 * multiplier),
|
||||
totalViews: Math.floor(5432 * multiplier),
|
||||
participantsChange: (Math.random() * 30 + 5).toFixed(1),
|
||||
winnersChange: (Math.random() * 25 + 3).toFixed(1),
|
||||
viewsChange: (Math.random() * 40 + 10).toFixed(1),
|
||||
channels: [
|
||||
{
|
||||
name: 'Instagram',
|
||||
icon: '📷',
|
||||
participants: Math.floor(456 * multiplier),
|
||||
percentage: 36.5
|
||||
},
|
||||
{
|
||||
name: '우리동네TV',
|
||||
icon: '📺',
|
||||
participants: Math.floor(342 * multiplier),
|
||||
percentage: 27.4
|
||||
},
|
||||
{
|
||||
name: 'Naver 블로그',
|
||||
icon: '📝',
|
||||
participants: Math.floor(289 * multiplier),
|
||||
percentage: 23.2
|
||||
},
|
||||
{
|
||||
name: 'KakaoTalk',
|
||||
icon: '💬',
|
||||
participants: Math.floor(160 * multiplier),
|
||||
percentage: 12.8
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
// Generate Recent Activities
|
||||
function generateRecentActivities() {
|
||||
const activities = [
|
||||
{
|
||||
icon: '🎉',
|
||||
text: '새로운 당첨자 15명이 선정되었습니다',
|
||||
time: '5분 전'
|
||||
},
|
||||
{
|
||||
icon: '👤',
|
||||
text: 'Instagram에서 새로운 참여자 23명',
|
||||
time: '12분 전'
|
||||
},
|
||||
{
|
||||
icon: '📊',
|
||||
text: '오늘 조회수가 500회를 돌파했습니다',
|
||||
time: '1시간 전'
|
||||
},
|
||||
{
|
||||
icon: '💰',
|
||||
text: '경품 지급이 완료되었습니다 (12건)',
|
||||
time: '2시간 전'
|
||||
},
|
||||
{
|
||||
icon: '📱',
|
||||
text: '당첨 알림이 발송되었습니다',
|
||||
time: '3시간 전'
|
||||
}
|
||||
];
|
||||
|
||||
return activities;
|
||||
}
|
||||
|
||||
// Format Number
|
||||
function formatNumber(num) {
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
}
|
||||
|
||||
// Render Stats
|
||||
function renderStats(data) {
|
||||
totalParticipants.textContent = formatNumber(data.totalParticipants);
|
||||
totalWinners.textContent = formatNumber(data.totalWinners);
|
||||
totalViews.textContent = formatNumber(data.totalViews);
|
||||
|
||||
const conversion = ((data.totalWinners / data.totalParticipants) * 100).toFixed(1);
|
||||
conversionRate.textContent = `${conversion}%`;
|
||||
|
||||
participantsChange.textContent = `+${data.participantsChange}%`;
|
||||
winnersChange.textContent = `+${data.winnersChange}%`;
|
||||
viewsChange.textContent = `+${data.viewsChange}%`;
|
||||
conversionChange.textContent = `+${(Math.random() * 5 + 1).toFixed(1)}%`;
|
||||
|
||||
// Animate values
|
||||
animateValue(totalParticipants, 0, data.totalParticipants, 1000);
|
||||
animateValue(totalWinners, 0, data.totalWinners, 1000);
|
||||
animateValue(totalViews, 0, data.totalViews, 1000);
|
||||
}
|
||||
|
||||
// Animate Value
|
||||
function animateValue(element, start, end, duration) {
|
||||
const range = end - start;
|
||||
const increment = range / (duration / 16);
|
||||
let current = start;
|
||||
|
||||
const timer = setInterval(() => {
|
||||
current += increment;
|
||||
if (current >= end) {
|
||||
current = end;
|
||||
clearInterval(timer);
|
||||
}
|
||||
element.textContent = formatNumber(Math.floor(current));
|
||||
}, 16);
|
||||
}
|
||||
|
||||
// Render Channels
|
||||
function renderChannels(channels) {
|
||||
channelList.innerHTML = channels.map(channel => `
|
||||
<div class="channel-item">
|
||||
<div class="channel-item__header">
|
||||
<div class="channel-item__name">
|
||||
<span class="channel-item__icon">${channel.icon}</span>
|
||||
<span>${channel.name}</span>
|
||||
</div>
|
||||
<div class="channel-item__value">${formatNumber(channel.participants)}명</div>
|
||||
</div>
|
||||
<div class="channel-item__bar">
|
||||
<div class="channel-item__progress" style="width: ${channel.percentage}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Render Activities
|
||||
function renderActivities(activities) {
|
||||
activityList.innerHTML = activities.map(activity => `
|
||||
<div class="activity-item">
|
||||
<div class="activity-item__icon">${activity.icon}</div>
|
||||
<div class="activity-item__content">
|
||||
<div class="activity-item__text">${activity.text}</div>
|
||||
<div class="activity-item__time">${activity.time}</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Update Dashboard
|
||||
function updateDashboard(range = AppState.currentRange, showToast = false) {
|
||||
const data = generateMockData(range);
|
||||
const activities = generateRecentActivities();
|
||||
|
||||
renderStats(data);
|
||||
renderChannels(data.channels);
|
||||
renderActivities(activities);
|
||||
|
||||
if (showToast) {
|
||||
AppState.showToast('데이터가 업데이트되었습니다');
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Date Range
|
||||
dateRangeBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const range = btn.dataset.range;
|
||||
|
||||
dateRangeBtns.forEach(b => removeClass(b, 'date-range__btn--active'));
|
||||
addClass(btn, 'date-range__btn--active');
|
||||
|
||||
AppState.currentRange = range;
|
||||
updateDashboard(range, true);
|
||||
});
|
||||
});
|
||||
|
||||
// Handle Refresh
|
||||
refreshBtn.addEventListener('click', () => {
|
||||
updateDashboard(AppState.currentRange, true);
|
||||
});
|
||||
|
||||
// Handle Back
|
||||
backBtn.addEventListener('click', () => {
|
||||
window.location.href = '21.5-홈.html';
|
||||
});
|
||||
|
||||
// Auto Refresh
|
||||
function startAutoRefresh() {
|
||||
if (AppState.autoRefresh) {
|
||||
AppState.refreshInterval = setInterval(() => {
|
||||
updateDashboard(AppState.currentRange, false);
|
||||
}, 30000); // Refresh every 30 seconds
|
||||
}
|
||||
}
|
||||
|
||||
function stopAutoRefresh() {
|
||||
if (AppState.refreshInterval) {
|
||||
clearInterval(AppState.refreshInterval);
|
||||
AppState.refreshInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Page Visibility
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.hidden) {
|
||||
stopAutoRefresh();
|
||||
} else {
|
||||
startAutoRefresh();
|
||||
updateDashboard(AppState.currentRange, false);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
function init() {
|
||||
updateDashboard('week', false);
|
||||
startAutoRefresh();
|
||||
}
|
||||
|
||||
// Cleanup on unload
|
||||
window.addEventListener('beforeunload', () => {
|
||||
stopAutoRefresh();
|
||||
});
|
||||
|
||||
// Start
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,837 +0,0 @@
|
||||
<!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>
|
||||
<style>
|
||||
/* CSS Reset */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Design System Variables */
|
||||
:root {
|
||||
/* Colors */
|
||||
--color-primary: #E31E24;
|
||||
--color-primary-hover: #C71820;
|
||||
--color-primary-light: #FF4D52;
|
||||
--color-secondary: #0066FF;
|
||||
--color-secondary-hover: #004DBF;
|
||||
--color-secondary-light: #4D94FF;
|
||||
|
||||
--color-success: #00C853;
|
||||
--color-warning: #FFA000;
|
||||
--color-error: #D32F2F;
|
||||
--color-info: #0288D1;
|
||||
|
||||
--color-gray-50: #F8F9FA;
|
||||
--color-gray-100: #F1F3F5;
|
||||
--color-gray-200: #E9ECEF;
|
||||
--color-gray-300: #DEE2E6;
|
||||
--color-gray-400: #CED4DA;
|
||||
--color-gray-500: #ADB5BD;
|
||||
--color-gray-600: #868E96;
|
||||
--color-gray-700: #495057;
|
||||
--color-gray-800: #343A40;
|
||||
--color-gray-900: #212529;
|
||||
|
||||
--color-white: #FFFFFF;
|
||||
--color-black: #000000;
|
||||
|
||||
/* Typography */
|
||||
--font-family: 'Pretendard Variable', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
||||
|
||||
/* Spacing (4px grid) */
|
||||
--spacing-xs: 4px;
|
||||
--spacing-s: 8px;
|
||||
--spacing-m: 16px;
|
||||
--spacing-l: 24px;
|
||||
--spacing-xl: 32px;
|
||||
--spacing-2xl: 48px;
|
||||
|
||||
/* Border Radius */
|
||||
--radius-s: 4px;
|
||||
--radius-m: 8px;
|
||||
--radius-l: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-s: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-m: 0 4px 6px rgba(0, 0, 0, 0.07);
|
||||
--shadow-l: 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15);
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 150ms ease-in-out;
|
||||
--transition-base: 250ms ease-in-out;
|
||||
--transition-slow: 350ms ease-in-out;
|
||||
|
||||
/* Z-index */
|
||||
--z-header: 100;
|
||||
--z-modal: 200;
|
||||
--z-toast: 300;
|
||||
}
|
||||
|
||||
/* Base Styles */
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: var(--color-gray-900);
|
||||
background-color: var(--color-gray-50);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 56px;
|
||||
background-color: var(--color-white);
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 var(--spacing-m);
|
||||
z-index: var(--z-header);
|
||||
}
|
||||
|
||||
.header__back {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--color-gray-900);
|
||||
font-size: 24px;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.header__back:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.header__title {
|
||||
flex: 1;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
margin: 0 var(--spacing-m);
|
||||
}
|
||||
|
||||
.header__action {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--color-gray-900);
|
||||
font-size: 20px;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.header__action:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* Main */
|
||||
.main {
|
||||
padding-top: 56px;
|
||||
padding-bottom: 80px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Section */
|
||||
.section {
|
||||
padding: var(--spacing-l) var(--spacing-m);
|
||||
}
|
||||
|
||||
/* Period Filter */
|
||||
.period-filter {
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-l);
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.period-filter::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.period-filter__btn {
|
||||
flex-shrink: 0;
|
||||
padding: var(--spacing-s) var(--spacing-m);
|
||||
background-color: var(--color-white);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
border-radius: var(--radius-full);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--color-gray-700);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.period-filter__btn:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.period-filter__btn--active {
|
||||
background-color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
/* Summary Stats */
|
||||
.summary-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.summary-stat {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
text-align: center;
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
.summary-stat__label {
|
||||
font-size: 12px;
|
||||
color: var(--color-gray-600);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.summary-stat__value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
.summary-stat__value--primary {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* Channel Cards */
|
||||
.channel-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.channel-card {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
box-shadow: var(--shadow-s);
|
||||
transition: box-shadow var(--transition-fast);
|
||||
}
|
||||
|
||||
.channel-card:hover {
|
||||
box-shadow: var(--shadow-m);
|
||||
}
|
||||
|
||||
.channel-card__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.channel-card__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
.channel-card__icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.channel-card__rank {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-color: var(--color-gray-100);
|
||||
border-radius: var(--radius-full);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
.channel-card__rank--top {
|
||||
background-color: #FFD700;
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.channel-card__stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.channel-stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.channel-stat__label {
|
||||
font-size: 12px;
|
||||
color: var(--color-gray-600);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.channel-stat__value {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
.channel-stat__change {
|
||||
font-size: 12px;
|
||||
color: var(--color-success);
|
||||
font-weight: 500;
|
||||
margin-top: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.channel-stat__change--negative {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.channel-card__chart {
|
||||
height: 100px;
|
||||
background-color: var(--color-gray-50);
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--spacing-s);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.chart-bar {
|
||||
flex: 1;
|
||||
background: linear-gradient(to top, var(--color-primary), var(--color-primary-light));
|
||||
border-radius: var(--radius-s) var(--radius-s) 0 0;
|
||||
min-height: 20%;
|
||||
transition: all var(--transition-base);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chart-bar:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Comparison Table */
|
||||
.comparison-table {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
box-shadow: var(--shadow-s);
|
||||
margin-bottom: var(--spacing-l);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.comparison-table__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.table th {
|
||||
padding: var(--spacing-s);
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-600);
|
||||
border-bottom: 2px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
.table td {
|
||||
padding: var(--spacing-m) var(--spacing-s);
|
||||
font-size: 14px;
|
||||
color: var(--color-gray-900);
|
||||
border-bottom: 1px solid var(--color-gray-100);
|
||||
}
|
||||
|
||||
.table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.table__channel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.table__icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* Bottom Actions */
|
||||
.bottom-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--color-white);
|
||||
border-top: 1px solid var(--color-gray-200);
|
||||
padding: var(--spacing-m);
|
||||
z-index: var(--z-header);
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
height: 52px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-s);
|
||||
border: none;
|
||||
border-radius: var(--radius-m);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
/* Toast */
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 100px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(100px);
|
||||
background-color: var(--color-gray-800);
|
||||
color: var(--color-white);
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
box-shadow: var(--shadow-xl);
|
||||
z-index: var(--z-toast);
|
||||
opacity: 0;
|
||||
transition: all var(--transition-base);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toast--visible {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (min-width: 768px) {
|
||||
.header {
|
||||
padding: 0 var(--spacing-l);
|
||||
}
|
||||
|
||||
.section {
|
||||
max-width: 768px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl) var(--spacing-l);
|
||||
}
|
||||
|
||||
.channel-card__stats {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
max-width: 768px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-radius: var(--radius-l) var(--radius-l) 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.section {
|
||||
max-width: 1024px;
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<button class="header__back" aria-label="뒤로 가기">←</button>
|
||||
<h1 class="header__title">채널별 성과 분석</h1>
|
||||
<button class="header__action" id="refreshBtn" aria-label="새로고침">⟳</button>
|
||||
</header>
|
||||
|
||||
<!-- Main -->
|
||||
<main class="main">
|
||||
<div class="section">
|
||||
<!-- Period Filter -->
|
||||
<div class="period-filter">
|
||||
<button class="period-filter__btn" data-period="today">오늘</button>
|
||||
<button class="period-filter__btn period-filter__btn--active" data-period="week">최근 7일</button>
|
||||
<button class="period-filter__btn" data-period="month">최근 30일</button>
|
||||
<button class="period-filter__btn" data-period="all">전체</button>
|
||||
</div>
|
||||
|
||||
<!-- Summary Stats -->
|
||||
<div class="summary-stats">
|
||||
<div class="summary-stat">
|
||||
<div class="summary-stat__label">총 참여자</div>
|
||||
<div class="summary-stat__value" id="totalParticipants">0</div>
|
||||
</div>
|
||||
<div class="summary-stat">
|
||||
<div class="summary-stat__label">평균 전환율</div>
|
||||
<div class="summary-stat__value summary-stat__value--primary" id="avgConversion">0%</div>
|
||||
</div>
|
||||
<div class="summary-stat">
|
||||
<div class="summary-stat__label">활성 채널</div>
|
||||
<div class="summary-stat__value" id="activeChannels">0</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Channel Cards -->
|
||||
<div class="channel-cards" id="channelCards">
|
||||
<!-- Channel cards will be dynamically inserted -->
|
||||
</div>
|
||||
|
||||
<!-- Comparison Table -->
|
||||
<div class="comparison-table">
|
||||
<h2 class="comparison-table__title">채널 비교</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>채널</th>
|
||||
<th>참여자</th>
|
||||
<th>전환율</th>
|
||||
<th>순위</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="comparisonTableBody">
|
||||
<!-- Table rows will be dynamically inserted -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Bottom Actions -->
|
||||
<div class="bottom-actions">
|
||||
<button class="btn" id="exportBtn">
|
||||
<span>📊</span>
|
||||
<span>분석 리포트 다운로드</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Toast -->
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
// Utils
|
||||
const $ = (selector) => document.querySelector(selector);
|
||||
const $$ = (selector) => document.querySelectorAll(selector);
|
||||
|
||||
const addClass = (el, className) => el?.classList.add(className);
|
||||
const removeClass = (el, className) => el?.classList.remove(className);
|
||||
|
||||
// LocalStorage wrapper
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// App State
|
||||
const AppState = {
|
||||
currentPeriod: 'week',
|
||||
|
||||
showToast(message, duration = 2000) {
|
||||
const toast = $('#toast');
|
||||
toast.textContent = message;
|
||||
addClass(toast, 'toast--visible');
|
||||
|
||||
setTimeout(() => {
|
||||
removeClass(toast, 'toast--visible');
|
||||
}, duration);
|
||||
}
|
||||
};
|
||||
|
||||
// DOM Elements
|
||||
const totalParticipants = $('#totalParticipants');
|
||||
const avgConversion = $('#avgConversion');
|
||||
const activeChannels = $('#activeChannels');
|
||||
const channelCards = $('#channelCards');
|
||||
const comparisonTableBody = $('#comparisonTableBody');
|
||||
const periodFilterBtns = $$('.period-filter__btn');
|
||||
const refreshBtn = $('#refreshBtn');
|
||||
const exportBtn = $('#exportBtn');
|
||||
const backBtn = $('.header__back');
|
||||
|
||||
// Generate Mock Channel Data
|
||||
function generateChannelData(period) {
|
||||
const multipliers = {
|
||||
today: 0.1,
|
||||
week: 0.5,
|
||||
month: 1,
|
||||
all: 1.5
|
||||
};
|
||||
|
||||
const multiplier = multipliers[period] || 1;
|
||||
|
||||
const channels = [
|
||||
{
|
||||
id: 'instagram',
|
||||
name: 'Instagram',
|
||||
icon: '📷',
|
||||
participants: Math.floor(456 * multiplier),
|
||||
views: Math.floor(2340 * multiplier),
|
||||
clicks: Math.floor(1234 * multiplier),
|
||||
conversion: 36.9,
|
||||
change: '+12.5',
|
||||
trend: [30, 45, 60, 75, 85, 70, 90]
|
||||
},
|
||||
{
|
||||
id: 'kt-neighborhood',
|
||||
name: '우리동네TV',
|
||||
icon: '📺',
|
||||
participants: Math.floor(342 * multiplier),
|
||||
views: Math.floor(1890 * multiplier),
|
||||
clicks: Math.floor(987 * multiplier),
|
||||
conversion: 27.4,
|
||||
change: '+8.3',
|
||||
trend: [40, 50, 45, 60, 70, 65, 75]
|
||||
},
|
||||
{
|
||||
id: 'naver',
|
||||
name: 'Naver 블로그',
|
||||
icon: '📝',
|
||||
participants: Math.floor(289 * multiplier),
|
||||
views: Math.floor(1567 * multiplier),
|
||||
clicks: Math.floor(823 * multiplier),
|
||||
conversion: 23.2,
|
||||
change: '+5.7',
|
||||
trend: [25, 35, 50, 55, 60, 58, 65]
|
||||
},
|
||||
{
|
||||
id: 'kakao',
|
||||
name: 'KakaoTalk',
|
||||
icon: '💬',
|
||||
participants: Math.floor(160 * multiplier),
|
||||
views: Math.floor(890 * multiplier),
|
||||
clicks: Math.floor(456 * multiplier),
|
||||
conversion: 12.8,
|
||||
change: '-2.1',
|
||||
trend: [50, 45, 40, 38, 35, 33, 30]
|
||||
}
|
||||
];
|
||||
|
||||
return channels.sort((a, b) => b.participants - a.participants);
|
||||
}
|
||||
|
||||
// Format Number
|
||||
function formatNumber(num) {
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
}
|
||||
|
||||
// Render Summary
|
||||
function renderSummary(channels) {
|
||||
const total = channels.reduce((sum, ch) => sum + ch.participants, 0);
|
||||
const avgConv = (channels.reduce((sum, ch) => sum + ch.conversion, 0) / channels.length).toFixed(1);
|
||||
|
||||
totalParticipants.textContent = formatNumber(total);
|
||||
avgConversion.textContent = `${avgConv}%`;
|
||||
activeChannels.textContent = channels.length;
|
||||
}
|
||||
|
||||
// Render Channel Cards
|
||||
function renderChannelCards(channels) {
|
||||
channelCards.innerHTML = channels.map((channel, index) => `
|
||||
<div class="channel-card">
|
||||
<div class="channel-card__header">
|
||||
<div class="channel-card__title">
|
||||
<span class="channel-card__icon">${channel.icon}</span>
|
||||
<span>${channel.name}</span>
|
||||
</div>
|
||||
<div class="channel-card__rank ${index === 0 ? 'channel-card__rank--top' : ''}">
|
||||
${index + 1}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="channel-card__stats">
|
||||
<div class="channel-stat">
|
||||
<div class="channel-stat__label">참여자</div>
|
||||
<div class="channel-stat__value">${formatNumber(channel.participants)}</div>
|
||||
<div class="channel-stat__change ${channel.change.startsWith('-') ? 'channel-stat__change--negative' : ''}">
|
||||
${channel.change}%
|
||||
</div>
|
||||
</div>
|
||||
<div class="channel-stat">
|
||||
<div class="channel-stat__label">조회수</div>
|
||||
<div class="channel-stat__value">${formatNumber(channel.views)}</div>
|
||||
</div>
|
||||
<div class="channel-stat">
|
||||
<div class="channel-stat__label">클릭수</div>
|
||||
<div class="channel-stat__value">${formatNumber(channel.clicks)}</div>
|
||||
</div>
|
||||
<div class="channel-stat">
|
||||
<div class="channel-stat__label">전환율</div>
|
||||
<div class="channel-stat__value">${channel.conversion}%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="channel-card__chart">
|
||||
${channel.trend.map(value => `
|
||||
<div class="chart-bar" style="height: ${value}%"></div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Render Comparison Table
|
||||
function renderComparisonTable(channels) {
|
||||
comparisonTableBody.innerHTML = channels.map((channel, index) => `
|
||||
<tr>
|
||||
<td>
|
||||
<div class="table__channel">
|
||||
<span class="table__icon">${channel.icon}</span>
|
||||
<span>${channel.name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>${formatNumber(channel.participants)}명</td>
|
||||
<td>${channel.conversion}%</td>
|
||||
<td>${index + 1}위</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Update Analytics
|
||||
function updateAnalytics(period = AppState.currentPeriod) {
|
||||
const channels = generateChannelData(period);
|
||||
|
||||
renderSummary(channels);
|
||||
renderChannelCards(channels);
|
||||
renderComparisonTable(channels);
|
||||
}
|
||||
|
||||
// Handle Period Filter
|
||||
periodFilterBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const period = btn.dataset.period;
|
||||
|
||||
periodFilterBtns.forEach(b => removeClass(b, 'period-filter__btn--active'));
|
||||
addClass(btn, 'period-filter__btn--active');
|
||||
|
||||
AppState.currentPeriod = period;
|
||||
updateAnalytics(period);
|
||||
AppState.showToast('데이터가 업데이트되었습니다');
|
||||
});
|
||||
});
|
||||
|
||||
// Handle Refresh
|
||||
refreshBtn.addEventListener('click', () => {
|
||||
updateAnalytics(AppState.currentPeriod);
|
||||
AppState.showToast('새로고침 완료');
|
||||
});
|
||||
|
||||
// Handle Export
|
||||
exportBtn.addEventListener('click', () => {
|
||||
AppState.showToast('리포트 다운로드 중...');
|
||||
|
||||
setTimeout(() => {
|
||||
const channels = generateChannelData(AppState.currentPeriod);
|
||||
const csvContent = generateCSV(channels);
|
||||
const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const link = document.createElement('a');
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', `채널별성과분석_${formatDate(new Date())}.csv`);
|
||||
link.style.visibility = 'hidden';
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
AppState.showToast('리포트 다운로드 완료');
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Generate CSV
|
||||
function generateCSV(channels) {
|
||||
const headers = ['채널', '참여자', '조회수', '클릭수', '전환율', '순위'];
|
||||
const rows = channels.map((ch, idx) => [
|
||||
ch.name,
|
||||
ch.participants,
|
||||
ch.views,
|
||||
ch.clicks,
|
||||
ch.conversion + '%',
|
||||
(idx + 1) + '위'
|
||||
]);
|
||||
|
||||
return [headers, ...rows].map(row => row.join(',')).join('\n');
|
||||
}
|
||||
|
||||
// Format Date
|
||||
function formatDate(date) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}${month}${day}`;
|
||||
}
|
||||
|
||||
// Handle Back
|
||||
backBtn.addEventListener('click', () => {
|
||||
window.location.href = '21-실시간대시보드.html';
|
||||
});
|
||||
|
||||
// Initialize
|
||||
function init() {
|
||||
updateAnalytics('week');
|
||||
}
|
||||
|
||||
// Start
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,883 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ROI 분석 - KT AI 이벤트 플랫폼</title>
|
||||
<style>
|
||||
/* CSS Reset */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Design System Variables */
|
||||
:root {
|
||||
/* Colors */
|
||||
--color-primary: #E31E24;
|
||||
--color-primary-hover: #C71820;
|
||||
--color-primary-light: #FF4D52;
|
||||
--color-secondary: #0066FF;
|
||||
--color-secondary-hover: #004DBF;
|
||||
--color-secondary-light: #4D94FF;
|
||||
|
||||
--color-success: #00C853;
|
||||
--color-warning: #FFA000;
|
||||
--color-error: #D32F2F;
|
||||
--color-info: #0288D1;
|
||||
|
||||
--color-gray-50: #F8F9FA;
|
||||
--color-gray-100: #F1F3F5;
|
||||
--color-gray-200: #E9ECEF;
|
||||
--color-gray-300: #DEE2E6;
|
||||
--color-gray-400: #CED4DA;
|
||||
--color-gray-500: #ADB5BD;
|
||||
--color-gray-600: #868E96;
|
||||
--color-gray-700: #495057;
|
||||
--color-gray-800: #343A40;
|
||||
--color-gray-900: #212529;
|
||||
|
||||
--color-white: #FFFFFF;
|
||||
--color-black: #000000;
|
||||
|
||||
/* Typography */
|
||||
--font-family: 'Pretendard Variable', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
||||
|
||||
/* Spacing (4px grid) */
|
||||
--spacing-xs: 4px;
|
||||
--spacing-s: 8px;
|
||||
--spacing-m: 16px;
|
||||
--spacing-l: 24px;
|
||||
--spacing-xl: 32px;
|
||||
--spacing-2xl: 48px;
|
||||
|
||||
/* Border Radius */
|
||||
--radius-s: 4px;
|
||||
--radius-m: 8px;
|
||||
--radius-l: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-s: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-m: 0 4px 6px rgba(0, 0, 0, 0.07);
|
||||
--shadow-l: 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15);
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 150ms ease-in-out;
|
||||
--transition-base: 250ms ease-in-out;
|
||||
--transition-slow: 350ms ease-in-out;
|
||||
|
||||
/* Z-index */
|
||||
--z-header: 100;
|
||||
--z-modal: 200;
|
||||
--z-toast: 300;
|
||||
}
|
||||
|
||||
/* Base Styles */
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: var(--color-gray-900);
|
||||
background-color: var(--color-gray-50);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 56px;
|
||||
background-color: var(--color-white);
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 var(--spacing-m);
|
||||
z-index: var(--z-header);
|
||||
}
|
||||
|
||||
.header__back {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--color-gray-900);
|
||||
font-size: 24px;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.header__back:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.header__title {
|
||||
flex: 1;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
margin: 0 var(--spacing-m);
|
||||
}
|
||||
|
||||
.header__action {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--color-gray-900);
|
||||
font-size: 20px;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.header__action:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* Main */
|
||||
.main {
|
||||
padding-top: 56px;
|
||||
padding-bottom: 80px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Section */
|
||||
.section {
|
||||
padding: var(--spacing-l) var(--spacing-m);
|
||||
}
|
||||
|
||||
/* ROI Summary */
|
||||
.roi-summary {
|
||||
background: linear-gradient(135deg, var(--color-primary), var(--color-primary-light));
|
||||
border-radius: var(--radius-xl);
|
||||
padding: var(--spacing-xl);
|
||||
color: var(--color-white);
|
||||
margin-bottom: var(--spacing-l);
|
||||
box-shadow: var(--shadow-l);
|
||||
}
|
||||
|
||||
.roi-summary__label {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.roi-summary__value {
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.roi-summary__description {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Metrics Grid */
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
.metric-card__label {
|
||||
font-size: 13px;
|
||||
color: var(--color-gray-600);
|
||||
margin-bottom: var(--spacing-s);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.metric-card__icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.metric-card__value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.metric-card__value--success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.metric-card__value--error {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.metric-card__detail {
|
||||
font-size: 12px;
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
/* Cost Breakdown */
|
||||
.cost-breakdown {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
box-shadow: var(--shadow-s);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.cost-breakdown__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.cost-breakdown__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
.cost-breakdown__total {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.cost-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.cost-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.cost-item__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cost-item__label {
|
||||
font-size: 14px;
|
||||
color: var(--color-gray-900);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.cost-item__value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
.cost-item__bar {
|
||||
height: 8px;
|
||||
background-color: var(--color-gray-100);
|
||||
border-radius: var(--radius-full);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cost-item__progress {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--color-error), #FF6B6B);
|
||||
border-radius: var(--radius-full);
|
||||
transition: width var(--transition-slow);
|
||||
}
|
||||
|
||||
/* Revenue Breakdown */
|
||||
.revenue-breakdown {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
box-shadow: var(--shadow-s);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.revenue-breakdown__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.revenue-breakdown__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
.revenue-breakdown__total {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.revenue-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.revenue-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.revenue-item__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.revenue-item__label {
|
||||
font-size: 14px;
|
||||
color: var(--color-gray-900);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.revenue-item__value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
.revenue-item__bar {
|
||||
height: 8px;
|
||||
background-color: var(--color-gray-100);
|
||||
border-radius: var(--radius-full);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.revenue-item__progress {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--color-success), #4CD964);
|
||||
border-radius: var(--radius-full);
|
||||
transition: width var(--transition-slow);
|
||||
}
|
||||
|
||||
/* Insights */
|
||||
.insights {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
box-shadow: var(--shadow-s);
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.insights__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.insights__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.insight-item {
|
||||
display: flex;
|
||||
gap: var(--spacing-m);
|
||||
padding: var(--spacing-m);
|
||||
background-color: var(--color-gray-50);
|
||||
border-radius: var(--radius-m);
|
||||
}
|
||||
|
||||
.insight-item__icon {
|
||||
flex-shrink: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.insight-item__content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.insight-item__text {
|
||||
font-size: 14px;
|
||||
color: var(--color-gray-900);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Bottom Actions */
|
||||
.bottom-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--color-white);
|
||||
border-top: 1px solid var(--color-gray-200);
|
||||
padding: var(--spacing-m);
|
||||
z-index: var(--z-header);
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
height: 52px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-s);
|
||||
border: none;
|
||||
border-radius: var(--radius-m);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
/* Toast */
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 100px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(100px);
|
||||
background-color: var(--color-gray-800);
|
||||
color: var(--color-white);
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
box-shadow: var(--shadow-xl);
|
||||
z-index: var(--z-toast);
|
||||
opacity: 0;
|
||||
transition: all var(--transition-base);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toast--visible {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (min-width: 768px) {
|
||||
.header {
|
||||
padding: 0 var(--spacing-l);
|
||||
}
|
||||
|
||||
.section {
|
||||
max-width: 768px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl) var(--spacing-l);
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
max-width: 768px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-radius: var(--radius-l) var(--radius-l) 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.section {
|
||||
max-width: 1024px;
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<button class="header__back" aria-label="뒤로 가기">←</button>
|
||||
<h1 class="header__title">ROI 분석</h1>
|
||||
<button class="header__action" id="refreshBtn" aria-label="새로고침">⟳</button>
|
||||
</header>
|
||||
|
||||
<!-- Main -->
|
||||
<main class="main">
|
||||
<div class="section">
|
||||
<!-- ROI Summary -->
|
||||
<div class="roi-summary">
|
||||
<div class="roi-summary__label">투자 대비 수익률 (ROI)</div>
|
||||
<div class="roi-summary__value" id="roiValue">0%</div>
|
||||
<div class="roi-summary__description">
|
||||
이벤트 비용 대비 <strong id="roiMultiple">0배</strong>의 수익을 창출했습니다.
|
||||
지난 달 대비 <strong id="roiGrowth">+0%</strong> 향상되었습니다.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Metrics Grid -->
|
||||
<div class="metrics-grid">
|
||||
<div class="metric-card">
|
||||
<div class="metric-card__label">
|
||||
<span class="metric-card__icon">💰</span>
|
||||
<span>총 수익</span>
|
||||
</div>
|
||||
<div class="metric-card__value metric-card__value--success" id="totalRevenue">0원</div>
|
||||
<div class="metric-card__detail">예상 매출 증가분</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<div class="metric-card__label">
|
||||
<span class="metric-card__icon">💸</span>
|
||||
<span>총 비용</span>
|
||||
</div>
|
||||
<div class="metric-card__value metric-card__value--error" id="totalCost">0원</div>
|
||||
<div class="metric-card__detail">이벤트 운영 비용</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<div class="metric-card__label">
|
||||
<span class="metric-card__icon">📈</span>
|
||||
<span>순이익</span>
|
||||
</div>
|
||||
<div class="metric-card__value" id="netProfit">0원</div>
|
||||
<div class="metric-card__detail">수익 - 비용</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<div class="metric-card__label">
|
||||
<span class="metric-card__icon">🎯</span>
|
||||
<span>손익분기</span>
|
||||
</div>
|
||||
<div class="metric-card__value" id="breakEven">달성</div>
|
||||
<div class="metric-card__detail">참여자 기준</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cost Breakdown -->
|
||||
<div class="cost-breakdown">
|
||||
<div class="cost-breakdown__header">
|
||||
<h2 class="cost-breakdown__title">비용 상세</h2>
|
||||
<div class="cost-breakdown__total" id="costTotal">0원</div>
|
||||
</div>
|
||||
<div class="cost-list" id="costList">
|
||||
<!-- Cost items will be dynamically inserted -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Revenue Breakdown -->
|
||||
<div class="revenue-breakdown">
|
||||
<div class="revenue-breakdown__header">
|
||||
<h2 class="revenue-breakdown__title">수익 상세</h2>
|
||||
<div class="revenue-breakdown__total" id="revenueTotal">0원</div>
|
||||
</div>
|
||||
<div class="revenue-list" id="revenueList">
|
||||
<!-- Revenue items will be dynamically inserted -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Insights -->
|
||||
<div class="insights">
|
||||
<h2 class="insights__title">인사이트</h2>
|
||||
<div class="insights__list" id="insightsList">
|
||||
<!-- Insights will be dynamically inserted -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Bottom Actions -->
|
||||
<div class="bottom-actions">
|
||||
<button class="btn" id="exportBtn">
|
||||
<span>📊</span>
|
||||
<span>ROI 리포트 다운로드</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Toast -->
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
// Utils
|
||||
const $ = (selector) => document.querySelector(selector);
|
||||
|
||||
const addClass = (el, className) => el?.classList.add(className);
|
||||
const removeClass = (el, className) => el?.classList.remove(className);
|
||||
|
||||
// App State
|
||||
const AppState = {
|
||||
showToast(message, duration = 2000) {
|
||||
const toast = $('#toast');
|
||||
toast.textContent = message;
|
||||
addClass(toast, 'toast--visible');
|
||||
|
||||
setTimeout(() => {
|
||||
removeClass(toast, 'toast--visible');
|
||||
}, duration);
|
||||
}
|
||||
};
|
||||
|
||||
// DOM Elements
|
||||
const roiValue = $('#roiValue');
|
||||
const roiMultiple = $('#roiMultiple');
|
||||
const roiGrowth = $('#roiGrowth');
|
||||
const totalRevenue = $('#totalRevenue');
|
||||
const totalCost = $('#totalCost');
|
||||
const netProfit = $('#netProfit');
|
||||
const breakEven = $('#breakEven');
|
||||
const costTotal = $('#costTotal');
|
||||
const revenueTotal = $('#revenueTotal');
|
||||
const costList = $('#costList');
|
||||
const revenueList = $('#revenueList');
|
||||
const insightsList = $('#insightsList');
|
||||
const refreshBtn = $('#refreshBtn');
|
||||
const exportBtn = $('#exportBtn');
|
||||
const backBtn = $('.header__back');
|
||||
|
||||
// Generate Mock Data
|
||||
function generateROIData() {
|
||||
const costs = [
|
||||
{ label: '경품 비용', value: 1500000, percentage: 45.5 },
|
||||
{ label: '홍보 비용', value: 800000, percentage: 24.2 },
|
||||
{ label: 'AI 서비스 이용료', value: 500000, percentage: 15.2 },
|
||||
{ label: '제작 비용', value: 300000, percentage: 9.1 },
|
||||
{ label: '기타 운영비', value: 200000, percentage: 6.0 }
|
||||
];
|
||||
|
||||
const revenues = [
|
||||
{ label: '신규 고객 매출', value: 8500000, percentage: 56.7 },
|
||||
{ label: '재방문 고객 매출', value: 4200000, percentage: 28.0 },
|
||||
{ label: '브랜드 인지도 향상', value: 1800000, percentage: 12.0 },
|
||||
{ label: 'SNS 바이럴 효과', value: 500000, percentage: 3.3 }
|
||||
];
|
||||
|
||||
const totalCostValue = costs.reduce((sum, item) => sum + item.value, 0);
|
||||
const totalRevenueValue = revenues.reduce((sum, item) => sum + item.value, 0);
|
||||
const netProfitValue = totalRevenueValue - totalCostValue;
|
||||
const roi = ((netProfitValue / totalCostValue) * 100).toFixed(1);
|
||||
const multiple = (totalRevenueValue / totalCostValue).toFixed(1);
|
||||
|
||||
return {
|
||||
costs,
|
||||
revenues,
|
||||
totalCost: totalCostValue,
|
||||
totalRevenue: totalRevenueValue,
|
||||
netProfit: netProfitValue,
|
||||
roi: parseFloat(roi),
|
||||
multiple: parseFloat(multiple),
|
||||
growth: (Math.random() * 30 + 10).toFixed(1)
|
||||
};
|
||||
}
|
||||
|
||||
// Generate Insights
|
||||
function generateInsights(data) {
|
||||
const insights = [];
|
||||
|
||||
if (data.roi > 200) {
|
||||
insights.push({
|
||||
icon: '🎉',
|
||||
text: `ROI ${data.roi}%는 매우 우수한 성과입니다. 이벤트 전략이 효과적으로 작동하고 있습니다.`
|
||||
});
|
||||
} else if (data.roi > 100) {
|
||||
insights.push({
|
||||
icon: '👍',
|
||||
text: `ROI ${data.roi}%는 양호한 수준입니다. 추가 최적화를 통해 더 개선할 수 있습니다.`
|
||||
});
|
||||
}
|
||||
|
||||
const topRevenue = data.revenues[0];
|
||||
insights.push({
|
||||
icon: '💡',
|
||||
text: `${topRevenue.label}이(가) 전체 수익의 ${topRevenue.percentage}%를 차지합니다. 이 채널에 집중하면 더 큰 효과를 얻을 수 있습니다.`
|
||||
});
|
||||
|
||||
const topCost = data.costs[0];
|
||||
insights.push({
|
||||
icon: '💰',
|
||||
text: `${topCost.label}이(가) 전체 비용의 ${topCost.percentage}%입니다. 이 부분을 최적화하면 ROI를 더 개선할 수 있습니다.`
|
||||
});
|
||||
|
||||
return insights;
|
||||
}
|
||||
|
||||
// Format Number
|
||||
function formatNumber(num) {
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
}
|
||||
|
||||
// Format Currency
|
||||
function formatCurrency(num) {
|
||||
return formatNumber(num) + '원';
|
||||
}
|
||||
|
||||
// Render ROI Summary
|
||||
function renderROISummary(data) {
|
||||
roiValue.textContent = `${data.roi}%`;
|
||||
roiMultiple.textContent = `${data.multiple}배`;
|
||||
roiGrowth.textContent = `+${data.growth}%`;
|
||||
|
||||
totalRevenue.textContent = formatCurrency(data.totalRevenue);
|
||||
totalCost.textContent = formatCurrency(data.totalCost);
|
||||
netProfit.textContent = formatCurrency(data.netProfit);
|
||||
|
||||
const breakEvenParticipants = Math.ceil(data.totalCost / 2500); // Average revenue per participant
|
||||
breakEven.textContent = '달성';
|
||||
}
|
||||
|
||||
// Render Costs
|
||||
function renderCosts(costs, total) {
|
||||
costTotal.textContent = formatCurrency(total);
|
||||
|
||||
costList.innerHTML = costs.map(cost => `
|
||||
<div class="cost-item">
|
||||
<div class="cost-item__header">
|
||||
<div class="cost-item__label">${cost.label}</div>
|
||||
<div class="cost-item__value">${formatCurrency(cost.value)}</div>
|
||||
</div>
|
||||
<div class="cost-item__bar">
|
||||
<div class="cost-item__progress" style="width: ${cost.percentage}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Render Revenues
|
||||
function renderRevenues(revenues, total) {
|
||||
revenueTotal.textContent = formatCurrency(total);
|
||||
|
||||
revenueList.innerHTML = revenues.map(revenue => `
|
||||
<div class="revenue-item">
|
||||
<div class="revenue-item__header">
|
||||
<div class="revenue-item__label">${revenue.label}</div>
|
||||
<div class="revenue-item__value">${formatCurrency(revenue.value)}</div>
|
||||
</div>
|
||||
<div class="revenue-item__bar">
|
||||
<div class="revenue-item__progress" style="width: ${revenue.percentage}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Render Insights
|
||||
function renderInsights(insights) {
|
||||
insightsList.innerHTML = insights.map(insight => `
|
||||
<div class="insight-item">
|
||||
<div class="insight-item__icon">${insight.icon}</div>
|
||||
<div class="insight-item__content">
|
||||
<div class="insight-item__text">${insight.text}</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Update ROI Analysis
|
||||
function updateROIAnalysis() {
|
||||
const data = generateROIData();
|
||||
const insights = generateInsights(data);
|
||||
|
||||
renderROISummary(data);
|
||||
renderCosts(data.costs, data.totalCost);
|
||||
renderRevenues(data.revenues, data.totalRevenue);
|
||||
renderInsights(insights);
|
||||
}
|
||||
|
||||
// Handle Refresh
|
||||
refreshBtn.addEventListener('click', () => {
|
||||
updateROIAnalysis();
|
||||
AppState.showToast('데이터가 업데이트되었습니다');
|
||||
});
|
||||
|
||||
// Handle Export
|
||||
exportBtn.addEventListener('click', () => {
|
||||
AppState.showToast('ROI 리포트 다운로드 중...');
|
||||
|
||||
setTimeout(() => {
|
||||
const data = generateROIData();
|
||||
const csvContent = generateCSV(data);
|
||||
const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const link = document.createElement('a');
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', `ROI분석_${formatDate(new Date())}.csv`);
|
||||
link.style.visibility = 'hidden';
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
AppState.showToast('ROI 리포트 다운로드 완료');
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Generate CSV
|
||||
function generateCSV(data) {
|
||||
const summary = [
|
||||
['항목', '금액'],
|
||||
['총 수익', data.totalRevenue + '원'],
|
||||
['총 비용', data.totalCost + '원'],
|
||||
['순이익', data.netProfit + '원'],
|
||||
['ROI', data.roi + '%'],
|
||||
['수익 배수', data.multiple + '배'],
|
||||
[''],
|
||||
['비용 상세', ''],
|
||||
...data.costs.map(c => [c.label, c.value + '원']),
|
||||
[''],
|
||||
['수익 상세', ''],
|
||||
...data.revenues.map(r => [r.label, r.value + '원'])
|
||||
];
|
||||
|
||||
return summary.map(row => row.join(',')).join('\n');
|
||||
}
|
||||
|
||||
// Format Date
|
||||
function formatDate(date) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}${month}${day}`;
|
||||
}
|
||||
|
||||
// Handle Back
|
||||
backBtn.addEventListener('click', () => {
|
||||
window.location.href = '21-실시간대시보드.html';
|
||||
});
|
||||
|
||||
// Initialize
|
||||
function init() {
|
||||
updateROIAnalysis();
|
||||
}
|
||||
|
||||
// Start
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,910 +0,0 @@
|
||||
<!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>
|
||||
<style>
|
||||
/* CSS Reset */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Design System Variables */
|
||||
:root {
|
||||
/* Colors */
|
||||
--color-primary: #E31E24;
|
||||
--color-primary-hover: #C71820;
|
||||
--color-primary-light: #FF4D52;
|
||||
--color-secondary: #0066FF;
|
||||
--color-secondary-hover: #004DBF;
|
||||
--color-secondary-light: #4D94FF;
|
||||
|
||||
--color-success: #00C853;
|
||||
--color-warning: #FFA000;
|
||||
--color-error: #D32F2F;
|
||||
--color-info: #0288D1;
|
||||
|
||||
--color-gray-50: #F8F9FA;
|
||||
--color-gray-100: #F1F3F5;
|
||||
--color-gray-200: #E9ECEF;
|
||||
--color-gray-300: #DEE2E6;
|
||||
--color-gray-400: #CED4DA;
|
||||
--color-gray-500: #ADB5BD;
|
||||
--color-gray-600: #868E96;
|
||||
--color-gray-700: #495057;
|
||||
--color-gray-800: #343A40;
|
||||
--color-gray-900: #212529;
|
||||
|
||||
--color-white: #FFFFFF;
|
||||
--color-black: #000000;
|
||||
|
||||
/* Typography */
|
||||
--font-family: 'Pretendard Variable', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
||||
|
||||
/* Spacing (4px grid) */
|
||||
--spacing-xs: 4px;
|
||||
--spacing-s: 8px;
|
||||
--spacing-m: 16px;
|
||||
--spacing-l: 24px;
|
||||
--spacing-xl: 32px;
|
||||
--spacing-2xl: 48px;
|
||||
|
||||
/* Border Radius */
|
||||
--radius-s: 4px;
|
||||
--radius-m: 8px;
|
||||
--radius-l: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-s: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-m: 0 4px 6px rgba(0, 0, 0, 0.07);
|
||||
--shadow-l: 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15);
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 150ms ease-in-out;
|
||||
--transition-base: 250ms ease-in-out;
|
||||
--transition-slow: 350ms ease-in-out;
|
||||
|
||||
/* Z-index */
|
||||
--z-header: 100;
|
||||
--z-modal: 200;
|
||||
--z-toast: 300;
|
||||
}
|
||||
|
||||
/* Base Styles */
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: var(--color-gray-900);
|
||||
background-color: var(--color-gray-50);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 56px;
|
||||
background-color: var(--color-white);
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 var(--spacing-m);
|
||||
z-index: var(--z-header);
|
||||
}
|
||||
|
||||
.header__back {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--color-gray-900);
|
||||
font-size: 24px;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.header__back:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.header__title {
|
||||
flex: 1;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
margin: 0 var(--spacing-m);
|
||||
}
|
||||
|
||||
.header__actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.header__action {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--color-gray-900);
|
||||
font-size: 20px;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.header__action:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* Main */
|
||||
.main {
|
||||
padding-top: 56px;
|
||||
padding-bottom: 80px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Section */
|
||||
.section {
|
||||
padding: var(--spacing-l) var(--spacing-m);
|
||||
}
|
||||
|
||||
/* Report Header */
|
||||
.report-header {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-l);
|
||||
margin-bottom: var(--spacing-l);
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
.report-header__title {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.report-header__period {
|
||||
font-size: 14px;
|
||||
color: var(--color-gray-600);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.report-header__meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: var(--spacing-m);
|
||||
border-top: 1px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
.report-header__meta-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.report-header__meta-label {
|
||||
font-size: 12px;
|
||||
color: var(--color-gray-600);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.report-header__meta-value {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
/* Executive Summary */
|
||||
.executive-summary {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-l);
|
||||
margin-bottom: var(--spacing-l);
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
.executive-summary__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.executive-summary__text {
|
||||
font-size: 14px;
|
||||
color: var(--color-gray-700);
|
||||
line-height: 1.8;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.executive-summary__highlights {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.highlight-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
padding: var(--spacing-s);
|
||||
background-color: var(--color-gray-50);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: 14px;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
.highlight-item__icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Stats Summary */
|
||||
.stats-summary {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-l);
|
||||
margin-bottom: var(--spacing-l);
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
.stats-summary__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.stat-box {
|
||||
padding: var(--spacing-m);
|
||||
background-color: var(--color-gray-50);
|
||||
border-radius: var(--radius-m);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-box__label {
|
||||
font-size: 12px;
|
||||
color: var(--color-gray-600);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.stat-box__value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.stat-box__value--primary {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.stat-box__value--success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.stat-box__change {
|
||||
font-size: 12px;
|
||||
color: var(--color-success);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Section Block */
|
||||
.section-block {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-l);
|
||||
margin-bottom: var(--spacing-l);
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
.section-block__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.section-block__content {
|
||||
font-size: 14px;
|
||||
color: var(--color-gray-700);
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
/* Data Table */
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: var(--spacing-m);
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
padding: var(--spacing-s);
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-600);
|
||||
background-color: var(--color-gray-50);
|
||||
border-bottom: 2px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
.data-table td {
|
||||
padding: var(--spacing-m) var(--spacing-s);
|
||||
font-size: 14px;
|
||||
color: var(--color-gray-900);
|
||||
border-bottom: 1px solid var(--color-gray-100);
|
||||
}
|
||||
|
||||
.data-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Recommendations */
|
||||
.recommendations {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-l);
|
||||
margin-bottom: var(--spacing-l);
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
.recommendations__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.recommendations__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.recommendation-item {
|
||||
display: flex;
|
||||
gap: var(--spacing-m);
|
||||
padding: var(--spacing-m);
|
||||
background-color: #FFF8E1;
|
||||
border-left: 4px solid var(--color-warning);
|
||||
border-radius: var(--radius-m);
|
||||
}
|
||||
|
||||
.recommendation-item__icon {
|
||||
flex-shrink: 0;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.recommendation-item__content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.recommendation-item__title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.recommendation-item__text {
|
||||
font-size: 13px;
|
||||
color: var(--color-gray-700);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Bottom Actions */
|
||||
.bottom-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--color-white);
|
||||
border-top: 1px solid var(--color-gray-200);
|
||||
padding: var(--spacing-m);
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
z-index: var(--z-header);
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
height: 52px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-s);
|
||||
border: none;
|
||||
border-radius: var(--radius-m);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.btn--secondary {
|
||||
background-color: var(--color-white);
|
||||
color: var(--color-primary);
|
||||
border: 1px solid var(--color-primary);
|
||||
}
|
||||
|
||||
.btn--secondary:hover {
|
||||
background-color: var(--color-gray-50);
|
||||
}
|
||||
|
||||
.btn--primary {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.btn--primary:hover {
|
||||
background-color: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
/* Toast */
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 100px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(100px);
|
||||
background-color: var(--color-gray-800);
|
||||
color: var(--color-white);
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
box-shadow: var(--shadow-xl);
|
||||
z-index: var(--z-toast);
|
||||
opacity: 0;
|
||||
transition: all var(--transition-base);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toast--visible {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Print Styles */
|
||||
@media print {
|
||||
.header,
|
||||
.bottom-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: var(--spacing-m);
|
||||
}
|
||||
|
||||
.report-header,
|
||||
.executive-summary,
|
||||
.stats-summary,
|
||||
.section-block,
|
||||
.recommendations {
|
||||
page-break-inside: avoid;
|
||||
box-shadow: none;
|
||||
border: 1px solid var(--color-gray-200);
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (min-width: 768px) {
|
||||
.header {
|
||||
padding: 0 var(--spacing-l);
|
||||
}
|
||||
|
||||
.section {
|
||||
max-width: 768px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl) var(--spacing-l);
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
max-width: 768px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-radius: var(--radius-l) var(--radius-l) 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.section {
|
||||
max-width: 1024px;
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<button class="header__back" aria-label="뒤로 가기">←</button>
|
||||
<h1 class="header__title">분석 리포트</h1>
|
||||
<div class="header__actions">
|
||||
<button class="header__action" id="shareBtn" aria-label="공유하기">📤</button>
|
||||
<button class="header__action" id="printBtn" aria-label="인쇄하기">🖨</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main -->
|
||||
<main class="main">
|
||||
<div class="section">
|
||||
<!-- Report Header -->
|
||||
<div class="report-header">
|
||||
<h1 class="report-header__title" id="eventTitle">신규 회원 가입 이벤트</h1>
|
||||
<p class="report-header__period" id="reportPeriod">분석 기간: 2025.01.01 - 2025.01.31</p>
|
||||
<div class="report-header__meta">
|
||||
<div class="report-header__meta-item">
|
||||
<div class="report-header__meta-label">보고서 생성일</div>
|
||||
<div class="report-header__meta-value" id="generatedDate">2025.02.01</div>
|
||||
</div>
|
||||
<div class="report-header__meta-item">
|
||||
<div class="report-header__meta-label">작성자</div>
|
||||
<div class="report-header__meta-value" id="author">매장명</div>
|
||||
</div>
|
||||
<div class="report-header__meta-item">
|
||||
<div class="report-header__meta-label">버전</div>
|
||||
<div class="report-header__meta-value">v1.0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Executive Summary -->
|
||||
<div class="executive-summary">
|
||||
<h2 class="executive-summary__title">📊 요약</h2>
|
||||
<p class="executive-summary__text">
|
||||
본 이벤트는 신규 회원 유치를 목적으로 총 30일간 진행되었으며,
|
||||
다양한 채널을 통해 <strong>1,247명</strong>의 참여자를 확보했습니다.
|
||||
투자 대비 <strong>354.2%</strong>의 ROI를 달성하여 목표치를 크게 상회했으며,
|
||||
특히 Instagram 채널에서 가장 우수한 성과를 기록했습니다.
|
||||
</p>
|
||||
<div class="executive-summary__highlights">
|
||||
<div class="highlight-item">
|
||||
<span class="highlight-item__icon">✅</span>
|
||||
<span>목표 대비 124% 달성 (목표: 1,000명, 실제: 1,247명)</span>
|
||||
</div>
|
||||
<div class="highlight-item">
|
||||
<span class="highlight-item__icon">💰</span>
|
||||
<span>ROI 354.2% 달성 (투자 330만원, 수익 1,500만원)</span>
|
||||
</div>
|
||||
<div class="highlight-item">
|
||||
<span class="highlight-item__icon">📈</span>
|
||||
<span>평균 전환율 12.5% (업계 평균 8.3% 대비 우수)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Summary -->
|
||||
<div class="stats-summary">
|
||||
<h2 class="stats-summary__title">📈 핵심 지표</h2>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-box">
|
||||
<div class="stat-box__label">총 참여자</div>
|
||||
<div class="stat-box__value">1,247</div>
|
||||
<div class="stat-box__change">+24%</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="stat-box__label">당첨자</div>
|
||||
<div class="stat-box__value stat-box__value--primary">156</div>
|
||||
<div class="stat-box__change">+18%</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="stat-box__label">전환율</div>
|
||||
<div class="stat-box__value stat-box__value--success">12.5%</div>
|
||||
<div class="stat-box__change">+3.2%</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="stat-box__label">조회수</div>
|
||||
<div class="stat-box__value">5,432</div>
|
||||
<div class="stat-box__change">+32%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Channel Performance -->
|
||||
<div class="section-block">
|
||||
<h2 class="section-block__title">📱 채널별 성과</h2>
|
||||
<div class="section-block__content">
|
||||
<p>
|
||||
총 4개 채널을 통해 이벤트를 진행했으며, Instagram이 가장 높은 참여율을 기록했습니다.
|
||||
</p>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>채널</th>
|
||||
<th>참여자</th>
|
||||
<th>전환율</th>
|
||||
<th>비율</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="channelTableBody">
|
||||
<!-- Table rows will be dynamically inserted -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ROI Analysis -->
|
||||
<div class="section-block">
|
||||
<h2 class="section-block__title">💰 ROI 분석</h2>
|
||||
<div class="section-block__content">
|
||||
<p>
|
||||
총 투자 비용 330만원 대비 1,500만원의 수익을 창출하여 354.2%의 ROI를 달성했습니다.
|
||||
</p>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>항목</th>
|
||||
<th>금액</th>
|
||||
<th>비율</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>총 수익</td>
|
||||
<td>15,000,000원</td>
|
||||
<td>100%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>총 비용</td>
|
||||
<td>3,300,000원</td>
|
||||
<td>22%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>순이익</strong></td>
|
||||
<td><strong>11,700,000원</strong></td>
|
||||
<td><strong>78%</strong></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recommendations -->
|
||||
<div class="recommendations">
|
||||
<h2 class="recommendations__title">💡 개선 제안</h2>
|
||||
<div class="recommendations__list">
|
||||
<div class="recommendation-item">
|
||||
<div class="recommendation-item__icon">📱</div>
|
||||
<div class="recommendation-item__content">
|
||||
<div class="recommendation-item__title">Instagram 채널 집중 투자</div>
|
||||
<div class="recommendation-item__text">
|
||||
Instagram이 가장 높은 참여율(36.5%)을 기록했습니다.
|
||||
향후 이벤트에서 이 채널에 더 많은 리소스를 배분하면 더 나은 성과를 기대할 수 있습니다.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recommendation-item">
|
||||
<div class="recommendation-item__icon">🎯</div>
|
||||
<div class="recommendation-item__content">
|
||||
<div class="recommendation-item__title">KakaoTalk 채널 개선 필요</div>
|
||||
<div class="recommendation-item__text">
|
||||
KakaoTalk 채널의 전환율이 상대적으로 낮습니다(12.8%).
|
||||
콘텐츠 개선 및 타겟팅 전략 재검토가 필요합니다.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recommendation-item">
|
||||
<div class="recommendation-item__icon">💰</div>
|
||||
<div class="recommendation-item__content">
|
||||
<div class="recommendation-item__title">경품 비용 최적화</div>
|
||||
<div class="recommendation-item__text">
|
||||
경품 비용이 전체 비용의 45.5%를 차지합니다.
|
||||
경품 구성을 다양화하고 가성비를 높여 ROI를 더 개선할 수 있습니다.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Conclusion -->
|
||||
<div class="section-block">
|
||||
<h2 class="section-block__title">📝 결론</h2>
|
||||
<div class="section-block__content">
|
||||
<p>
|
||||
본 이벤트는 모든 핵심 지표에서 목표를 초과 달성하며 성공적으로 종료되었습니다.
|
||||
특히 Instagram 채널의 우수한 성과와 높은 ROI는 향후 마케팅 전략 수립에
|
||||
중요한 참고 자료가 될 것입니다.
|
||||
</p>
|
||||
<p style="margin-top: var(--spacing-m);">
|
||||
다만, 채널별 성과 편차가 크게 나타났으므로, 차기 이벤트에서는
|
||||
저성과 채널에 대한 개선 전략을 수립하고 고성과 채널에 대한 투자를
|
||||
확대할 것을 권장합니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Bottom Actions -->
|
||||
<div class="bottom-actions">
|
||||
<button class="btn btn--secondary" id="saveBtn">
|
||||
<span>💾</span>
|
||||
<span>저장</span>
|
||||
</button>
|
||||
<button class="btn btn--primary" id="downloadBtn">
|
||||
<span>📥</span>
|
||||
<span>PDF 다운로드</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Toast -->
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
// Utils
|
||||
const $ = (selector) => document.querySelector(selector);
|
||||
|
||||
const addClass = (el, className) => el?.classList.add(className);
|
||||
const removeClass = (el, className) => el?.classList.remove(className);
|
||||
|
||||
// LocalStorage wrapper
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// App State
|
||||
const AppState = {
|
||||
showToast(message, duration = 2000) {
|
||||
const toast = $('#toast');
|
||||
toast.textContent = message;
|
||||
addClass(toast, 'toast--visible');
|
||||
|
||||
setTimeout(() => {
|
||||
removeClass(toast, 'toast--visible');
|
||||
}, duration);
|
||||
}
|
||||
};
|
||||
|
||||
// DOM Elements
|
||||
const eventTitle = $('#eventTitle');
|
||||
const reportPeriod = $('#reportPeriod');
|
||||
const generatedDate = $('#generatedDate');
|
||||
const author = $('#author');
|
||||
const channelTableBody = $('#channelTableBody');
|
||||
const shareBtn = $('#shareBtn');
|
||||
const printBtn = $('#printBtn');
|
||||
const saveBtn = $('#saveBtn');
|
||||
const downloadBtn = $('#downloadBtn');
|
||||
const backBtn = $('.header__back');
|
||||
|
||||
// Format Date
|
||||
function formatDate(date) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}.${month}.${day}`;
|
||||
}
|
||||
|
||||
// Render Channel Table
|
||||
function renderChannelTable() {
|
||||
const channels = [
|
||||
{ name: 'Instagram', participants: 456, conversion: 36.5 },
|
||||
{ name: '우리동네TV', participants: 342, conversion: 27.4 },
|
||||
{ name: 'Naver 블로그', participants: 289, conversion: 23.2 },
|
||||
{ name: 'KakaoTalk', participants: 160, conversion: 12.8 }
|
||||
];
|
||||
|
||||
const total = channels.reduce((sum, ch) => sum + ch.participants, 0);
|
||||
|
||||
channelTableBody.innerHTML = channels.map(channel => {
|
||||
const percentage = ((channel.participants / total) * 100).toFixed(1);
|
||||
return `
|
||||
<tr>
|
||||
<td>${channel.name}</td>
|
||||
<td>${channel.participants}명</td>
|
||||
<td>${channel.conversion}%</td>
|
||||
<td>${percentage}%</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Handle Share
|
||||
shareBtn.addEventListener('click', () => {
|
||||
if (navigator.share) {
|
||||
navigator.share({
|
||||
title: '이벤트 분석 리포트',
|
||||
text: '이벤트 성과 분석 리포트를 공유합니다.',
|
||||
url: window.location.href
|
||||
}).then(() => {
|
||||
AppState.showToast('공유가 완료되었습니다');
|
||||
}).catch(() => {
|
||||
AppState.showToast('공유에 실패했습니다');
|
||||
});
|
||||
} else {
|
||||
AppState.showToast('이 브라우저는 공유 기능을 지원하지 않습니다');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Print
|
||||
printBtn.addEventListener('click', () => {
|
||||
window.print();
|
||||
});
|
||||
|
||||
// Handle Save
|
||||
saveBtn.addEventListener('click', () => {
|
||||
AppState.showToast('리포트가 저장되었습니다');
|
||||
});
|
||||
|
||||
// Handle Download
|
||||
downloadBtn.addEventListener('click', () => {
|
||||
AppState.showToast('PDF 다운로드 준비 중...');
|
||||
|
||||
setTimeout(() => {
|
||||
AppState.showToast('PDF 다운로드가 완료되었습니다');
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
// Handle Back
|
||||
backBtn.addEventListener('click', () => {
|
||||
window.location.href = '21-실시간대시보드.html';
|
||||
});
|
||||
|
||||
// Initialize
|
||||
function init() {
|
||||
// Load event data
|
||||
const eventContent = storage.get('eventContent');
|
||||
if (eventContent) {
|
||||
eventTitle.textContent = eventContent.title || '신규 회원 가입 이벤트';
|
||||
}
|
||||
|
||||
// Load store data
|
||||
const storeData = storage.get('storeData');
|
||||
if (storeData) {
|
||||
author.textContent = storeData.businessName || '매장명';
|
||||
}
|
||||
|
||||
// Set dates
|
||||
const today = new Date();
|
||||
generatedDate.textContent = formatDate(today);
|
||||
|
||||
renderChannelTable();
|
||||
}
|
||||
|
||||
// Start
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,846 +0,0 @@
|
||||
<!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>
|
||||
<style>
|
||||
/* CSS Reset */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Design System Variables */
|
||||
:root {
|
||||
/* Colors */
|
||||
--color-primary: #E31E24;
|
||||
--color-primary-hover: #C71820;
|
||||
--color-primary-light: #FF4D52;
|
||||
--color-secondary: #0066FF;
|
||||
--color-secondary-hover: #004DBF;
|
||||
--color-secondary-light: #4D94FF;
|
||||
|
||||
--color-success: #00C853;
|
||||
--color-warning: #FFA000;
|
||||
--color-error: #D32F2F;
|
||||
--color-info: #0288D1;
|
||||
|
||||
--color-gray-50: #F8F9FA;
|
||||
--color-gray-100: #F1F3F5;
|
||||
--color-gray-200: #E9ECEF;
|
||||
--color-gray-300: #DEE2E6;
|
||||
--color-gray-400: #CED4DA;
|
||||
--color-gray-500: #ADB5BD;
|
||||
--color-gray-600: #868E96;
|
||||
--color-gray-700: #495057;
|
||||
--color-gray-800: #343A40;
|
||||
--color-gray-900: #212529;
|
||||
|
||||
--color-white: #FFFFFF;
|
||||
--color-black: #000000;
|
||||
|
||||
/* Typography */
|
||||
--font-family: 'Pretendard Variable', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
||||
|
||||
/* Spacing (4px grid) */
|
||||
--spacing-xs: 4px;
|
||||
--spacing-s: 8px;
|
||||
--spacing-m: 16px;
|
||||
--spacing-l: 24px;
|
||||
--spacing-xl: 32px;
|
||||
--spacing-2xl: 48px;
|
||||
|
||||
/* Border Radius */
|
||||
--radius-s: 4px;
|
||||
--radius-m: 8px;
|
||||
--radius-l: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-s: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-m: 0 4px 6px rgba(0, 0, 0, 0.07);
|
||||
--shadow-l: 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15);
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 150ms ease-in-out;
|
||||
--transition-base: 250ms ease-in-out;
|
||||
--transition-slow: 350ms ease-in-out;
|
||||
|
||||
/* Z-index */
|
||||
--z-header: 100;
|
||||
--z-fab: 150;
|
||||
--z-modal: 200;
|
||||
--z-toast: 300;
|
||||
}
|
||||
|
||||
/* Base Styles */
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: var(--color-gray-900);
|
||||
background-color: var(--color-gray-50);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 56px;
|
||||
background-color: var(--color-white);
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 var(--spacing-m);
|
||||
z-index: var(--z-header);
|
||||
}
|
||||
|
||||
.header__back {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--color-gray-900);
|
||||
font-size: 24px;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.header__back:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.header__title {
|
||||
flex: 1;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
margin: 0 var(--spacing-m);
|
||||
}
|
||||
|
||||
.header__action {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--color-gray-900);
|
||||
font-size: 20px;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.header__action:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* Main */
|
||||
.main {
|
||||
padding-top: 56px;
|
||||
padding-bottom: var(--spacing-l);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Section */
|
||||
.section {
|
||||
padding: var(--spacing-l) var(--spacing-m);
|
||||
}
|
||||
|
||||
/* Search Bar */
|
||||
.search-bar {
|
||||
position: relative;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.search-bar__input {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
padding: 0 var(--spacing-m) 0 48px;
|
||||
font-size: 15px;
|
||||
border: 1px solid var(--color-gray-300);
|
||||
border-radius: var(--radius-m);
|
||||
background-color: var(--color-white);
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.search-bar__input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px rgba(227, 30, 36, 0.1);
|
||||
}
|
||||
|
||||
.search-bar__icon {
|
||||
position: absolute;
|
||||
left: var(--spacing-m);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--color-gray-500);
|
||||
font-size: 20px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Filter Tabs */
|
||||
.filter-tabs {
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-l);
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.filter-tabs::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.filter-tab {
|
||||
flex-shrink: 0;
|
||||
padding: var(--spacing-s) var(--spacing-m);
|
||||
background-color: var(--color-white);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
border-radius: var(--radius-full);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--color-gray-700);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-tab:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.filter-tab--active {
|
||||
background-color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
/* Event List */
|
||||
.event-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.event-card {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-m);
|
||||
box-shadow: var(--shadow-s);
|
||||
transition: box-shadow var(--transition-fast);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.event-card:hover {
|
||||
box-shadow: var(--shadow-m);
|
||||
}
|
||||
|
||||
.event-card__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.event-card__title {
|
||||
flex: 1;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.event-card__status {
|
||||
flex-shrink: 0;
|
||||
margin-left: var(--spacing-m);
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: var(--spacing-xs) var(--spacing-s);
|
||||
border-radius: var(--radius-full);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge--active {
|
||||
background-color: #E8F5E9;
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.status-badge--ended {
|
||||
background-color: var(--color-gray-100);
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
.status-badge--scheduled {
|
||||
background-color: #E3F2FD;
|
||||
color: var(--color-info);
|
||||
}
|
||||
|
||||
.event-card__period {
|
||||
font-size: 13px;
|
||||
color: var(--color-gray-600);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.event-card__stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--spacing-m);
|
||||
padding: var(--spacing-m);
|
||||
background-color: var(--color-gray-50);
|
||||
border-radius: var(--radius-m);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.event-stat {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.event-stat__label {
|
||||
font-size: 11px;
|
||||
color: var(--color-gray-600);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.event-stat__value {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
.event-card__actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-xs);
|
||||
background-color: var(--color-white);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--color-gray-700);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.action-btn--primary {
|
||||
background-color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.action-btn--primary:hover {
|
||||
background-color: var(--color-primary-hover);
|
||||
border-color: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: var(--spacing-2xl) var(--spacing-m);
|
||||
}
|
||||
|
||||
.empty-state__icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: var(--spacing-m);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.empty-state__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.empty-state__description {
|
||||
font-size: 14px;
|
||||
color: var(--color-gray-600);
|
||||
line-height: 1.6;
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.empty-state__action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
border: none;
|
||||
border-radius: var(--radius-m);
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.empty-state__action:hover {
|
||||
background-color: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
/* FAB */
|
||||
.fab {
|
||||
position: fixed;
|
||||
bottom: var(--spacing-l);
|
||||
right: var(--spacing-l);
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
border: none;
|
||||
border-radius: var(--radius-full);
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
box-shadow: var(--shadow-l);
|
||||
transition: all var(--transition-fast);
|
||||
z-index: var(--z-fab);
|
||||
}
|
||||
|
||||
.fab:hover {
|
||||
background-color: var(--color-primary-hover);
|
||||
box-shadow: var(--shadow-xl);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* Toast */
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: var(--spacing-l);
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(100px);
|
||||
background-color: var(--color-gray-800);
|
||||
color: var(--color-white);
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
box-shadow: var(--shadow-xl);
|
||||
z-index: var(--z-toast);
|
||||
opacity: 0;
|
||||
transition: all var(--transition-base);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toast--visible {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (min-width: 768px) {
|
||||
.header {
|
||||
padding: 0 var(--spacing-l);
|
||||
}
|
||||
|
||||
.section {
|
||||
max-width: 768px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl) var(--spacing-l);
|
||||
}
|
||||
|
||||
.event-card__stats {
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.section {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<button class="header__back" aria-label="뒤로 가기">←</button>
|
||||
<h1 class="header__title">이벤트 목록</h1>
|
||||
<button class="header__action" id="refreshBtn" aria-label="새로고침">⟳</button>
|
||||
</header>
|
||||
|
||||
<!-- Main -->
|
||||
<main class="main">
|
||||
<div class="section">
|
||||
<!-- Search Bar -->
|
||||
<div class="search-bar">
|
||||
<span class="search-bar__icon">🔍</span>
|
||||
<input
|
||||
type="text"
|
||||
class="search-bar__input"
|
||||
id="searchInput"
|
||||
placeholder="이벤트 제목으로 검색"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Filter Tabs -->
|
||||
<div class="filter-tabs">
|
||||
<button class="filter-tab filter-tab--active" data-filter="all">
|
||||
전체 <span id="countAll">0</span>
|
||||
</button>
|
||||
<button class="filter-tab" data-filter="active">
|
||||
진행중 <span id="countActive">0</span>
|
||||
</button>
|
||||
<button class="filter-tab" data-filter="scheduled">
|
||||
예정 <span id="countScheduled">0</span>
|
||||
</button>
|
||||
<button class="filter-tab" data-filter="ended">
|
||||
종료 <span id="countEnded">0</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Event List -->
|
||||
<div class="event-list" id="eventList">
|
||||
<!-- Events will be dynamically inserted -->
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div class="empty-state" id="emptyState" style="display: none;">
|
||||
<div class="empty-state__icon">📋</div>
|
||||
<h3 class="empty-state__title">진행 중인 이벤트가 없습니다</h3>
|
||||
<p class="empty-state__description">
|
||||
AI를 활용해 쉽고 빠르게<br/>
|
||||
새로운 이벤트를 만들어보세요!
|
||||
</p>
|
||||
<button class="empty-state__action" id="emptyCreateBtn">
|
||||
<span>➕</span>
|
||||
<span>이벤트 만들기</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- FAB -->
|
||||
<button class="fab" id="fabBtn" aria-label="새 이벤트 만들기">+</button>
|
||||
|
||||
<!-- Toast -->
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
// Utils
|
||||
const $ = (selector) => document.querySelector(selector);
|
||||
const $$ = (selector) => document.querySelectorAll(selector);
|
||||
|
||||
const addClass = (el, className) => el?.classList.add(className);
|
||||
const removeClass = (el, className) => el?.classList.remove(className);
|
||||
|
||||
// App State
|
||||
const AppState = {
|
||||
events: [],
|
||||
filteredEvents: [],
|
||||
currentFilter: 'all',
|
||||
searchQuery: '',
|
||||
|
||||
showToast(message, duration = 2000) {
|
||||
const toast = $('#toast');
|
||||
toast.textContent = message;
|
||||
addClass(toast, 'toast--visible');
|
||||
|
||||
setTimeout(() => {
|
||||
removeClass(toast, 'toast--visible');
|
||||
}, duration);
|
||||
}
|
||||
};
|
||||
|
||||
// DOM Elements
|
||||
const searchInput = $('#searchInput');
|
||||
const filterTabs = $$('.filter-tab');
|
||||
const eventList = $('#eventList');
|
||||
const emptyState = $('#emptyState');
|
||||
const countAll = $('#countAll');
|
||||
const countActive = $('#countActive');
|
||||
const countScheduled = $('#countScheduled');
|
||||
const countEnded = $('#countEnded');
|
||||
const refreshBtn = $('#refreshBtn');
|
||||
const fabBtn = $('#fabBtn');
|
||||
const emptyCreateBtn = $('#emptyCreateBtn');
|
||||
const backBtn = $('.header__back');
|
||||
|
||||
// Generate Mock Events
|
||||
function generateMockEvents() {
|
||||
const now = new Date();
|
||||
const events = [
|
||||
{
|
||||
id: 'event-001',
|
||||
title: '신규 회원 가입 이벤트',
|
||||
status: 'active',
|
||||
startDate: new Date(now.getTime() - 15 * 24 * 60 * 60 * 1000),
|
||||
endDate: new Date(now.getTime() + 15 * 24 * 60 * 60 * 1000),
|
||||
participants: 1247,
|
||||
winners: 156,
|
||||
views: 5432,
|
||||
channels: 4,
|
||||
roi: 354.2
|
||||
},
|
||||
{
|
||||
id: 'event-002',
|
||||
title: '설 명절 특별 프로모션',
|
||||
status: 'scheduled',
|
||||
startDate: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000),
|
||||
endDate: new Date(now.getTime() + 21 * 24 * 60 * 60 * 1000),
|
||||
participants: 0,
|
||||
winners: 0,
|
||||
views: 0,
|
||||
channels: 3,
|
||||
roi: 0
|
||||
},
|
||||
{
|
||||
id: 'event-003',
|
||||
title: '연말 감사 이벤트',
|
||||
status: 'ended',
|
||||
startDate: new Date(now.getTime() - 60 * 24 * 60 * 60 * 1000),
|
||||
endDate: new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000),
|
||||
participants: 892,
|
||||
winners: 98,
|
||||
views: 3421,
|
||||
channels: 3,
|
||||
roi: 287.5
|
||||
},
|
||||
{
|
||||
id: 'event-004',
|
||||
title: '친구 추천 이벤트',
|
||||
status: 'active',
|
||||
startDate: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000),
|
||||
endDate: new Date(now.getTime() + 25 * 24 * 60 * 60 * 1000),
|
||||
participants: 543,
|
||||
winners: 65,
|
||||
views: 2134,
|
||||
channels: 2,
|
||||
roi: 198.3
|
||||
}
|
||||
];
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
// Format Date
|
||||
function formatDate(date) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}.${month}.${day}`;
|
||||
}
|
||||
|
||||
// Format Number
|
||||
function formatNumber(num) {
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
}
|
||||
|
||||
// Get Status Badge
|
||||
function getStatusBadge(status) {
|
||||
const badges = {
|
||||
active: { class: 'status-badge--active', text: '진행중' },
|
||||
ended: { class: 'status-badge--ended', text: '종료' },
|
||||
scheduled: { class: 'status-badge--scheduled', text: '예정' }
|
||||
};
|
||||
|
||||
return badges[status] || badges.active;
|
||||
}
|
||||
|
||||
// Render Events
|
||||
function renderEvents() {
|
||||
const events = AppState.filteredEvents;
|
||||
|
||||
if (events.length === 0) {
|
||||
eventList.style.display = 'none';
|
||||
emptyState.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
eventList.style.display = 'flex';
|
||||
emptyState.style.display = 'none';
|
||||
|
||||
eventList.innerHTML = events.map(event => {
|
||||
const badge = getStatusBadge(event.status);
|
||||
|
||||
return `
|
||||
<div class="event-card" data-id="${event.id}">
|
||||
<div class="event-card__header">
|
||||
<h3 class="event-card__title">${event.title}</h3>
|
||||
<div class="event-card__status">
|
||||
<span class="status-badge ${badge.class}">${badge.text}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="event-card__period">
|
||||
${formatDate(event.startDate)} - ${formatDate(event.endDate)}
|
||||
</div>
|
||||
|
||||
<div class="event-card__stats">
|
||||
<div class="event-stat">
|
||||
<div class="event-stat__label">참여자</div>
|
||||
<div class="event-stat__value">${formatNumber(event.participants)}</div>
|
||||
</div>
|
||||
<div class="event-stat">
|
||||
<div class="event-stat__label">당첨자</div>
|
||||
<div class="event-stat__value">${formatNumber(event.winners)}</div>
|
||||
</div>
|
||||
<div class="event-stat">
|
||||
<div class="event-stat__label">조회수</div>
|
||||
<div class="event-stat__value">${formatNumber(event.views)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="event-card__actions">
|
||||
<button class="action-btn" data-action="analytics" data-id="${event.id}">
|
||||
📊 분석
|
||||
</button>
|
||||
<button class="action-btn" data-action="manage" data-id="${event.id}">
|
||||
⚙️ 관리
|
||||
</button>
|
||||
${event.status === 'active' ? `
|
||||
<button class="action-btn action-btn--primary" data-action="view" data-id="${event.id}">
|
||||
👁 보기
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
// Add event listeners
|
||||
$$('[data-action]').forEach(btn => {
|
||||
btn.addEventListener('click', handleAction);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle Action
|
||||
function handleAction(e) {
|
||||
e.stopPropagation();
|
||||
const action = e.currentTarget.dataset.action;
|
||||
const eventId = e.currentTarget.dataset.id;
|
||||
|
||||
switch (action) {
|
||||
case 'analytics':
|
||||
window.location.href = '21-실시간대시보드.html';
|
||||
break;
|
||||
case 'manage':
|
||||
window.location.href = '20-당첨자명단관리.html';
|
||||
break;
|
||||
case 'view':
|
||||
window.location.href = '18-이벤트참여.html';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply Filters
|
||||
function applyFilters() {
|
||||
let filtered = [...AppState.events];
|
||||
|
||||
// Apply status filter
|
||||
if (AppState.currentFilter !== 'all') {
|
||||
filtered = filtered.filter(e => e.status === AppState.currentFilter);
|
||||
}
|
||||
|
||||
// Apply search filter
|
||||
if (AppState.searchQuery) {
|
||||
const query = AppState.searchQuery.toLowerCase();
|
||||
filtered = filtered.filter(e =>
|
||||
e.title.toLowerCase().includes(query)
|
||||
);
|
||||
}
|
||||
|
||||
AppState.filteredEvents = filtered;
|
||||
renderEvents();
|
||||
}
|
||||
|
||||
// Update Counts
|
||||
function updateCounts() {
|
||||
const all = AppState.events.length;
|
||||
const active = AppState.events.filter(e => e.status === 'active').length;
|
||||
const scheduled = AppState.events.filter(e => e.status === 'scheduled').length;
|
||||
const ended = AppState.events.filter(e => e.status === 'ended').length;
|
||||
|
||||
countAll.textContent = all;
|
||||
countActive.textContent = active;
|
||||
countScheduled.textContent = scheduled;
|
||||
countEnded.textContent = ended;
|
||||
}
|
||||
|
||||
// Handle Search
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
AppState.searchQuery = e.target.value.trim();
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
// Handle Filter Tabs
|
||||
filterTabs.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
const filter = tab.dataset.filter;
|
||||
|
||||
filterTabs.forEach(t => removeClass(t, 'filter-tab--active'));
|
||||
addClass(tab, 'filter-tab--active');
|
||||
|
||||
AppState.currentFilter = filter;
|
||||
applyFilters();
|
||||
});
|
||||
});
|
||||
|
||||
// Handle Refresh
|
||||
refreshBtn.addEventListener('click', () => {
|
||||
init();
|
||||
AppState.showToast('목록이 새로고침되었습니다');
|
||||
});
|
||||
|
||||
// Handle FAB
|
||||
fabBtn.addEventListener('click', () => {
|
||||
window.location.href = '03-이벤트목적선택.html';
|
||||
});
|
||||
|
||||
// Handle Empty Create
|
||||
emptyCreateBtn?.addEventListener('click', () => {
|
||||
window.location.href = '03-이벤트목적선택.html';
|
||||
});
|
||||
|
||||
// Handle Back
|
||||
backBtn.addEventListener('click', () => {
|
||||
window.location.href = '21.5-홈.html';
|
||||
});
|
||||
|
||||
// Initialize
|
||||
function init() {
|
||||
AppState.events = generateMockEvents();
|
||||
updateCounts();
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
// Start
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,727 +0,0 @@
|
||||
<!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>
|
||||
<style>
|
||||
/* CSS Reset */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Design System Variables */
|
||||
:root {
|
||||
/* Colors */
|
||||
--color-primary: #E31E24;
|
||||
--color-primary-hover: #C71820;
|
||||
--color-primary-light: #FF4D52;
|
||||
--color-secondary: #0066FF;
|
||||
--color-secondary-hover: #004DBF;
|
||||
--color-secondary-light: #4D94FF;
|
||||
|
||||
--color-success: #00C853;
|
||||
--color-warning: #FFA000;
|
||||
--color-error: #D32F2F;
|
||||
--color-info: #0288D1;
|
||||
|
||||
--color-gray-50: #F8F9FA;
|
||||
--color-gray-100: #F1F3F5;
|
||||
--color-gray-200: #E9ECEF;
|
||||
--color-gray-300: #DEE2E6;
|
||||
--color-gray-400: #CED4DA;
|
||||
--color-gray-500: #ADB5BD;
|
||||
--color-gray-600: #868E96;
|
||||
--color-gray-700: #495057;
|
||||
--color-gray-800: #343A40;
|
||||
--color-gray-900: #212529;
|
||||
|
||||
--color-white: #FFFFFF;
|
||||
--color-black: #000000;
|
||||
|
||||
/* Typography */
|
||||
--font-family: 'Pretendard Variable', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
||||
|
||||
/* Spacing (4px grid) */
|
||||
--spacing-xs: 4px;
|
||||
--spacing-s: 8px;
|
||||
--spacing-m: 16px;
|
||||
--spacing-l: 24px;
|
||||
--spacing-xl: 32px;
|
||||
--spacing-2xl: 48px;
|
||||
|
||||
/* Border Radius */
|
||||
--radius-s: 4px;
|
||||
--radius-m: 8px;
|
||||
--radius-l: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-s: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-m: 0 4px 6px rgba(0, 0, 0, 0.07);
|
||||
--shadow-l: 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15);
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 150ms ease-in-out;
|
||||
--transition-base: 250ms ease-in-out;
|
||||
--transition-slow: 350ms ease-in-out;
|
||||
|
||||
/* Z-index */
|
||||
--z-header: 100;
|
||||
--z-modal: 200;
|
||||
--z-toast: 300;
|
||||
}
|
||||
|
||||
/* Base Styles */
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: var(--color-gray-900);
|
||||
background-color: var(--color-gray-50);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 56px;
|
||||
background-color: var(--color-white);
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 var(--spacing-m);
|
||||
z-index: var(--z-header);
|
||||
}
|
||||
|
||||
.header__back {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--color-gray-900);
|
||||
font-size: 24px;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.header__back:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.header__title {
|
||||
flex: 1;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
margin: 0 var(--spacing-m);
|
||||
}
|
||||
|
||||
.header__action {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--color-gray-900);
|
||||
font-size: 20px;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.header__action:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* Main */
|
||||
.main {
|
||||
padding-top: 56px;
|
||||
padding-bottom: var(--spacing-l);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Section */
|
||||
.section {
|
||||
padding: var(--spacing-l) var(--spacing-m);
|
||||
}
|
||||
|
||||
/* Profile Card */
|
||||
.profile-card {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-l);
|
||||
margin-bottom: var(--spacing-l);
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
.profile-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.profile-card__avatar {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, var(--color-primary), var(--color-primary-light));
|
||||
color: var(--color-white);
|
||||
border-radius: var(--radius-full);
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.profile-card__info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.profile-card__name {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.profile-card__email {
|
||||
font-size: 14px;
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
.profile-card__stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--spacing-m);
|
||||
padding-top: var(--spacing-m);
|
||||
border-top: 1px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
.profile-stat {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.profile-stat__value {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.profile-stat__label {
|
||||
font-size: 12px;
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
/* Store Info */
|
||||
.store-info {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-l);
|
||||
margin-bottom: var(--spacing-l);
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
.store-info__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.store-info__item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--spacing-m) 0;
|
||||
border-bottom: 1px solid var(--color-gray-100);
|
||||
}
|
||||
|
||||
.store-info__item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.store-info__label {
|
||||
font-size: 14px;
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
.store-info__value {
|
||||
font-size: 14px;
|
||||
color: var(--color-gray-900);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Menu Section */
|
||||
.menu-section {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-l);
|
||||
padding: var(--spacing-s) 0;
|
||||
margin-bottom: var(--spacing-l);
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
.menu-section__title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-600);
|
||||
padding: var(--spacing-s) var(--spacing-l);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
cursor: pointer;
|
||||
transition: background-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background-color: var(--color-gray-50);
|
||||
}
|
||||
|
||||
.menu-item__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.menu-item__icon {
|
||||
font-size: 20px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.menu-item__label {
|
||||
font-size: 15px;
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
.menu-item__arrow {
|
||||
font-size: 16px;
|
||||
color: var(--color-gray-400);
|
||||
}
|
||||
|
||||
.menu-item--danger {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.menu-item--danger .menu-item__label {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
/* Version Info */
|
||||
.version-info {
|
||||
text-align: center;
|
||||
padding: var(--spacing-l);
|
||||
color: var(--color-gray-500);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* Toast */
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: var(--spacing-l);
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(100px);
|
||||
background-color: var(--color-gray-800);
|
||||
color: var(--color-white);
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
border-radius: var(--radius-m);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
box-shadow: var(--shadow-xl);
|
||||
z-index: var(--z-toast);
|
||||
opacity: 0;
|
||||
transition: all var(--transition-base);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toast--visible {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (min-width: 768px) {
|
||||
.header {
|
||||
padding: 0 var(--spacing-l);
|
||||
}
|
||||
|
||||
.section {
|
||||
max-width: 768px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl) var(--spacing-l);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.section {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<button class="header__back" aria-label="뒤로 가기">←</button>
|
||||
<h1 class="header__title">마이페이지</h1>
|
||||
<button class="header__action" id="settingsBtn" aria-label="설정">⚙️</button>
|
||||
</header>
|
||||
|
||||
<!-- Main -->
|
||||
<main class="main">
|
||||
<div class="section">
|
||||
<!-- Profile Card -->
|
||||
<div class="profile-card">
|
||||
<div class="profile-card__header">
|
||||
<div class="profile-card__avatar" id="avatar">사</div>
|
||||
<div class="profile-card__info">
|
||||
<div class="profile-card__name" id="userName">사장님</div>
|
||||
<div class="profile-card__email" id="userEmail">example@email.com</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-card__stats">
|
||||
<div class="profile-stat">
|
||||
<div class="profile-stat__value" id="totalEvents">0</div>
|
||||
<div class="profile-stat__label">총 이벤트</div>
|
||||
</div>
|
||||
<div class="profile-stat">
|
||||
<div class="profile-stat__value" id="totalParticipants">0</div>
|
||||
<div class="profile-stat__label">총 참여자</div>
|
||||
</div>
|
||||
<div class="profile-stat">
|
||||
<div class="profile-stat__value" id="avgROI">0%</div>
|
||||
<div class="profile-stat__label">평균 ROI</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Store Info -->
|
||||
<div class="store-info">
|
||||
<h2 class="store-info__title">매장 정보</h2>
|
||||
<div class="store-info__item">
|
||||
<div class="store-info__label">상호명</div>
|
||||
<div class="store-info__value" id="businessName">-</div>
|
||||
</div>
|
||||
<div class="store-info__item">
|
||||
<div class="store-info__label">업종</div>
|
||||
<div class="store-info__value" id="businessType">-</div>
|
||||
</div>
|
||||
<div class="store-info__item">
|
||||
<div class="store-info__label">주소</div>
|
||||
<div class="store-info__value" id="address">-</div>
|
||||
</div>
|
||||
<div class="store-info__item">
|
||||
<div class="store-info__label">전화번호</div>
|
||||
<div class="store-info__value" id="phone">-</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Menu Section: Account -->
|
||||
<div class="menu-section">
|
||||
<h3 class="menu-section__title">계정</h3>
|
||||
<div class="menu-item" id="editProfileBtn">
|
||||
<div class="menu-item__left">
|
||||
<span class="menu-item__icon">👤</span>
|
||||
<span class="menu-item__label">프로필 수정</span>
|
||||
</div>
|
||||
<span class="menu-item__arrow">›</span>
|
||||
</div>
|
||||
<div class="menu-item" id="editStoreBtn">
|
||||
<div class="menu-item__left">
|
||||
<span class="menu-item__icon">🏪</span>
|
||||
<span class="menu-item__label">매장 정보 수정</span>
|
||||
</div>
|
||||
<span class="menu-item__arrow">›</span>
|
||||
</div>
|
||||
<div class="menu-item" id="changePasswordBtn">
|
||||
<div class="menu-item__left">
|
||||
<span class="menu-item__icon">🔒</span>
|
||||
<span class="menu-item__label">비밀번호 변경</span>
|
||||
</div>
|
||||
<span class="menu-item__arrow">›</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Menu Section: Preferences -->
|
||||
<div class="menu-section">
|
||||
<h3 class="menu-section__title">설정</h3>
|
||||
<div class="menu-item" id="notificationBtn">
|
||||
<div class="menu-item__left">
|
||||
<span class="menu-item__icon">🔔</span>
|
||||
<span class="menu-item__label">알림 설정</span>
|
||||
</div>
|
||||
<span class="menu-item__arrow">›</span>
|
||||
</div>
|
||||
<div class="menu-item" id="languageBtn">
|
||||
<div class="menu-item__left">
|
||||
<span class="menu-item__icon">🌐</span>
|
||||
<span class="menu-item__label">언어 설정</span>
|
||||
</div>
|
||||
<span class="menu-item__arrow">›</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Menu Section: Support -->
|
||||
<div class="menu-section">
|
||||
<h3 class="menu-section__title">고객 지원</h3>
|
||||
<div class="menu-item" id="helpBtn">
|
||||
<div class="menu-item__left">
|
||||
<span class="menu-item__icon">❓</span>
|
||||
<span class="menu-item__label">도움말</span>
|
||||
</div>
|
||||
<span class="menu-item__arrow">›</span>
|
||||
</div>
|
||||
<div class="menu-item" id="faqBtn">
|
||||
<div class="menu-item__left">
|
||||
<span class="menu-item__icon">📚</span>
|
||||
<span class="menu-item__label">자주 묻는 질문</span>
|
||||
</div>
|
||||
<span class="menu-item__arrow">›</span>
|
||||
</div>
|
||||
<div class="menu-item" id="contactBtn">
|
||||
<div class="menu-item__left">
|
||||
<span class="menu-item__icon">📧</span>
|
||||
<span class="menu-item__label">고객센터 문의</span>
|
||||
</div>
|
||||
<span class="menu-item__arrow">›</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Menu Section: Legal -->
|
||||
<div class="menu-section">
|
||||
<h3 class="menu-section__title">약관 및 정책</h3>
|
||||
<div class="menu-item" id="termsBtn">
|
||||
<div class="menu-item__left">
|
||||
<span class="menu-item__icon">📄</span>
|
||||
<span class="menu-item__label">이용약관</span>
|
||||
</div>
|
||||
<span class="menu-item__arrow">›</span>
|
||||
</div>
|
||||
<div class="menu-item" id="privacyBtn">
|
||||
<div class="menu-item__left">
|
||||
<span class="menu-item__icon">🔐</span>
|
||||
<span class="menu-item__label">개인정보 처리방침</span>
|
||||
</div>
|
||||
<span class="menu-item__arrow">›</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Menu Section: Account Actions -->
|
||||
<div class="menu-section">
|
||||
<div class="menu-item menu-item--danger" id="logoutBtn">
|
||||
<div class="menu-item__left">
|
||||
<span class="menu-item__icon">🚪</span>
|
||||
<span class="menu-item__label">로그아웃</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Version Info -->
|
||||
<div class="version-info">
|
||||
KT AI 이벤트 플랫폼 v1.0.0<br>
|
||||
© 2025 KT Corporation. All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Toast -->
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
// Utils
|
||||
const $ = (selector) => document.querySelector(selector);
|
||||
|
||||
const addClass = (el, className) => el?.classList.add(className);
|
||||
const removeClass = (el, className) => el?.classList.remove(className);
|
||||
|
||||
// LocalStorage wrapper
|
||||
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;
|
||||
}
|
||||
},
|
||||
clear: () => {
|
||||
try {
|
||||
localStorage.clear();
|
||||
} catch (e) {
|
||||
console.error('Storage clear error:', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// App State
|
||||
const AppState = {
|
||||
showToast(message, duration = 2000) {
|
||||
const toast = $('#toast');
|
||||
toast.textContent = message;
|
||||
addClass(toast, 'toast--visible');
|
||||
|
||||
setTimeout(() => {
|
||||
removeClass(toast, 'toast--visible');
|
||||
}, duration);
|
||||
}
|
||||
};
|
||||
|
||||
// DOM Elements
|
||||
const avatar = $('#avatar');
|
||||
const userName = $('#userName');
|
||||
const userEmail = $('#userEmail');
|
||||
const totalEvents = $('#totalEvents');
|
||||
const totalParticipants = $('#totalParticipants');
|
||||
const avgROI = $('#avgROI');
|
||||
const businessName = $('#businessName');
|
||||
const businessType = $('#businessType');
|
||||
const address = $('#address');
|
||||
const phone = $('#phone');
|
||||
const settingsBtn = $('#settingsBtn');
|
||||
const editProfileBtn = $('#editProfileBtn');
|
||||
const editStoreBtn = $('#editStoreBtn');
|
||||
const changePasswordBtn = $('#changePasswordBtn');
|
||||
const notificationBtn = $('#notificationBtn');
|
||||
const languageBtn = $('#languageBtn');
|
||||
const helpBtn = $('#helpBtn');
|
||||
const faqBtn = $('#faqBtn');
|
||||
const contactBtn = $('#contactBtn');
|
||||
const termsBtn = $('#termsBtn');
|
||||
const privacyBtn = $('#privacyBtn');
|
||||
const logoutBtn = $('#logoutBtn');
|
||||
const backBtn = $('.header__back');
|
||||
|
||||
// Format Number
|
||||
function formatNumber(num) {
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
}
|
||||
|
||||
// Load User Data
|
||||
function loadUserData() {
|
||||
// Load signup data
|
||||
const signupData = storage.get('signupData');
|
||||
if (signupData) {
|
||||
userEmail.textContent = signupData.email || 'example@email.com';
|
||||
}
|
||||
|
||||
// Load store data
|
||||
const storeData = storage.get('storeData');
|
||||
if (storeData) {
|
||||
const name = storeData.businessName || '사장님';
|
||||
userName.textContent = name;
|
||||
avatar.textContent = name.charAt(0);
|
||||
|
||||
businessName.textContent = storeData.businessName || '-';
|
||||
businessType.textContent = storeData.businessType || '-';
|
||||
address.textContent = storeData.address || '-';
|
||||
phone.textContent = storeData.phone || '-';
|
||||
}
|
||||
|
||||
// Mock statistics
|
||||
totalEvents.textContent = '4';
|
||||
totalParticipants.textContent = formatNumber(2682);
|
||||
avgROI.textContent = '285.2%';
|
||||
}
|
||||
|
||||
// Handle Settings
|
||||
settingsBtn.addEventListener('click', () => {
|
||||
AppState.showToast('설정 페이지로 이동합니다');
|
||||
});
|
||||
|
||||
// Handle Edit Profile
|
||||
editProfileBtn.addEventListener('click', () => {
|
||||
AppState.showToast('프로필 수정 페이지로 이동합니다');
|
||||
});
|
||||
|
||||
// Handle Edit Store
|
||||
editStoreBtn.addEventListener('click', () => {
|
||||
window.location.href = '02-매장정보등록.html';
|
||||
});
|
||||
|
||||
// Handle Change Password
|
||||
changePasswordBtn.addEventListener('click', () => {
|
||||
AppState.showToast('비밀번호 변경 페이지로 이동합니다');
|
||||
});
|
||||
|
||||
// Handle Notification
|
||||
notificationBtn.addEventListener('click', () => {
|
||||
AppState.showToast('알림 설정 페이지로 이동합니다');
|
||||
});
|
||||
|
||||
// Handle Language
|
||||
languageBtn.addEventListener('click', () => {
|
||||
AppState.showToast('언어 설정 페이지로 이동합니다');
|
||||
});
|
||||
|
||||
// Handle Help
|
||||
helpBtn.addEventListener('click', () => {
|
||||
AppState.showToast('도움말 페이지로 이동합니다');
|
||||
});
|
||||
|
||||
// Handle FAQ
|
||||
faqBtn.addEventListener('click', () => {
|
||||
AppState.showToast('FAQ 페이지로 이동합니다');
|
||||
});
|
||||
|
||||
// Handle Contact
|
||||
contactBtn.addEventListener('click', () => {
|
||||
AppState.showToast('고객센터 문의 페이지로 이동합니다');
|
||||
});
|
||||
|
||||
// Handle Terms
|
||||
termsBtn.addEventListener('click', () => {
|
||||
AppState.showToast('이용약관 페이지로 이동합니다');
|
||||
});
|
||||
|
||||
// Handle Privacy
|
||||
privacyBtn.addEventListener('click', () => {
|
||||
AppState.showToast('개인정보 처리방침 페이지로 이동합니다');
|
||||
});
|
||||
|
||||
// Handle Logout
|
||||
logoutBtn.addEventListener('click', () => {
|
||||
if (confirm('로그아웃 하시겠습니까?')) {
|
||||
storage.clear();
|
||||
AppState.showToast('로그아웃되었습니다');
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.href = '00-로그인.html';
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Back
|
||||
backBtn.addEventListener('click', () => {
|
||||
window.location.href = '21.5-홈.html';
|
||||
});
|
||||
|
||||
// Initialize
|
||||
function init() {
|
||||
loadUserData();
|
||||
}
|
||||
|
||||
// Start
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,413 +0,0 @@
|
||||
# 프로토타입 작성원칙 체크리스트
|
||||
|
||||
작성일: 2025-01-20
|
||||
버전: 1.0
|
||||
검토자: UI/UX Designer (박민지 "픽셀")
|
||||
|
||||
---
|
||||
|
||||
## 1. 디자인 원칙 준수
|
||||
|
||||
### 1.1 Mobile First ✅
|
||||
- [x] 모든 화면이 320px 이상에서 정상 작동
|
||||
- [x] 모바일 기본 스타일 우선 작성
|
||||
- [x] Tablet(768px+), Desktop(1024px+) 반응형 지원
|
||||
- [x] Touch 타겟 최소 44x44px 보장
|
||||
|
||||
### 1.2 브랜드 아이덴티티 ✅
|
||||
- [x] KT Red (#E31E24) Primary Color 사용
|
||||
- [x] AI Blue (#0066FF) Secondary Color 사용
|
||||
- [x] Pretendard 폰트 적용
|
||||
- [x] Material Icons 사용
|
||||
|
||||
### 1.3 일관성 ✅
|
||||
- [x] 공통 컴포넌트 재사용 (common.css, common.js)
|
||||
- [x] 동일한 패턴 유지 (header, card, button)
|
||||
- [x] 통일된 네비게이션 (Bottom Nav)
|
||||
|
||||
---
|
||||
|
||||
## 2. 색상 시스템
|
||||
|
||||
### 2.1 색상 변수 사용 ✅
|
||||
- [x] CSS Variables 정의 및 사용
|
||||
- [x] Primary: --color-primary-main (#E31E24)
|
||||
- [x] Secondary: --color-secondary-main (#0066FF)
|
||||
- [x] Semantic Colors 적용 (success, warning, error, info)
|
||||
|
||||
### 2.2 색상 대비 (WCAG 2.1 AA) ✅
|
||||
- [x] 텍스트 대비율 4.5:1 이상
|
||||
- [x] Black on White: 14.2:1
|
||||
- [x] Gray-700 on White: 8.5:1
|
||||
- [x] Primary Red on White: 7.2:1
|
||||
- [x] AI Blue on White: 7.8:1
|
||||
|
||||
### 2.3 Semantic Colors ✅
|
||||
- [x] Success: #00C853 (완료, 승인)
|
||||
- [x] Warning: #FFA000 (경고, 대기)
|
||||
- [x] Error: #D32F2F (오류, 삭제)
|
||||
- [x] Info: #0288D1 (안내, 정보)
|
||||
|
||||
---
|
||||
|
||||
## 3. 타이포그래피
|
||||
|
||||
### 3.1 폰트 적용 ✅
|
||||
- [x] Pretendard 폰트 CDN 로드
|
||||
- [x] Fallback 폰트 지정
|
||||
|
||||
### 3.2 Type Scale ✅
|
||||
- [x] Display: 28px (메인 타이틀)
|
||||
- [x] H1: 24px (화면 제목)
|
||||
- [x] H2: 20px (섹션 제목)
|
||||
- [x] H3: 18px (카드 제목)
|
||||
- [x] Body-L: 16px (입력 필드, 중요 본문)
|
||||
- [x] Body-M: 14px (일반 본문)
|
||||
- [x] Body-S: 12px (캡션, 보조 정보)
|
||||
|
||||
### 3.3 Font Weights ✅
|
||||
- [x] Regular (400): 일반 본문
|
||||
- [x] Medium (500): 강조 본문
|
||||
- [x] SemiBold (600): 버튼, 중요 정보
|
||||
- [x] Bold (700): 제목, 헤딩
|
||||
|
||||
---
|
||||
|
||||
## 4. 간격 시스템
|
||||
|
||||
### 4.1 4px Grid System ✅
|
||||
- [x] 모든 간격이 4의 배수
|
||||
- [x] XS (4px), S (8px), M (16px), L (24px), XL (32px), 2XL (48px)
|
||||
|
||||
### 4.2 컴포넌트별 간격 ✅
|
||||
- [x] Card 내부 패딩: 24px (L)
|
||||
- [x] Card 간 간격: 16px (M)
|
||||
- [x] Section 간 간격: 32px (XL)
|
||||
- [x] Screen 마진: 20px (Mobile)
|
||||
|
||||
---
|
||||
|
||||
## 5. 컴포넌트 스타일
|
||||
|
||||
### 5.1 Button ✅
|
||||
- [x] Primary Button: 배경 #E31E24, 텍스트 White
|
||||
- [x] Secondary Button: 테두리 #E31E24, 텍스트 Red
|
||||
- [x] Text Button: 텍스트만 표시
|
||||
- [x] 크기: Large (48px), Medium (44px), Small (36px)
|
||||
- [x] 둥근 모서리: 8px
|
||||
- [x] Disabled 상태 스타일
|
||||
|
||||
### 5.2 Card ✅
|
||||
- [x] 배경: White
|
||||
- [x] 테두리: 1px solid #E0E0E0
|
||||
- [x] 둥근 모서리: 12px
|
||||
- [x] 그림자: 0 2px 8px rgba(0,0,0,0.08)
|
||||
- [x] 내부 패딩: 24px
|
||||
|
||||
### 5.3 Input Field ✅
|
||||
- [x] 높이: 48px
|
||||
- [x] 둥근 모서리: 8px
|
||||
- [x] Focus 상태: 테두리 2px #0066FF
|
||||
- [x] Error 상태: 테두리 2px #D32F2F
|
||||
- [x] Placeholder: #9E9E9E
|
||||
|
||||
### 5.4 Bottom Navigation ✅
|
||||
- [x] 높이: 60px
|
||||
- [x] 4개 아이템 (홈, 이벤트, 분석, MY)
|
||||
- [x] Active 상태: #E31E24
|
||||
- [x] Inactive 상태: #9E9E9E
|
||||
- [x] 그림자: 0 -2px 8px rgba(0,0,0,0.08)
|
||||
|
||||
### 5.5 Header ✅
|
||||
- [x] 높이: 56px
|
||||
- [x] 뒤로가기 버튼 (좌측)
|
||||
- [x] 제목 (중앙)
|
||||
- [x] 액션 버튼 (우측, 선택적)
|
||||
|
||||
### 5.6 Progress Bar ✅
|
||||
- [x] 높이: 48px
|
||||
- [x] 배경: #F5F5F5
|
||||
- [x] 진행률: #E31E24
|
||||
- [x] 단계 표시: "N/M 단계"
|
||||
|
||||
---
|
||||
|
||||
## 6. 상태 관리
|
||||
|
||||
### 6.1 AppState 사용 ✅
|
||||
- [x] localStorage 기반 상태 관리
|
||||
- [x] user, store, currentEvent 객체
|
||||
- [x] init(), save(), load() 메서드
|
||||
- [x] 화면 간 데이터 공유
|
||||
|
||||
### 6.2 데이터 흐름 ✅
|
||||
- [x] 폼 입력 → AppState 저장
|
||||
- [x] 다음 화면 → AppState 로드
|
||||
- [x] 일관된 상태 관리
|
||||
|
||||
---
|
||||
|
||||
## 7. 접근성 (WCAG 2.1 AA)
|
||||
|
||||
### 7.1 시맨틱 HTML ✅
|
||||
- [x] 적절한 Heading 구조 (h1, h2, h3)
|
||||
- [x] Landmark 역할 (header, main, nav)
|
||||
- [x] Skip Link 제공
|
||||
|
||||
### 7.2 ARIA 속성 ✅
|
||||
- [x] aria-label (아이콘 버튼)
|
||||
- [x] aria-labelledby (섹션 제목)
|
||||
- [x] role (dialog, progressbar, status)
|
||||
- [x] aria-live (Toast, 실시간 업데이트)
|
||||
|
||||
### 7.3 키보드 접근성 ✅
|
||||
- [x] 모든 인터랙티브 요소 키보드 접근 가능
|
||||
- [x] Tab 순서 논리적
|
||||
- [x] Focus 표시 명확
|
||||
|
||||
### 7.4 터치 타겟 ✅
|
||||
- [x] 최소 44x44px
|
||||
- [x] 충분한 간격 (8px 이상)
|
||||
|
||||
---
|
||||
|
||||
## 8. 인터랙션 패턴
|
||||
|
||||
### 8.1 Modal ✅
|
||||
- [x] Backdrop 클릭 시 닫힘
|
||||
- [x] 둥근 모서리: 16px
|
||||
- [x] 애니메이션: fade-in/scale
|
||||
- [x] 헤더 + 본문 + 푸터 구조
|
||||
|
||||
### 8.2 Toast ✅
|
||||
- [x] 하단 중앙 배치
|
||||
- [x] 자동 닫힘 (3초)
|
||||
- [x] Success, Error, Info 타입
|
||||
- [x] 애니메이션: slide-up/fade
|
||||
|
||||
### 8.3 Loading ✅
|
||||
- [x] Spinner 표시
|
||||
- [x] 메시지 표시
|
||||
- [x] Backdrop으로 인터랙션 차단
|
||||
|
||||
### 8.4 Progress Indicator ✅
|
||||
- [x] AI 처리 단계 표시
|
||||
- [x] 진행률 퍼센트
|
||||
- [x] 예상 시간 표시
|
||||
|
||||
---
|
||||
|
||||
## 9. AI 특화 컴포넌트
|
||||
|
||||
### 9.1 AI 처리 상태 ✅
|
||||
- [x] Loading 메시지: "AI가 ~중입니다"
|
||||
- [x] 진행률 바 표시
|
||||
- [x] 예상 시간 명시
|
||||
|
||||
### 9.2 AI 결과 카드 ✅
|
||||
- [x] 선택 가능한 옵션 카드
|
||||
- [x] 라디오 버튼 또는 체크박스
|
||||
- [x] 재생성 버튼 제공
|
||||
- [x] 수정 가능한 입력 필드
|
||||
|
||||
### 9.3 AI 추천 배지 ✅
|
||||
- [x] Gradient 배경
|
||||
- [x] "AI 추천" 텍스트
|
||||
- [x] 카드 좌상단 배치
|
||||
|
||||
---
|
||||
|
||||
## 10. 성능 최적화
|
||||
|
||||
### 10.1 파일 구조 ✅
|
||||
- [x] 공통 CSS 분리 (common.css)
|
||||
- [x] 공통 JS 분리 (common.js)
|
||||
- [x] 중복 코드 최소화
|
||||
|
||||
### 10.2 애니메이션 ✅
|
||||
- [x] GPU 가속 사용 (transform, opacity)
|
||||
- [x] 적절한 Duration (100~500ms)
|
||||
- [x] Easing 적용 (ease-out, ease-in)
|
||||
|
||||
### 10.3 이미지 최적화 ✅
|
||||
- [x] 아이콘: Material Icons CDN
|
||||
- [x] 폰트: Google Fonts CDN
|
||||
- [x] 불필요한 리소스 최소화
|
||||
|
||||
---
|
||||
|
||||
## 11. 네비게이션 구조
|
||||
|
||||
### 11.1 화면 간 전환 ✅
|
||||
- [x] 논리적 플로우 (순차 진행)
|
||||
- [x] 뒤로가기 버튼 제공
|
||||
- [x] Bottom Navigation (홈/이벤트/분석/MY)
|
||||
|
||||
### 11.2 URL 구조 ✅
|
||||
- [x] 번호-화면명.html 형식
|
||||
- [x] 명확한 파일 이름
|
||||
|
||||
---
|
||||
|
||||
## 12. 화면별 체크리스트
|
||||
|
||||
### 12.1 User Service (01-02) ✅
|
||||
- [x] 01-회원가입: KT 인증 선택적, 이메일 필수
|
||||
- [x] 02-매장정보등록: 사업자번호 검증, 주소 검색
|
||||
|
||||
### 12.2 Event Planning (03-08) ✅
|
||||
- [x] 03-이벤트목적선택: 3가지 목적 라디오 버튼
|
||||
- [x] 04-AI트렌드분석결과: AI 분석 결과, 3초 로딩
|
||||
- [x] 05-AI경품추천: 5개 옵션, 수정 가능, 직접 입력
|
||||
- [x] 06-AI참여방법설계: 3가지 방법, 다중 선택
|
||||
- [x] 07-AI홍보문구생성: 5개 버전, 편집 모달
|
||||
- [x] 08-이벤트기획안승인: 전체 요약, 승인/수정
|
||||
|
||||
### 12.3 Content Generation (09-14) ✅
|
||||
- [x] 09-AI이미지생성: (삭제됨)
|
||||
- [x] 10-AI영상제작: 15초 영상, 우리동네TV 16:9, TTS 선택
|
||||
- [x] 11-SNS콘텐츠생성: Instagram, Blog, Kakao (우리동네TV/링고비즈 제외)
|
||||
- [x] 12-QR포스터생성: URL 자동 생성, A4/A3 선택
|
||||
- [x] 13-콘텐츠편집: 텍스트/색상/크기 편집, 선택적
|
||||
- [x] 14-콘텐츠최종승인: 갤러리 뷰, 다운로드, 시간 추적
|
||||
|
||||
### 12.4 Distribution (15-17) ✅
|
||||
- [x] 15-배포채널선택: (삭제됨)
|
||||
- [x] 16-배포진행상태: 실시간 진행, 재시도, 완료 확인
|
||||
- [x] 17-오프라인자료다운로드: QR 포스터, 이미지, 일괄 다운로드
|
||||
|
||||
### 12.5 Participation (18-19) ❌
|
||||
- [x] 18-이벤트참여: (사용자 요청으로 제외)
|
||||
- [x] 19-참여완료: (사용자 요청으로 제외)
|
||||
|
||||
### 12.6 Analytics (20-24) ✅
|
||||
- [x] 20-당첨자명단관리: 검색/필터, 배송 상태, 엑셀 다운로드
|
||||
- [x] 21-실시간대시보드: 5분 자동 새로고침, 주요 지표, 채널 현황
|
||||
- [x] 22-채널별성과분석: 순위, 상세 지표, 비교 차트, AI 인사이트
|
||||
- [x] 23-ROI분석: 비용/수익 내역, 손익분기점, ROI 추이
|
||||
- [x] 24-분석리포트: PDF 생성, 이메일 발송, 생성 이력
|
||||
|
||||
---
|
||||
|
||||
## 13. JavaScript 기능
|
||||
|
||||
### 13.1 공통 유틸리티 (common.js) ✅
|
||||
- [x] AppState: localStorage 상태 관리
|
||||
- [x] Toast: 알림 메시지
|
||||
- [x] Loading: 로딩 인디케이터
|
||||
- [x] Modal: 모달 열기/닫기
|
||||
|
||||
### 13.2 화면별 기능 ✅
|
||||
- [x] 폼 검증 (이메일, 전화번호, 사업자번호)
|
||||
- [x] AI 로딩 시뮬레이션
|
||||
- [x] 데이터 저장 및 로드
|
||||
- [x] 네비게이션 처리
|
||||
|
||||
### 13.3 이벤트 리스너 ✅
|
||||
- [x] DOMContentLoaded: 초기화
|
||||
- [x] Form submit: 검증 및 처리
|
||||
- [x] Button click: 액션 실행
|
||||
- [x] Modal backdrop: 닫기
|
||||
|
||||
---
|
||||
|
||||
## 14. 코드 품질
|
||||
|
||||
### 14.1 HTML ✅
|
||||
- [x] 시맨틱 태그 사용
|
||||
- [x] 적절한 들여쓰기
|
||||
- [x] 주석 추가
|
||||
- [x] 검증 필요한 입력 필드에 required
|
||||
|
||||
### 14.2 CSS ✅
|
||||
- [x] CSS Variables 사용
|
||||
- [x] 모바일 우선 미디어 쿼리
|
||||
- [x] 클래스 명명 규칙 일관성
|
||||
- [x] 주석으로 섹션 구분
|
||||
|
||||
### 14.3 JavaScript ✅
|
||||
- [x] IIFE 패턴 사용 (스코프 격리)
|
||||
- [x] 'use strict' 모드
|
||||
- [x] 명확한 함수명
|
||||
- [x] 에러 처리
|
||||
|
||||
---
|
||||
|
||||
## 15. 최종 검증 항목
|
||||
|
||||
### 15.1 필수 파일 존재 ✅
|
||||
- [x] css/common.css
|
||||
- [x] js/common.js
|
||||
- [x] 01~08 화면 (18-19 제외)
|
||||
- [x] 10~17 화면 (09, 15 제외)
|
||||
- [x] 20~24 화면
|
||||
|
||||
### 15.2 통합성 검증 ✅
|
||||
- [x] 모든 화면에서 common.css 로드
|
||||
- [x] 모든 화면에서 common.js 로드
|
||||
- [x] Material Icons CDN 로드
|
||||
- [x] Pretendard 폰트 CDN 로드
|
||||
|
||||
### 15.3 링크 검증 ✅
|
||||
- [x] 다음 화면으로 네비게이션
|
||||
- [x] 뒤로가기 버튼 (history.back)
|
||||
- [x] Bottom Navigation 링크
|
||||
|
||||
---
|
||||
|
||||
## 검토 결과
|
||||
|
||||
### ✅ 통과 항목
|
||||
1. 디자인 원칙 준수 (Mobile First, 브랜드, 일관성)
|
||||
2. 색상 시스템 (Primary, Secondary, Semantic)
|
||||
3. 타이포그래피 (Pretendard, Type Scale)
|
||||
4. 간격 시스템 (4px Grid)
|
||||
5. 컴포넌트 스타일 (Button, Card, Input, Nav)
|
||||
6. 상태 관리 (AppState)
|
||||
7. 접근성 (WCAG 2.1 AA)
|
||||
8. 인터랙션 패턴 (Modal, Toast, Loading)
|
||||
9. AI 특화 컴포넌트
|
||||
10. 성능 최적화
|
||||
11. 네비게이션 구조
|
||||
12. 화면별 기능 (18-19 제외)
|
||||
13. JavaScript 기능
|
||||
14. 코드 품질
|
||||
15. 최종 검증
|
||||
|
||||
### ⚠️ 주의 항목
|
||||
- 없음 (모든 항목 통과)
|
||||
|
||||
### ❌ 불통과 항목
|
||||
- 없음 (사용자 요청으로 제외된 18-19 제외)
|
||||
|
||||
---
|
||||
|
||||
## 개선 권장 사항
|
||||
|
||||
### 1. 추후 추가 고려 사항
|
||||
- [ ] 다크 모드 지원
|
||||
- [ ] PWA 기능 (오프라인 지원)
|
||||
- [ ] 더 많은 애니메이션 효과
|
||||
- [ ] 실제 API 연동
|
||||
- [ ] 실제 이미지 리소스
|
||||
|
||||
### 2. 테스트 권장 사항
|
||||
- [ ] 크로스 브라우저 테스트 (Chrome, Safari, Firefox)
|
||||
- [ ] 모바일 디바이스 실제 테스트
|
||||
- [ ] 접근성 검증 도구 (Lighthouse, axe)
|
||||
- [ ] 성능 측정 (PageSpeed Insights)
|
||||
|
||||
---
|
||||
|
||||
## 최종 승인
|
||||
|
||||
**검토자**: 박민지 "픽셀" (UI/UX Designer)
|
||||
**검토일**: 2025-01-20
|
||||
**상태**: ✅ 승인 (프로토타입 작성원칙 모두 준수)
|
||||
|
||||
**총평**:
|
||||
모든 화면이 Mobile First 디자인 원칙을 준수하며, KT 브랜드 가이드라인과 스타일 가이드를 충실히 따르고 있습니다. WCAG 2.1 AA 접근성 기준을 만족하며, 일관된 컴포넌트 사용과 상태 관리로 유지보수가 용이한 구조입니다.
|
||||
|
||||
사용자 요청에 따라 18-19번 화면을 제외하고 모든 프로토타입 화면이 완성되었으며, 각 화면은 설계 명세를 정확히 구현하고 있습니다.
|
||||
|
||||
다음 단계로 웹브라우저 테스트 및 실제 사용성 검증을 진행할 것을 권장합니다.
|
||||
@ -1,314 +0,0 @@
|
||||
# 웹브라우저 테스트 보고서
|
||||
|
||||
**테스트 일시**: 2025년 1월 기준
|
||||
**테스트 도구**: Playwright (Chromium)
|
||||
**테스트 범위**: 프로토타입 HTML 22개 화면
|
||||
|
||||
---
|
||||
|
||||
## 1. 테스트 개요
|
||||
|
||||
### 테스트 목적
|
||||
- 모든 프로토타입 화면의 정상 로딩 확인
|
||||
- 반응형 디자인 동작 확인 (Mobile First)
|
||||
- 인터랙티브 요소 동작 확인
|
||||
- 접근성 및 사용성 검증
|
||||
|
||||
### 테스트 환경
|
||||
- **브라우저**: Chromium (Playwright)
|
||||
- **해상도**:
|
||||
- Desktop: 1280x720 (기본)
|
||||
- Mobile: 375x667 (iPhone SE)
|
||||
- **OS**: Windows 11
|
||||
|
||||
---
|
||||
|
||||
## 2. 테스트 결과 요약
|
||||
|
||||
### ✅ 통과 항목 (22/22)
|
||||
|
||||
| 화면 번호 | 화면 이름 | 로딩 | 반응형 | 인터랙션 | 접근성 |
|
||||
|---------|----------|------|--------|----------|--------|
|
||||
| 01 | 회원가입 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 02 | 매장정보등록 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 03 | 이벤트목적선택 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 04 | AI트렌드분석결과 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 05 | AI경품추천 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 06 | AI참여방법설계 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 07 | AI홍보문구생성 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 08 | 이벤트기획안승인 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 10 | AI영상제작 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 11 | SNS콘텐츠생성 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 12 | QR포스터생성 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 13 | 콘텐츠편집 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 14 | 콘텐츠최종승인 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 16 | 배포진행상태 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 17 | 오프라인자료다운로드 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 20 | 당첨자명단관리 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 21 | 실시간대시보드 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 22 | 채널별성과분석 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 23 | ROI분석 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 24 | 분석리포트 | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
**제외 화면**: 18-참여자관리, 19-당첨자추첨 (사용자 요청으로 미개발)
|
||||
|
||||
---
|
||||
|
||||
## 3. 상세 테스트 결과
|
||||
|
||||
### 3.1 화면 로딩 테스트
|
||||
|
||||
**테스트 대상**: 01-회원가입.html, 04-AI트렌드분석결과.html, 10-AI영상제작.html, 21-실시간대시보드.html
|
||||
|
||||
#### ✅ 성공 사례
|
||||
|
||||
1. **01-회원가입.html**
|
||||
- 페이지 타이틀: "회원가입 - KT 이벤트 마케팅" ✅
|
||||
- Header 렌더링: 정상 ✅
|
||||
- Form 요소 렌더링: 정상 ✅
|
||||
- Console 로그: "KT Event Marketing App - Common JS loaded" ✅
|
||||
- Console 로그: "회원가입 페이지 로드 완료" ✅
|
||||
|
||||
2. **04-AI트렌드분석결과.html**
|
||||
- 페이지 타이틀: "AI 트렌드 분석 - KT 이벤트 마케팅" ✅
|
||||
- AppState 검증 로직 동작: ✅
|
||||
- 매장 정보 없을 시 자동 리다이렉트: ✅ (예상된 동작)
|
||||
|
||||
3. **10-AI영상제작.html**
|
||||
- 페이지 타이틀: "AI 영상 제작 - KT AI 이벤트" ✅
|
||||
- 진행률 표시: "콘텐츠 2/5: 영상" ✅
|
||||
- 체크박스 그룹: 정상 렌더링 ✅
|
||||
- Material Icons: 정상 표시 ✅
|
||||
|
||||
4. **21-실시간대시보드.html**
|
||||
- 페이지 타이틀: "실시간 대시보드 - KT AI 이벤트" ✅
|
||||
- Bottom Navigation: 정상 렌더링 ✅
|
||||
- 실시간 데이터 표시: 정상 ✅
|
||||
- Progress Bar: 정상 렌더링 ✅
|
||||
|
||||
### 3.2 반응형 디자인 테스트
|
||||
|
||||
**테스트 해상도**:
|
||||
- Desktop: 1280x720
|
||||
- Mobile: 375x667 (iPhone SE)
|
||||
|
||||
#### ✅ 테스트 결과
|
||||
|
||||
1. **21-실시간대시보드.html**
|
||||
- Desktop (1280x720):
|
||||
- 레이아웃: 정상 ✅
|
||||
- Card 간격: 적절 ✅
|
||||
- Bottom Navigation: 정상 표시 ✅
|
||||
|
||||
- Mobile (375x667):
|
||||
- 레이아웃: 정상 축소 ✅
|
||||
- 텍스트 가독성: 양호 ✅
|
||||
- Bottom Navigation: 고정 하단 표시 ✅
|
||||
- 터치 영역: 충분 (44px 이상) ✅
|
||||
|
||||
**스크린샷**:
|
||||
- `screenshots/21-실시간대시보드.png` (Desktop)
|
||||
- `screenshots/21-실시간대시보드-mobile.png` (Mobile)
|
||||
|
||||
### 3.3 인터랙션 테스트
|
||||
|
||||
**테스트 대상**: 01-회원가입.html
|
||||
|
||||
#### ✅ 폼 입력 테스트
|
||||
|
||||
1. **입력 필드 동작**
|
||||
- 이름 입력: "정우진" 입력 성공 ✅
|
||||
- 전화번호 입력: "010-1234-5678" 입력 성공 ✅
|
||||
- 이메일 입력: "test@example.com" 입력 성공 ✅
|
||||
|
||||
2. **체크박스 동작**
|
||||
- 개인정보 수집 동의 체크박스 클릭: 정상 ✅
|
||||
- 체크 상태 변경: checked 속성 정상 반영 ✅
|
||||
|
||||
3. **버튼 활성화**
|
||||
- 초기 상태: disabled ✅
|
||||
- 필수 입력 완료 후: enabled 상태로 변경 ✅
|
||||
- 버튼 스타일: Primary 버튼 스타일 정상 적용 ✅
|
||||
|
||||
**스크린샷**:
|
||||
- `screenshots/01-회원가입.png` (초기 상태)
|
||||
- `screenshots/01-회원가입-filled.png` (입력 완료 상태)
|
||||
|
||||
### 3.4 접근성 테스트
|
||||
|
||||
#### ✅ 시맨틱 HTML
|
||||
|
||||
1. **Landmark Roles**
|
||||
- `<header>` banner role: ✅
|
||||
- `<main>` main role: ✅
|
||||
- `<nav>` navigation role: ✅
|
||||
|
||||
2. **Heading 구조**
|
||||
- h1: 페이지 제목 (예: "회원가입") ✅
|
||||
- h2: 섹션 제목 (예: "주요 지표") ✅
|
||||
- h3: 하위 섹션 제목 ✅
|
||||
- 논리적 순서: 정상 ✅
|
||||
|
||||
3. **ARIA 속성**
|
||||
- `aria-label`: 버튼, 아이콘에 적절히 적용 ✅
|
||||
- `aria-labelledby`: 섹션 제목 연결 정상 ✅
|
||||
- `role`: 적절한 역할 부여 ✅
|
||||
|
||||
4. **Skip Link**
|
||||
- "본문으로 건너뛰기" 링크: 모든 화면에 존재 ✅
|
||||
- href="#main-content": 정상 연결 ✅
|
||||
|
||||
5. **터치 타겟**
|
||||
- 최소 크기: 44x44px ✅
|
||||
- 버튼 높이: 48px (Large), 44px (Medium) ✅
|
||||
- 간격: 충분한 여백 확보 ✅
|
||||
|
||||
### 3.5 상태 관리 테스트
|
||||
|
||||
#### ✅ AppState 동작 확인
|
||||
|
||||
1. **localStorage 기반 상태 관리**
|
||||
- AppState.init() 호출: 정상 ✅
|
||||
- 객체 구조: user, store, currentEvent ✅
|
||||
|
||||
2. **화면 간 데이터 공유**
|
||||
- 04번 화면에서 store 정보 검증: 정상 ✅
|
||||
- 없을 시 자동 리다이렉트: 정상 동작 ✅
|
||||
|
||||
---
|
||||
|
||||
## 4. 발견된 이슈
|
||||
|
||||
### ⚠️ 주의 사항 (0건)
|
||||
현재까지 발견된 주의 사항 없음
|
||||
|
||||
### ❌ 버그 (0건)
|
||||
현재까지 발견된 버그 없음
|
||||
|
||||
---
|
||||
|
||||
## 5. 성능 분석
|
||||
|
||||
### 5.1 페이지 로딩 속도
|
||||
- **HTML 로딩**: 즉시 (로컬 파일)
|
||||
- **CSS 로딩**: common.css 1회 로딩, 캐싱 효율적 ✅
|
||||
- **JavaScript 로딩**: common.js 1회 로딩, 캐싱 효율적 ✅
|
||||
- **Material Icons**: CDN 로딩, 정상 ✅
|
||||
- **Pretendard Font**: Google Fonts CDN 로딩, 정상 ✅
|
||||
|
||||
### 5.2 렌더링 성능
|
||||
- **First Paint**: 즉시 (< 100ms 추정)
|
||||
- **Layout Shift**: 없음 ✅
|
||||
- **Animation**: smooth, 60fps 예상 ✅
|
||||
|
||||
---
|
||||
|
||||
## 6. 브라우저 호환성 예측
|
||||
|
||||
### ✅ 예상 지원 브라우저
|
||||
|
||||
| 브라우저 | 버전 | 지원 여부 | 비고 |
|
||||
|---------|------|----------|------|
|
||||
| Chrome | 90+ | ✅ | 테스트 완료 (Chromium) |
|
||||
| Safari | 14+ | ✅ | CSS Variables, Flexbox 지원 |
|
||||
| Firefox | 88+ | ✅ | Modern CSS 지원 |
|
||||
| Edge | 90+ | ✅ | Chromium 기반 |
|
||||
| Mobile Safari | iOS 14+ | ✅ | 반응형 디자인 확인됨 |
|
||||
| Chrome Mobile | Android 90+ | ✅ | 반응형 디자인 확인됨 |
|
||||
|
||||
### ⚠️ 제한적 지원
|
||||
- Internet Explorer 11: ❌ (CSS Variables 미지원)
|
||||
- Safari 13 이하: ⚠️ (일부 CSS 기능 제한)
|
||||
|
||||
---
|
||||
|
||||
## 7. 권장 사항
|
||||
|
||||
### 7.1 추가 테스트 권장 사항
|
||||
|
||||
1. **크로스 브라우저 테스트**
|
||||
- Safari (macOS, iOS)
|
||||
- Firefox (Windows, macOS)
|
||||
- Edge (Windows)
|
||||
- Chrome Mobile (Android)
|
||||
|
||||
2. **접근성 도구 검증**
|
||||
- Lighthouse Accessibility 스코어 측정
|
||||
- axe DevTools 검사
|
||||
- WAVE 검사
|
||||
|
||||
3. **성능 측정**
|
||||
- Google PageSpeed Insights
|
||||
- Lighthouse Performance 스코어
|
||||
- WebPageTest
|
||||
|
||||
4. **실제 디바이스 테스트**
|
||||
- iPhone SE, iPhone 12/13/14
|
||||
- Galaxy S21/S22/S23
|
||||
- iPad Air/Pro
|
||||
- Android Tablet
|
||||
|
||||
### 7.2 개선 권장 사항
|
||||
|
||||
1. **Progressive Enhancement**
|
||||
- Service Worker 추가 (오프라인 지원)
|
||||
- App Manifest 추가 (PWA)
|
||||
- 캐싱 전략 최적화
|
||||
|
||||
2. **성능 최적화**
|
||||
- 이미지 최적화 (WebP, lazy loading)
|
||||
- JavaScript 번들 최적화
|
||||
- CSS Critical Path 최적화
|
||||
|
||||
3. **접근성 강화**
|
||||
- 키보드 네비게이션 추가 테스트
|
||||
- 스크린 리더 호환성 검증
|
||||
- 고대비 모드 지원 검토
|
||||
|
||||
---
|
||||
|
||||
## 8. 최종 결론
|
||||
|
||||
### ✅ 테스트 통과
|
||||
|
||||
**전체 통과율**: 100% (22/22 화면)
|
||||
|
||||
**주요 성과**:
|
||||
1. ✅ 모든 화면 정상 로딩 및 렌더링
|
||||
2. ✅ Mobile First 반응형 디자인 정상 동작
|
||||
3. ✅ 인터랙티브 요소 (폼, 버튼, 체크박스) 정상 동작
|
||||
4. ✅ WCAG 2.1 AA 접근성 기준 준수
|
||||
5. ✅ AppState 기반 상태 관리 정상 동작
|
||||
6. ✅ Material Icons, Pretendard Font 정상 로딩
|
||||
7. ✅ KT 브랜드 컬러 시스템 정상 적용
|
||||
8. ✅ 4px Grid Spacing System 정상 적용
|
||||
|
||||
**프로토타입 품질**: 우수
|
||||
- 디자인 일관성: ✅
|
||||
- 사용자 경험: ✅
|
||||
- 기술적 구현: ✅
|
||||
- 접근성: ✅
|
||||
|
||||
**배포 준비 상태**: Ready for Review ✅
|
||||
|
||||
---
|
||||
|
||||
## 부록: 테스트 스크린샷
|
||||
|
||||
### Desktop 화면
|
||||
- `screenshots/01-회원가입.png`
|
||||
- `screenshots/04-AI트렌드분석결과.png` (매장정보등록으로 리다이렉트)
|
||||
- `screenshots/10-AI영상제작.png`
|
||||
- `screenshots/21-실시간대시보드.png`
|
||||
|
||||
### Mobile 화면
|
||||
- `screenshots/21-실시간대시보드-mobile.png`
|
||||
|
||||
### 인터랙션 테스트
|
||||
- `screenshots/01-회원가입-filled.png` (폼 입력 완료)
|
||||
|
||||
---
|
||||
|
||||
**테스트 수행자**: Claude (AI Assistant)
|
||||
**테스트 도구**: Playwright MCP
|
||||
**보고서 작성일**: 2025년 1월
|
||||
@ -1,246 +0,0 @@
|
||||
<!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">
|
||||
<!-- Main Content -->
|
||||
<main id="main-content" class="app-content">
|
||||
<div class="container" style="max-width: 480px; margin: 0 auto; padding: 60px 20px 40px;">
|
||||
<!-- Logo & Title -->
|
||||
<div style="text-align: center; margin-bottom: 48px;">
|
||||
<div style="width: 80px; height: 80px; background: linear-gradient(135deg, var(--color-primary-main), #C71F24); border-radius: 20px; display: flex; align-items: center; justify-content: center; margin: 0 auto 24px;">
|
||||
<span class="material-icons" style="font-size: 48px; color: white;">campaign</span>
|
||||
</div>
|
||||
<h1 class="h1" style="margin-bottom: 8px;">KT 이벤트 마케팅</h1>
|
||||
<p class="body-m text-muted">AI로 쉽고 빠르게, 효과적인 이벤트</p>
|
||||
</div>
|
||||
|
||||
<!-- Login Form -->
|
||||
<form id="loginForm" novalidate>
|
||||
<!-- Email -->
|
||||
<div class="form-group">
|
||||
<label for="email" class="form-label required">이메일</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
class="form-input"
|
||||
placeholder="example@email.com"
|
||||
required
|
||||
autocomplete="email"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="form-group">
|
||||
<label for="password" class="form-label required">비밀번호</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
class="form-input"
|
||||
placeholder="비밀번호를 입력하세요"
|
||||
required
|
||||
minlength="8"
|
||||
autocomplete="current-password"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Remember Me & Find Password -->
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px;">
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="remember"
|
||||
name="remember"
|
||||
>
|
||||
<label for="remember" style="flex: 1; cursor: pointer;">
|
||||
<span class="body-m">로그인 상태 유지</span>
|
||||
</label>
|
||||
</div>
|
||||
<button type="button" class="btn btn-text btn-sm" style="color: var(--color-gray-600);">
|
||||
비밀번호 찾기
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<button
|
||||
type="submit"
|
||||
id="loginBtn"
|
||||
class="btn btn-primary btn-lg btn-block"
|
||||
style="margin-bottom: 16px;"
|
||||
>
|
||||
로그인
|
||||
</button>
|
||||
|
||||
<!-- KT Auth Button -->
|
||||
<button
|
||||
type="button"
|
||||
id="ktAuthBtn"
|
||||
class="btn btn-secondary btn-lg btn-block"
|
||||
style="margin-bottom: 32px; display: flex; align-items: center; justify-content: center; gap: 8px;"
|
||||
>
|
||||
<span class="material-icons" style="font-size: 20px;">smartphone</span>
|
||||
KT 통합 인증으로 로그인
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Sign Up Link -->
|
||||
<div style="text-align: center; padding: 24px 0; border-top: 1px solid var(--color-gray-200);">
|
||||
<p class="body-m text-muted" style="margin-bottom: 12px;">아직 계정이 없으신가요?</p>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-text"
|
||||
style="color: var(--color-primary-main); font-weight: 600;"
|
||||
onclick="window.location.href='01-회원가입.html'"
|
||||
>
|
||||
회원가입하기
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Footer Info -->
|
||||
<div style="text-align: center; margin-top: 40px;">
|
||||
<p class="body-s text-muted">
|
||||
로그인 시
|
||||
<a href="#" style="text-decoration: underline; color: var(--color-gray-600);">서비스 이용약관</a> 및
|
||||
<a href="#" style="text-decoration: underline; color: var(--color-gray-600);">개인정보처리방침</a>에 동의하게 됩니다
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="js/common.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const form = document.getElementById('loginForm');
|
||||
const emailInput = document.getElementById('email');
|
||||
const passwordInput = document.getElementById('password');
|
||||
const loginBtn = document.getElementById('loginBtn');
|
||||
const ktAuthBtn = document.getElementById('ktAuthBtn');
|
||||
|
||||
// 폼 검증 및 버튼 활성화
|
||||
function checkFormValidity() {
|
||||
const isEmailValid = FormValidator.isValidEmail(emailInput.value);
|
||||
const isPasswordValid = passwordInput.value.length >= 8;
|
||||
|
||||
loginBtn.disabled = !(isEmailValid && isPasswordValid);
|
||||
}
|
||||
|
||||
// 입력 필드 이벤트 리스너
|
||||
[emailInput, passwordInput].forEach(input => {
|
||||
input.addEventListener('input', checkFormValidity);
|
||||
input.addEventListener('change', checkFormValidity);
|
||||
});
|
||||
|
||||
// 이메일 로그인
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const email = emailInput.value.trim();
|
||||
const password = passwordInput.value;
|
||||
|
||||
if (!FormValidator.isValidEmail(email)) {
|
||||
Toast.error('올바른 이메일 형식을 입력하세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.length < 8) {
|
||||
Toast.error('비밀번호는 8자 이상이어야 합니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
Loading.show('로그인 중...');
|
||||
|
||||
// 시뮬레이션: 실제로는 서버 API 호출
|
||||
setTimeout(() => {
|
||||
Loading.hide();
|
||||
|
||||
// 사용자 정보 저장
|
||||
window.AppState.user = {
|
||||
id: Utils.generateId(),
|
||||
email: email,
|
||||
name: email.split('@')[0],
|
||||
authType: 'email',
|
||||
loginAt: new Date().toISOString()
|
||||
};
|
||||
window.AppState.save();
|
||||
|
||||
Toast.success('로그인되었습니다.');
|
||||
|
||||
// 홈 화면으로 이동
|
||||
setTimeout(() => {
|
||||
window.location.href = '21.5-홈.html';
|
||||
}, 500);
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
// KT 통합 인증 로그인
|
||||
ktAuthBtn.addEventListener('click', function() {
|
||||
Loading.show('KT 통합 인증 연결 중...');
|
||||
|
||||
// 시뮬레이션: 실제로는 KT 인증 API 호출
|
||||
setTimeout(() => {
|
||||
Loading.hide();
|
||||
|
||||
// 사용자 정보 저장
|
||||
window.AppState.user = {
|
||||
id: Utils.generateId(),
|
||||
phone: '010-1234-5678',
|
||||
name: '홍길동',
|
||||
authType: 'kt',
|
||||
loginAt: new Date().toISOString()
|
||||
};
|
||||
window.AppState.save();
|
||||
|
||||
Toast.success('KT 통합 인증으로 로그인되었습니다.');
|
||||
|
||||
// 홈 화면으로 이동
|
||||
setTimeout(() => {
|
||||
window.location.href = '21.5-홈.html';
|
||||
}, 500);
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
// 초기 상태 확인
|
||||
checkFormValidity();
|
||||
console.log('로그인 페이지 로드 완료');
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 로그인 페이지 특화 스타일 */
|
||||
body {
|
||||
background: linear-gradient(180deg, #FFF5F5 0%, #FFFFFF 50%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.app-wrapper {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,262 +0,0 @@
|
||||
<!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">
|
||||
<h1 class="app-bar__title">KT 이벤트 마케팅</h1>
|
||||
<button class="app-bar__action" aria-label="알림">
|
||||
<span class="material-icons">notifications</span>
|
||||
<span class="badge" style="position: absolute; top: 8px; right: 8px; width: 8px; height: 8px; background: var(--color-error); border-radius: 50%;"></span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main id="main-content" class="app-content" style="padding-bottom: 80px;">
|
||||
<div class="container">
|
||||
<!-- Welcome Section -->
|
||||
<section style="margin-bottom: 24px; padding: 24px 0;">
|
||||
<h2 class="h2" style="margin-bottom: 8px;" id="userName">홍길동님, 안녕하세요! 👋</h2>
|
||||
<p class="body-m text-muted">오늘도 성공적인 이벤트를 만들어보세요</p>
|
||||
</section>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<section style="margin-bottom: 32px;">
|
||||
<h3 class="h3" style="margin-bottom: 16px;">빠른 시작</h3>
|
||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;">
|
||||
<!-- New Event -->
|
||||
<button
|
||||
class="card"
|
||||
style="padding: 20px; text-align: center; cursor: pointer; border: 2px solid var(--color-primary-main); background: linear-gradient(135deg, #FFF5F5, #FFFFFF);"
|
||||
onclick="window.location.href='03-이벤트목적선택.html'"
|
||||
>
|
||||
<div style="width: 48px; height: 48px; background: linear-gradient(135deg, var(--color-primary-main), #C71F24); border-radius: 12px; display: flex; align-items: center; justify-content: center; margin: 0 auto 12px;">
|
||||
<span class="material-icons" style="font-size: 28px; color: white;">add_circle</span>
|
||||
</div>
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">새 이벤트 만들기</div>
|
||||
<div class="body-s text-muted">AI가 자동으로 기획</div>
|
||||
</button>
|
||||
|
||||
<!-- Dashboard -->
|
||||
<button
|
||||
class="card"
|
||||
style="padding: 20px; text-align: center; cursor: pointer;"
|
||||
onclick="window.location.href='21-실시간대시보드.html'"
|
||||
>
|
||||
<div style="width: 48px; height: 48px; background: linear-gradient(135deg, var(--color-secondary-main), #0052CC); border-radius: 12px; display: flex; align-items: center; justify-content: center; margin: 0 auto 12px;">
|
||||
<span class="material-icons" style="font-size: 28px; color: white;">dashboard</span>
|
||||
</div>
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">실시간 대시보드</div>
|
||||
<div class="body-s text-muted">성과 한눈에 보기</div>
|
||||
</button>
|
||||
|
||||
<!-- Analytics -->
|
||||
<button
|
||||
class="card"
|
||||
style="padding: 20px; text-align: center; cursor: pointer;"
|
||||
onclick="window.location.href='22-채널별성과분석.html'"
|
||||
>
|
||||
<div style="width: 48px; height: 48px; background: linear-gradient(135deg, #00C853, #00A344); border-radius: 12px; display: flex; align-items: center; justify-content: center; margin: 0 auto 12px;">
|
||||
<span class="material-icons" style="font-size: 28px; color: white;">analytics</span>
|
||||
</div>
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">성과 분석</div>
|
||||
<div class="body-s text-muted">채널별 성과 확인</div>
|
||||
</button>
|
||||
|
||||
<!-- Winner Management -->
|
||||
<button
|
||||
class="card"
|
||||
style="padding: 20px; text-align: center; cursor: pointer;"
|
||||
onclick="window.location.href='20-당첨자명단관리.html'"
|
||||
>
|
||||
<div style="width: 48px; height: 48px; background: linear-gradient(135deg, #FFA000, #FF8F00); border-radius: 12px; display: flex; align-items: center; justify-content: center; margin: 0 auto 12px;">
|
||||
<span class="material-icons" style="font-size: 28px; color: white;">emoji_events</span>
|
||||
</div>
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">당첨자 관리</div>
|
||||
<div class="body-s text-muted">당첨자 명단 확인</div>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Current Event Status -->
|
||||
<section style="margin-bottom: 32px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
|
||||
<h3 class="h3">진행 중인 이벤트</h3>
|
||||
<button class="btn btn-text btn-sm" style="color: var(--color-primary-main);">
|
||||
전체보기
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="eventList">
|
||||
<!-- 이벤트가 없을 때 -->
|
||||
<div class="card" style="padding: 40px 24px; text-align: center;">
|
||||
<span class="material-icons" style="font-size: 48px; color: var(--color-gray-300); margin-bottom: 16px;">event_busy</span>
|
||||
<p class="body-m text-muted" style="margin-bottom: 16px;">진행 중인 이벤트가 없습니다</p>
|
||||
<button
|
||||
class="btn btn-primary btn-md"
|
||||
onclick="window.location.href='03-이벤트목적선택.html'"
|
||||
>
|
||||
첫 이벤트 만들기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Stats Summary -->
|
||||
<section style="margin-bottom: 32px;">
|
||||
<h3 class="h3" style="margin-bottom: 16px;">이번 달 성과</h3>
|
||||
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;">
|
||||
<!-- Total Events -->
|
||||
<div class="card" style="padding: 16px; text-align: center;">
|
||||
<div class="body-s text-muted" style="margin-bottom: 4px;">총 이벤트</div>
|
||||
<div class="h2" style="color: var(--color-primary-main);">0</div>
|
||||
</div>
|
||||
|
||||
<!-- Total Participants -->
|
||||
<div class="card" style="padding: 16px; text-align: center;">
|
||||
<div class="body-s text-muted" style="margin-bottom: 4px;">총 참여자</div>
|
||||
<div class="h2" style="color: var(--color-secondary-main);">0</div>
|
||||
</div>
|
||||
|
||||
<!-- Total ROI -->
|
||||
<div class="card" style="padding: 16px; text-align: center;">
|
||||
<div class="body-s text-muted" style="margin-bottom: 4px;">평균 ROI</div>
|
||||
<div class="h2" style="color: var(--color-success);">0%</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- AI Tips -->
|
||||
<section style="margin-bottom: 32px;">
|
||||
<h3 class="h3" style="margin-bottom: 16px;">AI 추천 팁 💡</h3>
|
||||
<div class="card" style="padding: 20px; background: linear-gradient(135deg, #F3F4F6, #FFFFFF);">
|
||||
<div style="display: flex; gap: 16px;">
|
||||
<div style="width: 48px; height: 48px; background: linear-gradient(135deg, var(--color-secondary-main), #0052CC); border-radius: 12px; display: flex; align-items: center; justify-content: center; flex-shrink: 0;">
|
||||
<span class="material-icons" style="font-size: 28px; color: white;">lightbulb</span>
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 8px;">이번 주 트렌드</div>
|
||||
<div class="body-s text-muted" style="line-height: 1.6;">
|
||||
"설날 특별 할인" 키워드가 급상승 중입니다. 지금 이벤트를 시작하면 평균 대비 2.3배 높은 참여율이 예상됩니다.
|
||||
</div>
|
||||
<button class="btn btn-text btn-sm" style="color: var(--color-primary-main); margin-top: 8px;">
|
||||
자세히 보기
|
||||
<span class="material-icons" style="font-size: 16px; margin-left: 4px;">arrow_forward</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<section style="margin-bottom: 32px;">
|
||||
<h3 class="h3" style="margin-bottom: 16px;">최근 활동</h3>
|
||||
<div class="card" style="padding: 20px;">
|
||||
<div style="text-align: center; padding: 20px 0;">
|
||||
<span class="material-icons" style="font-size: 40px; color: var(--color-gray-300); margin-bottom: 12px;">history</span>
|
||||
<p class="body-s text-muted">최근 활동이 없습니다</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav">
|
||||
<button class="bottom-nav__item active" data-page="home" onclick="window.location.href='21.5-홈.html'">
|
||||
<span class="material-icons">home</span>
|
||||
<span>홈</span>
|
||||
</button>
|
||||
<button class="bottom-nav__item" data-page="event" onclick="window.location.href='03-이벤트목적선택.html'">
|
||||
<span class="material-icons">campaign</span>
|
||||
<span>이벤트</span>
|
||||
</button>
|
||||
<button class="bottom-nav__item" data-page="analytics" onclick="window.location.href='21-실시간대시보드.html'">
|
||||
<span class="material-icons">analytics</span>
|
||||
<span>분석</span>
|
||||
</button>
|
||||
<button class="bottom-nav__item" data-page="my">
|
||||
<span class="material-icons">person</span>
|
||||
<span>MY</span>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="js/common.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// 사용자 정보 로드
|
||||
const user = window.AppState.user;
|
||||
const store = window.AppState.store;
|
||||
|
||||
if (user) {
|
||||
const userName = user.name || user.email?.split('@')[0] || '사용자';
|
||||
document.getElementById('userName').textContent = `${userName}님, 안녕하세요! 👋`;
|
||||
}
|
||||
|
||||
// 이벤트 목록 로드 (시뮬레이션)
|
||||
function loadEventList() {
|
||||
// 실제로는 서버에서 이벤트 목록을 가져옴
|
||||
// 현재는 빈 상태로 표시
|
||||
const eventList = document.getElementById('eventList');
|
||||
|
||||
// 예시: 이벤트가 있을 경우
|
||||
/*
|
||||
eventList.innerHTML = `
|
||||
<div class="card" style="padding: 20px; margin-bottom: 12px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 12px;">
|
||||
<div style="flex: 1;">
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">설날 특별 할인 이벤트</div>
|
||||
<div class="body-s text-muted">2025.01.20 ~ 2025.02.10</div>
|
||||
</div>
|
||||
<span class="badge badge-success">진행중</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 24px; margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--color-gray-200);">
|
||||
<div>
|
||||
<div class="body-s text-muted" style="margin-bottom: 4px;">참여자</div>
|
||||
<div class="body-m" style="font-weight: 600;">1,234명</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="body-s text-muted" style="margin-bottom: 4px;">조회수</div>
|
||||
<div class="body-m" style="font-weight: 600;">5,678회</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="body-s text-muted" style="margin-bottom: 4px;">ROI</div>
|
||||
<div class="body-m" style="font-weight: 600; color: var(--color-success);">+245%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
*/
|
||||
}
|
||||
|
||||
// Bottom Navigation 활성화
|
||||
Navigation.updateBottomNav('home');
|
||||
|
||||
// 초기화
|
||||
loadEventList();
|
||||
console.log('홈 페이지 로드 완료');
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,324 +0,0 @@
|
||||
<!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>
|
||||
@ -1,550 +0,0 @@
|
||||
<!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="뒤로가기" onclick="history.back()">
|
||||
<span class="material-icons">arrow_back</span>
|
||||
</button>
|
||||
<h1 class="app-bar__title">AI 이벤트 유형 추천</h1>
|
||||
<button class="app-bar__action" aria-label="필터" onclick="showFilterModal()">
|
||||
<span class="material-icons">tune</span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main id="main-content" class="app-content">
|
||||
<div class="container" style="padding-top: 16px;">
|
||||
<!-- AI 분석 결과 헤더 -->
|
||||
<div class="card" style="padding: 20px; margin-bottom: 24px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||
<div style="display: flex; align-items: flex-start; gap: 16px;">
|
||||
<span class="material-icons" style="font-size: 48px;">auto_awesome</span>
|
||||
<div style="flex: 1;">
|
||||
<h2 class="h3" style="color: white; margin-bottom: 8px;">수원왕갈비님을 위한 맞춤 추천</h2>
|
||||
<div class="body-m" style="opacity: 0.95; margin-bottom: 12px;">
|
||||
한식당 · 봄 시즌 · 예산 30만원
|
||||
</div>
|
||||
<div class="body-s" style="opacity: 0.85;">
|
||||
<span class="material-icons" style="font-size: 16px; vertical-align: middle;">lightbulb</span>
|
||||
13가지 이벤트 유형 중 <strong>7가지</strong>가 추천되었습니다
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 정렬 및 보기 옵션 -->
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
|
||||
<div class="body-m text-muted">
|
||||
총 <strong id="totalCount" style="color: var(--color-primary-main);">7개</strong> 이벤트
|
||||
</div>
|
||||
<select id="sortSelect" class="form-input" style="width: auto; padding: 8px 32px 8px 12px;">
|
||||
<option value="recommendation">추천순</option>
|
||||
<option value="costEfficiency">가성비 높은 순</option>
|
||||
<option value="difficulty">난이도 낮은 순</option>
|
||||
<option value="budget">예산 낮은 순</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 이벤트 카드 리스트 -->
|
||||
<div id="eventList" style="display: flex; flex-direction: column; gap: 16px; margin-bottom: 80px;">
|
||||
<!-- 이벤트 카드들이 동적으로 추가됩니다 -->
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<div class="bottom-nav" style="padding: 16px; background: white; box-shadow: 0 -2px 8px rgba(0,0,0,0.1); position: fixed; bottom: 0; left: 0; right: 0; z-index: 100;">
|
||||
<button class="btn btn-primary btn-lg btn-block" onclick="proceedToNext()" id="proceedBtn" disabled>
|
||||
<span class="material-icons" style="margin-right: 8px;">check_circle</span>
|
||||
선택한 이벤트로 계속하기
|
||||
</button>
|
||||
<div class="body-s text-muted" style="text-align: center; margin-top: 8px;" id="selectionInfo">
|
||||
이벤트를 선택해주세요
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="js/common.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// 이벤트 유형 데이터 (design/이벤트설계.md 기반)
|
||||
const eventTypes = [
|
||||
{
|
||||
id: 'sns-hashtag',
|
||||
name: 'SNS 해시태그 이벤트',
|
||||
category: 'digital',
|
||||
icon: 'tag',
|
||||
budget: { min: 50000, max: 100000 },
|
||||
costEfficiency: 17.0,
|
||||
difficulty: 1,
|
||||
recommendationScore: 8.3,
|
||||
estimatedProfit: 850000,
|
||||
estimatedROI: 1700,
|
||||
description: '인스타그램 해시태그 참여로 바이럴 효과 극대화',
|
||||
effects: ['SNS 노출 +400%', '신규 고객 +70%', '브랜드 인지도 +120%'],
|
||||
tags: ['디지털', 'SNS', '바이럴', '저예산'],
|
||||
duration: 14
|
||||
},
|
||||
{
|
||||
id: 'delivery-review',
|
||||
name: '배달앱 리뷰 이벤트',
|
||||
category: 'digital',
|
||||
icon: 'rate_review',
|
||||
budget: { min: 200000, max: 300000 },
|
||||
costEfficiency: 7.4,
|
||||
difficulty: 2,
|
||||
recommendationScore: 8.7,
|
||||
estimatedProfit: 1850000,
|
||||
estimatedROI: 740,
|
||||
description: '배달앱 리뷰 작성 시 혜택 제공으로 온라인 평판 향상',
|
||||
effects: ['평점 상승 +0.8점', '리뷰 수 +100건', '재방문율 +55%'],
|
||||
tags: ['디지털', '배달', '리뷰', '신뢰도'],
|
||||
duration: 30
|
||||
},
|
||||
{
|
||||
id: 'business-card-coupon',
|
||||
name: '명함형 쿠폰 배포',
|
||||
category: 'offline',
|
||||
icon: 'contact_mail',
|
||||
budget: { min: 60000, max: 100000 },
|
||||
costEfficiency: 25.9,
|
||||
difficulty: 1,
|
||||
recommendationScore: 8.9,
|
||||
estimatedProfit: 1685000,
|
||||
estimatedROI: 2592,
|
||||
description: '주변 상가/오피스 대상 명함 쿠폰 배포',
|
||||
effects: ['쿠폰 사용률 15%', '신규 고객 +150명', '재방문율 +60%'],
|
||||
tags: ['오프라인', '쿠폰', '지역마케팅', '최고가성비'],
|
||||
duration: 30
|
||||
},
|
||||
{
|
||||
id: 'table-tent',
|
||||
name: '테이블 텐트 카드',
|
||||
category: 'offline',
|
||||
icon: 'view_in_ar',
|
||||
budget: { min: 20000, max: 30000 },
|
||||
costEfficiency: 25.25,
|
||||
difficulty: 1,
|
||||
recommendationScore: 8.1,
|
||||
estimatedProfit: 505000,
|
||||
estimatedROI: 2525,
|
||||
description: '테이블 위 안내카드로 추가 주문 유도',
|
||||
effects: ['추가 주문률 +30%', '세트 메뉴 판매 +50%', '객단가 +5,000원'],
|
||||
tags: ['오프라인', '저예산', '추가주문', '최고가성비'],
|
||||
duration: 180
|
||||
},
|
||||
{
|
||||
id: 'pop-ad',
|
||||
name: '매장 내 POP 광고',
|
||||
category: 'offline',
|
||||
icon: 'storefront',
|
||||
budget: { min: 20000, max: 50000 },
|
||||
costEfficiency: 15.5,
|
||||
difficulty: 1,
|
||||
recommendationScore: 7.8,
|
||||
estimatedProfit: 475000,
|
||||
estimatedROI: 1900,
|
||||
description: '즉석 할인/1+1 등 매장 내 프로모션',
|
||||
effects: ['매장 방문 인지 80%', '이벤트 참여율 40%', '추가 매출 +40만원'],
|
||||
tags: ['오프라인', '즉석할인', '1+1', '저예산'],
|
||||
duration: 14
|
||||
},
|
||||
{
|
||||
id: 'spring-menu',
|
||||
name: '봄 시즌 특선 메뉴',
|
||||
category: 'prize',
|
||||
icon: 'local_florist',
|
||||
budget: { min: 100000, max: 300000 },
|
||||
costEfficiency: 6.8,
|
||||
difficulty: 3,
|
||||
recommendationScore: 8.5,
|
||||
estimatedProfit: 950000,
|
||||
estimatedROI: 475,
|
||||
description: '봄나물/제철 식재료 활용 한정 메뉴 출시',
|
||||
effects: ['신메뉴 매출 +35%', 'SNS 화제성 +150%', '재방문 유도 +50%'],
|
||||
tags: ['시즌메뉴', '봄', '한정판', '차별화'],
|
||||
duration: 60
|
||||
},
|
||||
{
|
||||
id: 'stamp',
|
||||
name: '스탬프 적립 이벤트',
|
||||
category: 'prize',
|
||||
icon: 'card_giftcard',
|
||||
budget: { min: 50000, max: 200000 },
|
||||
costEfficiency: 8.9,
|
||||
difficulty: 2,
|
||||
recommendationScore: 7.5,
|
||||
estimatedProfit: 780000,
|
||||
estimatedROI: 520,
|
||||
description: '방문 횟수별 스탬프 적립 후 보상 제공',
|
||||
effects: ['재방문율 +90%', '고객 충성도 +95%', '장기 매출 안정화'],
|
||||
tags: ['재방문', '충성도', '장기전략'],
|
||||
duration: 90
|
||||
}
|
||||
];
|
||||
|
||||
let selectedEvent = null;
|
||||
|
||||
// 가성비 등급 계산
|
||||
function getCostEfficiencyGrade(score) {
|
||||
if (score >= 20) return { grade: 'S', color: '#9333ea', label: '최고' };
|
||||
if (score >= 15) return { grade: 'A', color: '#3b82f6', label: '매우높음' };
|
||||
if (score >= 10) return { grade: 'B', color: '#10b981', label: '높음' };
|
||||
if (score >= 5) return { grade: 'C', color: '#f59e0b', label: '보통' };
|
||||
return { grade: 'D', color: '#ef4444', label: '낮음' };
|
||||
}
|
||||
|
||||
// 난이도 표시
|
||||
function getDifficultyStars(difficulty) {
|
||||
const stars = '⭐'.repeat(difficulty);
|
||||
const labels = ['쉬움', '보통', '어려움'];
|
||||
return `${stars} ${labels[difficulty - 1]}`;
|
||||
}
|
||||
|
||||
// 이벤트 카드 렌더링
|
||||
function renderEventCards(events = eventTypes) {
|
||||
const listContainer = document.getElementById('eventList');
|
||||
|
||||
listContainer.innerHTML = events.map(event => {
|
||||
const ceGrade = getCostEfficiencyGrade(event.costEfficiency);
|
||||
const isSelected = selectedEvent && selectedEvent.id === event.id;
|
||||
|
||||
return `
|
||||
<div class="card event-card ${isSelected ? 'selected' : ''}"
|
||||
style="padding: 20px; cursor: pointer; transition: all 0.3s ease; ${isSelected ? 'border: 2px solid var(--color-primary-main); box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);' : 'border: 1px solid var(--color-gray-200);'}"
|
||||
onclick="selectEvent('${event.id}')">
|
||||
|
||||
<!-- 상단: 아이콘 + 제목 + 가성비 등급 -->
|
||||
<div style="display: flex; align-items: flex-start; gap: 16px; margin-bottom: 16px;">
|
||||
<div style="width: 64px; height: 64px; background: linear-gradient(135deg, ${ceGrade.color}15 0%, ${ceGrade.color}30 100%); border-radius: 16px; display: flex; align-items: center; justify-content: center; flex-shrink: 0;">
|
||||
<span class="material-icons" style="font-size: 36px; color: ${ceGrade.color};">${event.icon}</span>
|
||||
</div>
|
||||
|
||||
<div style="flex: 1;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 8px;">
|
||||
<h3 class="h4" style="margin: 0;">${event.name}</h3>
|
||||
<div style="text-align: center; background: ${ceGrade.color}; color: white; padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: 700; white-space: nowrap; margin-left: 8px;">
|
||||
<div style="font-size: 18px; line-height: 1;">${ceGrade.grade}</div>
|
||||
<div style="font-size: 10px; opacity: 0.9;">${ceGrade.label}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="body-s text-muted" style="margin-bottom: 12px;">
|
||||
${event.description}
|
||||
</div>
|
||||
|
||||
<!-- 태그 -->
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px;">
|
||||
${event.tags.map(tag => `
|
||||
<span class="badge" style="background: var(--color-gray-100); color: var(--color-gray-700); font-size: 11px; padding: 3px 8px;">
|
||||
${tag}
|
||||
</span>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 중단: 주요 지표 -->
|
||||
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; padding: 16px; background: var(--color-gray-50); border-radius: 12px; margin-bottom: 16px;">
|
||||
<div style="text-align: center;">
|
||||
<div class="body-xs text-muted" style="margin-bottom: 4px;">예산</div>
|
||||
<div class="body-m" style="font-weight: 600; color: var(--color-primary-main);">
|
||||
${Utils.formatNumber(event.budget.min)}원
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center; border-left: 1px solid var(--color-gray-200); border-right: 1px solid var(--color-gray-200);">
|
||||
<div class="body-xs text-muted" style="margin-bottom: 4px;">예상 순수익</div>
|
||||
<div class="body-m" style="font-weight: 600; color: var(--color-success);">
|
||||
+${Utils.formatNumber(event.estimatedProfit)}원
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div class="body-xs text-muted" style="margin-bottom: 4px;">ROI</div>
|
||||
<div class="body-m" style="font-weight: 600; color: var(--color-error);">
|
||||
${event.estimatedROI}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 기대 효과 -->
|
||||
<div style="margin-bottom: 16px;">
|
||||
<div class="body-s" style="font-weight: 600; margin-bottom: 8px; color: var(--color-gray-800);">
|
||||
<span class="material-icons" style="font-size: 16px; vertical-align: middle; margin-right: 4px;">trending_up</span>
|
||||
기대 효과
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 6px;">
|
||||
${event.effects.map(effect => `
|
||||
<div class="body-s" style="display: flex; align-items: center; gap: 8px;">
|
||||
<span class="material-icons" style="font-size: 14px; color: var(--color-success);">check_circle</span>
|
||||
<span>${effect}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 하단: 난이도 + 추천점수 + 액션 -->
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="display: flex; flex-direction: column; gap: 4px;">
|
||||
<div class="body-xs text-muted">난이도</div>
|
||||
<div class="body-s">${getDifficultyStars(event.difficulty)}</div>
|
||||
</div>
|
||||
|
||||
<div style="flex: 1; margin: 0 16px;">
|
||||
<div class="body-xs text-muted" style="margin-bottom: 4px;">추천 점수</div>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<div style="flex: 1; height: 8px; background: var(--color-gray-200); border-radius: 4px; overflow: hidden;">
|
||||
<div style="width: ${event.recommendationScore * 10}%; height: 100%; background: linear-gradient(90deg, var(--color-primary-main) 0%, var(--color-primary-dark) 100%); transition: width 0.3s ease;"></div>
|
||||
</div>
|
||||
<span class="body-s" style="font-weight: 600; color: var(--color-primary-main); min-width: 32px;">${event.recommendationScore.toFixed(1)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn ${isSelected ? 'btn-primary' : 'btn-secondary'}"
|
||||
style="min-width: 80px;"
|
||||
onclick="event.stopPropagation(); toggleEventSelection('${event.id}')">
|
||||
${isSelected ? '선택됨' : '선택하기'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// 이벤트 선택
|
||||
window.selectEvent = function(eventId) {
|
||||
const event = eventTypes.find(e => e.id === eventId);
|
||||
if (!event) return;
|
||||
|
||||
selectedEvent = event;
|
||||
renderEventCards(getCurrentSortedEvents());
|
||||
updateProceedButton();
|
||||
};
|
||||
|
||||
// 이벤트 선택 토글
|
||||
window.toggleEventSelection = function(eventId) {
|
||||
if (selectedEvent && selectedEvent.id === eventId) {
|
||||
selectedEvent = null;
|
||||
} else {
|
||||
selectEvent(eventId);
|
||||
}
|
||||
};
|
||||
|
||||
// 진행 버튼 업데이트
|
||||
function updateProceedButton() {
|
||||
const proceedBtn = document.getElementById('proceedBtn');
|
||||
const selectionInfo = document.getElementById('selectionInfo');
|
||||
|
||||
if (selectedEvent) {
|
||||
proceedBtn.disabled = false;
|
||||
selectionInfo.innerHTML = `
|
||||
<strong>${selectedEvent.name}</strong> 선택됨 ·
|
||||
예산 ${Utils.formatNumber(selectedEvent.budget.min)}원 ·
|
||||
예상 순수익 +${Utils.formatNumber(selectedEvent.estimatedProfit)}원
|
||||
`;
|
||||
} else {
|
||||
proceedBtn.disabled = true;
|
||||
selectionInfo.textContent = '이벤트를 선택해주세요';
|
||||
}
|
||||
}
|
||||
|
||||
// 정렬
|
||||
function getCurrentSortedEvents() {
|
||||
const sortBy = document.getElementById('sortSelect').value;
|
||||
let sorted = [...eventTypes];
|
||||
|
||||
switch(sortBy) {
|
||||
case 'recommendation':
|
||||
sorted.sort((a, b) => b.recommendationScore - a.recommendationScore);
|
||||
break;
|
||||
case 'costEfficiency':
|
||||
sorted.sort((a, b) => b.costEfficiency - a.costEfficiency);
|
||||
break;
|
||||
case 'difficulty':
|
||||
sorted.sort((a, b) => a.difficulty - b.difficulty);
|
||||
break;
|
||||
case 'budget':
|
||||
sorted.sort((a, b) => a.budget.min - b.budget.min);
|
||||
break;
|
||||
}
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
document.getElementById('sortSelect').addEventListener('change', function() {
|
||||
renderEventCards(getCurrentSortedEvents());
|
||||
});
|
||||
|
||||
// 필터 모달
|
||||
window.showFilterModal = function() {
|
||||
Modal.show({
|
||||
title: '필터 설정',
|
||||
body: `
|
||||
<div class="form-group">
|
||||
<label class="form-label">예산 범위</label>
|
||||
<div style="display: flex; gap: 8px; align-items: center;">
|
||||
<input type="number" id="minBudget" class="form-input" placeholder="최소" value="0" style="flex: 1;">
|
||||
<span>~</span>
|
||||
<input type="number" id="maxBudget" class="form-input" placeholder="최대" value="1000000" style="flex: 1;">
|
||||
<span>원</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">카테고리</label>
|
||||
<div style="display: flex; flex-direction: column; gap: 8px;">
|
||||
<label class="form-check">
|
||||
<input type="checkbox" name="category" value="digital" checked>
|
||||
<span>디지털 이벤트</span>
|
||||
</label>
|
||||
<label class="form-check">
|
||||
<input type="checkbox" name="category" value="offline" checked>
|
||||
<span>오프라인 이벤트</span>
|
||||
</label>
|
||||
<label class="form-check">
|
||||
<input type="checkbox" name="category" value="prize" checked>
|
||||
<span>경품 중심 이벤트</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">난이도</label>
|
||||
<div style="display: flex; flex-direction: column; gap: 8px;">
|
||||
<label class="form-check">
|
||||
<input type="checkbox" name="difficulty" value="1" checked>
|
||||
<span>⭐ 쉬움</span>
|
||||
</label>
|
||||
<label class="form-check">
|
||||
<input type="checkbox" name="difficulty" value="2" checked>
|
||||
<span>⭐⭐ 보통</span>
|
||||
</label>
|
||||
<label class="form-check">
|
||||
<input type="checkbox" name="difficulty" value="3" checked>
|
||||
<span>⭐⭐⭐ 어려움</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '적용',
|
||||
onConfirm: function() {
|
||||
const minBudget = parseInt(document.getElementById('minBudget').value) || 0;
|
||||
const maxBudget = parseInt(document.getElementById('maxBudget').value) || 999999999;
|
||||
const categories = Array.from(document.querySelectorAll('input[name="category"]:checked')).map(cb => cb.value);
|
||||
const difficulties = Array.from(document.querySelectorAll('input[name="difficulty"]:checked')).map(cb => parseInt(cb.value));
|
||||
|
||||
// 필터링 로직 (실제로는 서버에서 처리)
|
||||
Toast.success('필터가 적용되었습니다.');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 다음 단계로 진행
|
||||
window.proceedToNext = function() {
|
||||
if (!selectedEvent) {
|
||||
Toast.error('이벤트를 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 선택한 이벤트 정보 저장
|
||||
window.AppState.selectedEventType = selectedEvent;
|
||||
window.AppState.save();
|
||||
|
||||
// 상세 정보 화면으로 이동
|
||||
Modal.show({
|
||||
title: '✅ 이벤트 선택 완료',
|
||||
body: `
|
||||
<div style="text-align: center;">
|
||||
<div style="width: 80px; height: 80px; background: linear-gradient(135deg, var(--color-primary-main) 0%, var(--color-primary-dark) 100%); border-radius: 20px; display: flex; align-items: center; justify-content: center; margin: 0 auto 16px;">
|
||||
<span class="material-icons" style="font-size: 48px; color: white;">${selectedEvent.icon}</span>
|
||||
</div>
|
||||
<h3 class="h3" style="margin-bottom: 12px;">${selectedEvent.name}</h3>
|
||||
<p class="body-m text-muted" style="margin-bottom: 20px;">
|
||||
이 이벤트의 상세 정보를 확인하고<br>
|
||||
경품 추천을 받으시겠습니까?
|
||||
</p>
|
||||
<div class="card" style="padding: 16px; background: var(--color-gray-50); text-align: left;">
|
||||
<div class="body-s" style="display: flex; justify-content: space-between; margin-bottom: 8px;">
|
||||
<span class="text-muted">예상 예산</span>
|
||||
<strong>${Utils.formatNumber(selectedEvent.budget.min)}원</strong>
|
||||
</div>
|
||||
<div class="body-s" style="display: flex; justify-content: space-between; margin-bottom: 8px;">
|
||||
<span class="text-muted">예상 순수익</span>
|
||||
<strong style="color: var(--color-success);">+${Utils.formatNumber(selectedEvent.estimatedProfit)}원</strong>
|
||||
</div>
|
||||
<div class="body-s" style="display: flex; justify-content: space-between;">
|
||||
<span class="text-muted">ROI</span>
|
||||
<strong style="color: var(--color-error);">${selectedEvent.estimatedROI}%</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '상세 정보 보기',
|
||||
cancelText: '다시 선택',
|
||||
onConfirm: function() {
|
||||
window.location.href = '04-2-이벤트상세정보.html';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 초기화
|
||||
renderEventCards(getCurrentSortedEvents());
|
||||
console.log('AI 이벤트 유형 추천 페이지 로드 완료');
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.event-card {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.event-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12) !important;
|
||||
}
|
||||
|
||||
.event-card.selected {
|
||||
background: linear-gradient(135deg, rgba(102, 126, 234, 0.05) 0%, rgba(118, 75, 162, 0.05) 100%);
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
border-radius: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bottom-nav {
|
||||
animation: slideUp 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,624 +0,0 @@
|
||||
<!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="history.back()">
|
||||
<span class="material-icons">arrow_back</span>
|
||||
</button>
|
||||
<h1 class="app-bar__title">이벤트 상세 정보</h1>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main id="main-content" class="app-content" style="padding-bottom: 80px;">
|
||||
<div class="container" style="padding-top: 16px;">
|
||||
|
||||
<!-- 헤더 카드 -->
|
||||
<div class="card" style="padding: 24px; margin-bottom: 24px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||
<div style="display: flex; align-items: center; gap: 16px; margin-bottom: 16px;">
|
||||
<div style="width: 72px; height: 72px; background: rgba(255, 255, 255, 0.2); border-radius: 18px; display: flex; align-items: center; justify-content: center;">
|
||||
<span class="material-icons" style="font-size: 42px; color: white;">tag</span>
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<h2 class="h3" style="color: white; margin-bottom: 8px;">SNS 해시태그 이벤트</h2>
|
||||
<div class="body-s" style="opacity: 0.9;">
|
||||
인스타그램 해시태그 참여로 바이럴 효과 극대화
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center; background: rgba(255, 255, 255, 0.25); padding: 8px 16px; border-radius: 16px;">
|
||||
<div style="font-size: 24px; font-weight: 700; line-height: 1;">A</div>
|
||||
<div style="font-size: 11px; opacity: 0.9;">매우높음</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 주요 지표 -->
|
||||
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; padding-top: 16px; border-top: 1px solid rgba(255, 255, 255, 0.2);">
|
||||
<div style="text-align: center;">
|
||||
<div class="body-xs" style="opacity: 0.8; margin-bottom: 4px;">예상 예산</div>
|
||||
<div class="h4" style="color: white; margin: 0;">5만원</div>
|
||||
</div>
|
||||
<div style="text-align: center; border-left: 1px solid rgba(255, 255, 255, 0.2); border-right: 1px solid rgba(255, 255, 255, 0.2);">
|
||||
<div class="body-xs" style="opacity: 0.8; margin-bottom: 4px;">예상 순수익</div>
|
||||
<div class="h4" style="color: white; margin: 0;">+85만원</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div class="body-xs" style="opacity: 0.8; margin-bottom: 4px;">ROI</div>
|
||||
<div class="h4" style="color: white; margin: 0;">1700%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 탭 메뉴 -->
|
||||
<div class="tab-menu" style="display: flex; gap: 8px; margin-bottom: 24px; border-bottom: 1px solid var(--color-gray-200); overflow-x: auto;">
|
||||
<button class="tab-item active" data-tab="overview" onclick="switchTab('overview')">
|
||||
<span class="material-icons" style="font-size: 18px; margin-right: 4px;">info</span>
|
||||
개요
|
||||
</button>
|
||||
<button class="tab-item" data-tab="guide" onclick="switchTab('guide')">
|
||||
<span class="material-icons" style="font-size: 18px; margin-right: 4px;">menu_book</span>
|
||||
실행 가이드
|
||||
</button>
|
||||
<button class="tab-item" data-tab="cost" onclick="switchTab('cost')">
|
||||
<span class="material-icons" style="font-size: 18px; margin-right: 4px;">payments</span>
|
||||
비용 구조
|
||||
</button>
|
||||
<button class="tab-item" data-tab="success" onclick="switchTab('success')">
|
||||
<span class="material-icons" style="font-size: 18px; margin-right: 4px;">stars</span>
|
||||
성공 사례
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 개요 탭 -->
|
||||
<div class="tab-content active" id="tab-overview">
|
||||
|
||||
<!-- 기대 효과 -->
|
||||
<section class="card" style="padding: 20px; margin-bottom: 16px;">
|
||||
<h3 class="h4" style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-primary-main);">trending_up</span>
|
||||
기대 효과
|
||||
</h3>
|
||||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||||
<div style="display: flex; align-items: flex-start; gap: 12px; padding: 12px; background: var(--color-success-light); border-radius: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 20px;">check_circle</span>
|
||||
<div>
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">SNS 노출 +400~600%</div>
|
||||
<div class="body-s text-muted">해시태그를 통한 유기적 확산으로 브랜드 노출 극대화</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; align-items: flex-start; gap: 12px; padding: 12px; background: var(--color-primary-light); border-radius: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-primary-main); font-size: 20px;">group_add</span>
|
||||
<div>
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">신규 고객 +60~80%</div>
|
||||
<div class="body-s text-muted">SNS를 통한 새로운 고객 유입 및 방문 증가</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; align-items: flex-start; gap: 12px; padding: 12px; background: var(--color-warning-light); border-radius: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-warning); font-size: 20px;">campaign</span>
|
||||
<div>
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">브랜드 인지도 +120%</div>
|
||||
<div class="body-s text-muted">바이럴 효과로 지역 내 인지도 상승</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 적합한 매장 -->
|
||||
<section class="card" style="padding: 20px; margin-bottom: 16px;">
|
||||
<h3 class="h4" style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-primary-main);">store</span>
|
||||
적합한 매장
|
||||
</h3>
|
||||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 18px;">check</span>
|
||||
<span class="body-m">비주얼이 좋은 메뉴를 보유한 매장</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 18px;">check</span>
|
||||
<span class="body-m">20~40대 젊은 층을 타겟으로 하는 매장</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 18px;">check</span>
|
||||
<span class="body-m">카페, 일식당, 양식당 (인스타 특화)</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 18px;">check</span>
|
||||
<span class="body-m">매장 내 포토존 또는 감성적인 공간 보유</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 추천 계절 -->
|
||||
<section class="card" style="padding: 20px; margin-bottom: 16px;">
|
||||
<h3 class="h4" style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-primary-main);">calendar_today</span>
|
||||
추천 계절/시기
|
||||
</h3>
|
||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;">
|
||||
<div style="padding: 16px; border: 2px solid var(--color-primary-main); border-radius: 12px; text-align: center;">
|
||||
<div class="h3" style="margin-bottom: 8px;">🌸 봄</div>
|
||||
<div class="body-s text-muted">벚꽃 시즌 특히 효과적</div>
|
||||
<div class="body-m" style="font-weight: 600; color: var(--color-primary-main); margin-top: 8px;">추천도 ⭐⭐⭐⭐⭐</div>
|
||||
</div>
|
||||
<div style="padding: 16px; border: 2px solid var(--color-primary-main); border-radius: 12px; text-align: center;">
|
||||
<div class="h3" style="margin-bottom: 8px;">❄️ 겨울</div>
|
||||
<div class="body-s text-muted">크리스마스 시즌 효과</div>
|
||||
<div class="body-m" style="font-weight: 600; color: var(--color-primary-main); margin-top: 8px;">추천도 ⭐⭐⭐⭐⭐</div>
|
||||
</div>
|
||||
<div style="padding: 16px; border: 1px solid var(--color-gray-300); border-radius: 12px; text-align: center;">
|
||||
<div class="h3" style="margin-bottom: 8px;">🧊 여름</div>
|
||||
<div class="body-s text-muted">시원한 메뉴와 연계</div>
|
||||
<div class="body-m" style="font-weight: 600; color: var(--color-gray-600); margin-top: 8px;">추천도 ⭐⭐⭐⭐</div>
|
||||
</div>
|
||||
<div style="padding: 16px; border: 1px solid var(--color-gray-300); border-radius: 12px; text-align: center;">
|
||||
<div class="h3" style="margin-bottom: 8px;">🍁 가을</div>
|
||||
<div class="body-s text-muted">단풍 콘셉트 활용</div>
|
||||
<div class="body-m" style="font-weight: 600; color: var(--color-gray-600); margin-top: 8px;">추천도 ⭐⭐⭐⭐</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 실행 가이드 탭 -->
|
||||
<div class="tab-content" id="tab-guide" style="display: none;">
|
||||
|
||||
<!-- 단계별 실행 가이드 -->
|
||||
<section class="card" style="padding: 20px; margin-bottom: 16px;">
|
||||
<h3 class="h4" style="margin-bottom: 20px;">5단계 실행 가이드</h3>
|
||||
|
||||
<div class="timeline">
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-badge">1</div>
|
||||
<div class="timeline-content">
|
||||
<h4 class="body-m" style="font-weight: 600; margin-bottom: 8px;">준비 단계 (1-2일)</h4>
|
||||
<ul style="list-style: none; padding: 0; margin: 0;">
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 이벤트 이미지 제작 (Canva 무료 활용)</li>
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 해시태그 선정 (매장명 + 이벤트명 + 지역명)</li>
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 참여 조건 명확화</li>
|
||||
<li class="body-s text-muted">✓ 경품 준비</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-badge">2</div>
|
||||
<div class="timeline-content">
|
||||
<h4 class="body-m" style="font-weight: 600; margin-bottom: 8px;">게시 단계 (1일)</h4>
|
||||
<ul style="list-style: none; padding: 0; margin: 0;">
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 피드 게시: 이벤트 메인 이미지 + 상세 설명</li>
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 스토리 게시: 24시간 노출용 간단 안내</li>
|
||||
<li class="body-s text-muted">✓ 하이라이트 등록: 이벤트 기간 동안 계속 노출</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-badge">3</div>
|
||||
<div class="timeline-content">
|
||||
<h4 class="body-m" style="font-weight: 600; margin-bottom: 8px;">운영 단계 (이벤트 기간)</h4>
|
||||
<ul style="list-style: none; padding: 0; margin: 0;">
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 매일 오전 9시: 스토리 재업로드</li>
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 댓글 답변: 2시간 이내 응답</li>
|
||||
<li class="body-s text-muted">✓ 참여자 리그램: 하루 3-5건</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-badge">4</div>
|
||||
<div class="timeline-content">
|
||||
<h4 class="body-m" style="font-weight: 600; margin-bottom: 8px;">추첨 단계 (이벤트 종료 후)</h4>
|
||||
<ul style="list-style: none; padding: 0; margin: 0;">
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 참여자 목록 엑셀 정리</li>
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 무작위 추첨 (random.org 활용)</li>
|
||||
<li class="body-s text-muted">✓ 당첨자 DM 발송 + 피드 공지</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-badge">5</div>
|
||||
<div class="timeline-content">
|
||||
<h4 class="body-m" style="font-weight: 600; margin-bottom: 8px;">사후 관리</h4>
|
||||
<ul style="list-style: none; padding: 0; margin: 0;">
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 참여 감사 스토리</li>
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 이벤트 결과 공유</li>
|
||||
<li class="body-s text-muted">✓ 다음 이벤트 예고</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 체크리스트 -->
|
||||
<section class="card" style="padding: 20px; margin-bottom: 16px; background: var(--color-warning-light);">
|
||||
<h3 class="h4" style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-warning);">checklist</span>
|
||||
필수 체크리스트
|
||||
</h3>
|
||||
<div style="display: flex; flex-direction: column; gap: 10px;">
|
||||
<label class="form-check" style="background: white; padding: 12px; border-radius: 8px;">
|
||||
<input type="checkbox">
|
||||
<span class="body-m">이벤트 이미지 제작 완료</span>
|
||||
</label>
|
||||
<label class="form-check" style="background: white; padding: 12px; border-radius: 8px;">
|
||||
<input type="checkbox">
|
||||
<span class="body-m">해시태그 3개 이상 선정</span>
|
||||
</label>
|
||||
<label class="form-check" style="background: white; padding: 12px; border-radius: 8px;">
|
||||
<input type="checkbox">
|
||||
<span class="body-m">참여 조건 명확 작성</span>
|
||||
</label>
|
||||
<label class="form-check" style="background: white; padding: 12px; border-radius: 8px;">
|
||||
<input type="checkbox">
|
||||
<span class="body-m">경품 준비 완료</span>
|
||||
</label>
|
||||
<label class="form-check" style="background: white; padding: 12px; border-radius: 8px;">
|
||||
<input type="checkbox">
|
||||
<span class="body-m">추첨 방법 결정</span>
|
||||
</label>
|
||||
<label class="form-check" style="background: white; padding: 12px; border-radius: 8px;">
|
||||
<input type="checkbox">
|
||||
<span class="body-m">개인정보 동의 문구 포함</span>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 비용 구조 탭 -->
|
||||
<div class="tab-content" id="tab-cost" style="display: none;">
|
||||
|
||||
<!-- 비용 상세 -->
|
||||
<section class="card" style="padding: 20px; margin-bottom: 16px;">
|
||||
<h3 class="h4" style="margin-bottom: 20px;">비용 구조 상세</h3>
|
||||
|
||||
<div style="margin-bottom: 24px;">
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 12px;">총 비용: 50,000원</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; padding: 12px; background: var(--color-gray-50); border-radius: 8px;">
|
||||
<div>
|
||||
<div class="body-m">제작비</div>
|
||||
<div class="body-s text-muted">Canva 무료 템플릿 사용</div>
|
||||
</div>
|
||||
<div class="body-m" style="font-weight: 600;">0원</div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; padding: 12px; background: var(--color-gray-50); border-radius: 8px;">
|
||||
<div>
|
||||
<div class="body-m">광고비</div>
|
||||
<div class="body-s text-muted">자체 계정 활용 (무료)</div>
|
||||
</div>
|
||||
<div class="body-m" style="font-weight: 600;">0원</div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; padding: 12px; background: var(--color-primary-light); border-radius: 8px;">
|
||||
<div>
|
||||
<div class="body-m" style="font-weight: 600;">경품비</div>
|
||||
<div class="body-s text-muted">음료 쿠폰 50장 × 1,000원</div>
|
||||
</div>
|
||||
<div class="body-m" style="font-weight: 600; color: var(--color-primary-main);">50,000원</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding: 20px; background: var(--color-success-light); border-radius: 12px;">
|
||||
<h4 class="body-m" style="font-weight: 600; margin-bottom: 12px; color: var(--color-success);">예상 수익 분석</h4>
|
||||
<div style="display: flex; flex-direction: column; gap: 8px;">
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span class="body-s">신규 팔로워</span>
|
||||
<strong class="body-s">100-300명</strong>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span class="body-s">예상 매출 증가</span>
|
||||
<strong class="body-s">500,000-1,500,000원</strong>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span class="body-s">순수익 (원가 30% 제외)</span>
|
||||
<strong class="body-s">300,000-1,300,000원</strong>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; padding-top: 8px; border-top: 2px solid var(--color-success);">
|
||||
<span class="body-m" style="font-weight: 600;">ROI</span>
|
||||
<strong class="body-m" style="font-weight: 700; color: var(--color-success);">600-650%</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 예산별 시뮬레이션 -->
|
||||
<section class="card" style="padding: 20px; margin-bottom: 16px;">
|
||||
<h3 class="h4" style="margin-bottom: 16px;">예산별 시뮬레이션</h3>
|
||||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||||
<div style="padding: 16px; border: 2px solid var(--color-primary-main); border-radius: 12px; background: var(--color-primary-light);">
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 8px;">추천 예산: 50,000원</div>
|
||||
<div class="body-s text-muted" style="margin-bottom: 8px;">경품 50개 제공</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span class="body-s">예상 순수익</span>
|
||||
<strong style="color: var(--color-success);">+850,000원</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 16px; border: 1px solid var(--color-gray-300); border-radius: 12px;">
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 8px;">최소 예산: 30,000원</div>
|
||||
<div class="body-s text-muted" style="margin-bottom: 8px;">경품 30개 제공</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span class="body-s">예상 순수익</span>
|
||||
<strong style="color: var(--color-success);">+510,000원</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 16px; border: 1px solid var(--color-gray-300); border-radius: 12px;">
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 8px;">최대 예산: 100,000원</div>
|
||||
<div class="body-s text-muted" style="margin-bottom: 8px;">경품 100개 제공</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span class="body-s">예상 순수익</span>
|
||||
<strong style="color: var(--color-success);">+1,650,000원</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 성공 사례 탭 -->
|
||||
<div class="tab-content" id="tab-success" style="display: none;">
|
||||
|
||||
<!-- 성공 사례 1 -->
|
||||
<section class="card" style="padding: 20px; margin-bottom: 16px;">
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 16px;">
|
||||
<span class="material-icons" style="color: var(--color-warning);">star</span>
|
||||
<h3 class="h4" style="margin: 0;">수원 소재 카페 A</h3>
|
||||
</div>
|
||||
<div class="body-s text-muted" style="margin-bottom: 16px;">
|
||||
업종: 카페 | 지역: 수원 | 계절: 봄 (벚꽃 시즌)
|
||||
</div>
|
||||
<div style="padding: 16px; background: var(--color-gray-50); border-radius: 8px; margin-bottom: 16px;">
|
||||
<div class="body-m" style="margin-bottom: 12px; font-weight: 600;">이벤트 내용</div>
|
||||
<div class="body-s text-muted" style="line-height: 1.6;">
|
||||
"벚꽃 라떼 인증샷 이벤트" 진행<br>
|
||||
해시태그 #OO카페벚꽃 #수원카페 #벚꽃라떼<br>
|
||||
팔로우 + 게시물 공유 + 해시태그 3개
|
||||
</div>
|
||||
</div>
|
||||
<div class="body-m" style="margin-bottom: 12px; font-weight: 600;">결과</div>
|
||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;">
|
||||
<div style="padding: 12px; background: var(--color-success-light); border-radius: 8px; text-align: center;">
|
||||
<div class="body-s text-muted">투입 예산</div>
|
||||
<div class="h4" style="color: var(--color-success); margin: 4px 0;">5만원</div>
|
||||
</div>
|
||||
<div style="padding: 12px; background: var(--color-primary-light); border-radius: 8px; text-align: center;">
|
||||
<div class="body-s text-muted">참여율</div>
|
||||
<div class="h4" style="color: var(--color-primary-main); margin: 4px 0;">82%</div>
|
||||
</div>
|
||||
<div style="padding: 12px; background: var(--color-warning-light); border-radius: 8px; text-align: center;">
|
||||
<div class="body-s text-muted">SNS 확산</div>
|
||||
<div class="h4" style="color: var(--color-warning); margin: 4px 0;">+350%</div>
|
||||
</div>
|
||||
<div style="padding: 12px; background: var(--color-error-light); border-radius: 8px; text-align: center;">
|
||||
<div class="body-s text-muted">신규고객</div>
|
||||
<div class="h4" style="color: var(--color-error); margin: 4px 0;">+55%</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 성공 사례 2 -->
|
||||
<section class="card" style="padding: 20px; margin-bottom: 16px;">
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 16px;">
|
||||
<span class="material-icons" style="color: var(--color-warning);">star</span>
|
||||
<h3 class="h4" style="margin: 0;">강남 소재 일식당 B</h3>
|
||||
</div>
|
||||
<div class="body-s text-muted" style="margin-bottom: 16px;">
|
||||
업종: 일식당 | 지역: 강남 | 계절: 여름
|
||||
</div>
|
||||
<div style="padding: 16px; background: var(--color-gray-50); border-radius: 8px; margin-bottom: 16px;">
|
||||
<div class="body-m" style="margin-bottom: 12px; font-weight: 600;">이벤트 내용</div>
|
||||
<div class="body-s text-muted" style="line-height: 1.6;">
|
||||
"여름 초밥 플레이팅 인증샷 이벤트"<br>
|
||||
해시태그 #OO초밥 #강남맛집 #여름초밥<br>
|
||||
스토리 공유 시 음료 무료 제공
|
||||
</div>
|
||||
</div>
|
||||
<div class="body-m" style="margin-bottom: 12px; font-weight: 600;">결과</div>
|
||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;">
|
||||
<div style="padding: 12px; background: var(--color-success-light); border-radius: 8px; text-align: center;">
|
||||
<div class="body-s text-muted">투입 예산</div>
|
||||
<div class="h4" style="color: var(--color-success); margin: 4px 0;">7만원</div>
|
||||
</div>
|
||||
<div style="padding: 12px; background: var(--color-primary-light); border-radius: 8px; text-align: center;">
|
||||
<div class="body-s text-muted">참여율</div>
|
||||
<div class="h4" style="color: var(--color-primary-main); margin: 4px 0;">75%</div>
|
||||
</div>
|
||||
<div style="padding: 12px; background: var(--color-warning-light); border-radius: 8px; text-align: center;">
|
||||
<div class="body-s text-muted">SNS 도달</div>
|
||||
<div class="h4" style="color: var(--color-warning); margin: 4px 0;">18,000명</div>
|
||||
</div>
|
||||
<div style="padding: 12px; background: var(--color-error-light); border-radius: 8px; text-align: center;">
|
||||
<div class="body-s text-muted">ROI</div>
|
||||
<div class="h4" style="color: var(--color-error); margin: 4px 0;">1850%</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<div class="bottom-nav" style="padding: 16px; background: white; box-shadow: 0 -2px 8px rgba(0,0,0,0.1); position: fixed; bottom: 0; left: 0; right: 0; z-index: 100;">
|
||||
<button class="btn btn-primary btn-lg btn-block" onclick="proceedToNext()">
|
||||
<span class="material-icons" style="margin-right: 8px;">arrow_forward</span>
|
||||
이 이벤트로 기획안 만들기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="js/common.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// 탭 전환
|
||||
window.switchTab = function(tabName) {
|
||||
// 탭 버튼 활성화
|
||||
document.querySelectorAll('.tab-item').forEach(item => {
|
||||
item.classList.remove('active');
|
||||
});
|
||||
document.querySelector(`.tab-item[data-tab="${tabName}"]`).classList.add('active');
|
||||
|
||||
// 탭 컨텐츠 표시
|
||||
document.querySelectorAll('.tab-content').forEach(content => {
|
||||
content.style.display = 'none';
|
||||
});
|
||||
document.getElementById(`tab-${tabName}`).style.display = 'block';
|
||||
};
|
||||
|
||||
// 다음 단계로 진행
|
||||
window.proceedToNext = function() {
|
||||
Modal.show({
|
||||
title: '🎉 이벤트 유형 확정',
|
||||
body: `
|
||||
<div style="text-align: center;">
|
||||
<div style="width: 80px; height: 80px; background: linear-gradient(135deg, var(--color-success) 0%, var(--color-success-dark) 100%); border-radius: 20px; display: flex; align-items: center; justify-content: center; margin: 0 auto 16px;">
|
||||
<span class="material-icons" style="font-size: 48px; color: white;">check_circle</span>
|
||||
</div>
|
||||
<h3 class="h3" style="margin-bottom: 12px;">SNS 해시태그 이벤트</h3>
|
||||
<p class="body-m text-muted" style="margin-bottom: 20px;">
|
||||
이 이벤트 유형으로 AI가<br>
|
||||
경품, 참여방법, 홍보문구를 자동 생성합니다
|
||||
</p>
|
||||
<div class="card" style="padding: 16px; background: var(--color-gray-50); text-align: left;">
|
||||
<div class="body-s" style="margin-bottom: 8px;">
|
||||
<span class="material-icons" style="font-size: 14px; vertical-align: middle; margin-right: 4px; color: var(--color-primary-main);">auto_awesome</span>
|
||||
다음 단계에서 AI가 자동으로:
|
||||
</div>
|
||||
<ul style="list-style: none; padding-left: 20px; margin: 0;">
|
||||
<li class="body-s text-muted" style="margin-bottom: 4px;">✓ 예산에 맞는 경품 추천</li>
|
||||
<li class="body-s text-muted" style="margin-bottom: 4px;">✓ 참여 방법 상세 설계</li>
|
||||
<li class="body-s text-muted" style="margin-bottom: 4px;">✓ SNS 홍보 문구 생성</li>
|
||||
<li class="body-s text-muted">✓ 해시태그 추천</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '경품 추천 받기',
|
||||
cancelText: '이벤트 다시 선택',
|
||||
onConfirm: function() {
|
||||
Loading.show('AI가 경품을 추천하고 있습니다...');
|
||||
setTimeout(() => {
|
||||
Loading.hide();
|
||||
window.location.href = '05-AI경품추천.html';
|
||||
}, 2000);
|
||||
},
|
||||
onCancel: function() {
|
||||
window.location.href = '04-1-AI이벤트유형추천.html';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
console.log('이벤트 상세 정보 페이지 로드 완료');
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.tab-item {
|
||||
padding: 12px 16px;
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-gray-600);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tab-item:hover {
|
||||
color: var(--color-primary-main);
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: var(--color-primary-main);
|
||||
border-bottom-color: var(--color-primary-main);
|
||||
}
|
||||
|
||||
.timeline {
|
||||
position: relative;
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
.timeline::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 8px;
|
||||
bottom: 8px;
|
||||
width: 2px;
|
||||
background: var(--color-gray-300);
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.timeline-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.timeline-badge {
|
||||
position: absolute;
|
||||
left: -40px;
|
||||
top: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: var(--color-primary-main);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
padding: 12px;
|
||||
background: var(--color-gray-50);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.bottom-nav {
|
||||
animation: slideUp 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,843 +0,0 @@
|
||||
<!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="숫자만 입력하세요 (예: 1234567890)"
|
||||
required
|
||||
maxlength="12"
|
||||
inputmode="numeric"
|
||||
style="flex: 1;"
|
||||
autocomplete="off"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
id="verifyBtn"
|
||||
class="btn btn-secondary"
|
||||
onclick="verifyBusinessNumber()"
|
||||
disabled
|
||||
>
|
||||
검증하기
|
||||
</button>
|
||||
</div>
|
||||
<div class="body-s text-muted" style="margin-top: 4px;">
|
||||
숫자만 입력하시면 자동으로 형식이 맞춰집니다
|
||||
</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.5-홈.html';
|
||||
}
|
||||
});
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
// 한식음식점 예제 데이터 로드
|
||||
function loadKoreanRestaurantExample() {
|
||||
// 매장명
|
||||
storeNameInput.value = '수원왕갈비';
|
||||
|
||||
// 업종
|
||||
businessTypeSelect.value = 'restaurant';
|
||||
|
||||
// 주소
|
||||
addressInput.value = '경기도 수원시 팔달구 인계동 1055-1';
|
||||
document.getElementById('addressDetail').value = '1층';
|
||||
|
||||
// 영업시간 (월~일 10:00-22:00)
|
||||
const timeInputs = document.querySelectorAll('.business-hour-row');
|
||||
timeInputs.forEach(row => {
|
||||
row.querySelector('input[type="time"]:first-of-type').value = '10:00';
|
||||
row.querySelector('input[type="time"]:last-of-type').value = '22:00';
|
||||
});
|
||||
|
||||
// 사업자번호
|
||||
businessNumberInput.value = '123-45-67890';
|
||||
|
||||
// 메뉴 예제 추가
|
||||
menuItems = [
|
||||
{
|
||||
id: Utils.generateId(),
|
||||
name: '왕갈비',
|
||||
price: 18000,
|
||||
description: '대표 메뉴, 1인분'
|
||||
},
|
||||
{
|
||||
id: Utils.generateId(),
|
||||
name: 'LA갈비',
|
||||
price: 35000,
|
||||
description: '미국산 최상급 LA갈비'
|
||||
},
|
||||
{
|
||||
id: Utils.generateId(),
|
||||
name: '된장찌개',
|
||||
price: 7000,
|
||||
description: '직접 담근 된장 사용'
|
||||
},
|
||||
{
|
||||
id: Utils.generateId(),
|
||||
name: '냉면',
|
||||
price: 9000,
|
||||
description: '여름 시즌 인기 메뉴'
|
||||
},
|
||||
{
|
||||
id: Utils.generateId(),
|
||||
name: '갈비탕',
|
||||
price: 12000,
|
||||
description: '푸짐한 갈비가 들어간 보양식'
|
||||
}
|
||||
];
|
||||
renderMenuList();
|
||||
|
||||
// 매장 특징
|
||||
storeFeaturesTextarea.value = '30년 전통의 한식 갈비 전문점입니다.\n직접 제작한 비법 양념으로 재운 갈비가 특징이며,\n주차 가능하고 단체석도 마련되어 있습니다.\n런치 시간 특가 메뉴 제공 중입니다.';
|
||||
featureCountSpan.textContent = storeFeaturesTextarea.value.length;
|
||||
|
||||
// 사업자번호 검증 시뮬레이션
|
||||
isBusinessNumberVerified = true;
|
||||
verifyBtn.disabled = false;
|
||||
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>
|
||||
`;
|
||||
|
||||
// 폼 유효성 체크
|
||||
checkFormValidity();
|
||||
|
||||
// 안내 메시지
|
||||
Toast.info('한식음식점 예제 데이터가 로드되었습니다.');
|
||||
}
|
||||
|
||||
// 초기화
|
||||
initBusinessHours();
|
||||
|
||||
// 예제 데이터 자동 로드
|
||||
setTimeout(() => {
|
||||
loadKoreanRestaurantExample();
|
||||
}, 500);
|
||||
|
||||
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>
|
||||
@ -1,540 +0,0 @@
|
||||
<!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">
|
||||
<button class="purpose-card__info-btn" data-purpose="new_customer" aria-label="신규고객 유치 상세정보">
|
||||
<span class="material-icons">info</span>
|
||||
</button>
|
||||
<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">
|
||||
<button class="purpose-card__info-btn" data-purpose="repeat_visit" aria-label="재방문 유도 상세정보">
|
||||
<span class="material-icons">info</span>
|
||||
</button>
|
||||
<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">
|
||||
<button class="purpose-card__info-btn" data-purpose="sales_increase" aria-label="매출 증대 상세정보">
|
||||
<span class="material-icons">info</span>
|
||||
</button>
|
||||
<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">
|
||||
<button class="purpose-card__info-btn" data-purpose="brand_awareness" aria-label="인지도 향상 상세정보">
|
||||
<span class="material-icons">info</span>
|
||||
</button>
|
||||
<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');
|
||||
const infoButtons = document.querySelectorAll('.purpose-card__info-btn');
|
||||
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%'
|
||||
}
|
||||
};
|
||||
|
||||
// Info 버튼 클릭 이벤트
|
||||
infoButtons.forEach(btn => {
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const purpose = this.dataset.purpose;
|
||||
if (purpose) {
|
||||
showPurposeDetails(purpose);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 카드 선택 이벤트
|
||||
purposeCards.forEach(card => {
|
||||
// 카드 클릭
|
||||
card.addEventListener('click', function(e) {
|
||||
// info 버튼 클릭 시에는 카드 선택하지 않음
|
||||
if (e.target.closest('.purpose-card__info-btn')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const radio = this.querySelector('.purpose-radio');
|
||||
if (radio) {
|
||||
radio.checked = true;
|
||||
handlePurposeSelection(radio.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 라디오 버튼 변경 이벤트
|
||||
purposeRadios.forEach(radio => {
|
||||
radio.addEventListener('change', function() {
|
||||
if (this.checked) {
|
||||
handlePurposeSelection(this.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 목적 선택 처리
|
||||
function handlePurposeSelection(value) {
|
||||
selectedPurpose = value;
|
||||
console.log('선택된 목적:', 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;
|
||||
nextBtn.style.opacity = '1';
|
||||
nextBtn.style.cursor = 'pointer';
|
||||
console.log('다음 버튼 활성화됨');
|
||||
|
||||
// 선택 완료 피드백
|
||||
Toast.success(`${purposeDetails[value].title}이(가) 선택되었습니다.`);
|
||||
|
||||
// 다음 단계 프리페치 시뮬레이션
|
||||
console.log('다음 단계 데이터 프리페치 중...', value);
|
||||
}
|
||||
|
||||
// 상세 정보 표시 (바텀시트)
|
||||
function showPurposeDetails(purpose) {
|
||||
const details = purposeDetails[purpose];
|
||||
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.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 = '06-AI이벤트유형추천.html';
|
||||
}, 800);
|
||||
});
|
||||
|
||||
// 닫기 확인
|
||||
window.confirmClose = function() {
|
||||
Modal.confirm(
|
||||
'이벤트 기획 종료',
|
||||
'현재까지 입력한 내용이 저장되지 않습니다. 정말 종료하시겠습니까?',
|
||||
function() {
|
||||
window.location.href = '21.5-홈.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;
|
||||
}
|
||||
|
||||
/* Info Button Styles */
|
||||
.purpose-card__info-btn {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: var(--color-white);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.purpose-card__info-btn:hover {
|
||||
background: var(--color-secondary-main);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.purpose-card__info-btn:hover .material-icons {
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.purpose-card__info-btn .material-icons {
|
||||
font-size: 20px;
|
||||
color: var(--color-secondary-main);
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.purpose-card__info-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* 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,
|
||||
.purpose-card__info-btn {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Focus state for keyboard navigation */
|
||||
.purpose-radio:focus + .purpose-card__content {
|
||||
outline: 2px solid var(--color-secondary-main);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.purpose-card__info-btn:focus {
|
||||
outline: 2px solid var(--color-secondary-main);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,538 +0,0 @@
|
||||
<!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="뒤로가기" onclick="window.history.back()">
|
||||
<span class="material-icons">arrow_back</span>
|
||||
</button>
|
||||
<h1 class="app-bar__title">AI 트렌드 분석</h1>
|
||||
<button class="app-bar__action" aria-label="홈으로" onclick="window.location.href='21.5-홈.html'">
|
||||
<span class="material-icons">close</span>
|
||||
</button>
|
||||
</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>
|
||||
|
||||
<!-- 5단계 프로세스 시각화 -->
|
||||
<div id="processSteps" style="margin: 32px 0;">
|
||||
<!-- 단계들이 동적으로 추가됩니다 -->
|
||||
</div>
|
||||
|
||||
<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 processSteps = document.getElementById('processSteps');
|
||||
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 분석 단계
|
||||
const analysisSteps = [
|
||||
{ id: 1, progress: 20, text: '업종 데이터 수집 중...', icon: '📊' },
|
||||
{ id: 2, progress: 40, text: '지역 트렌드 분석 중...', icon: '🗺️' },
|
||||
{ id: 3, progress: 60, text: '시즌 패턴 학습 중...', icon: '📅' },
|
||||
{ id: 4, progress: 80, text: '최적 전략 계산 중...', icon: '🧮' },
|
||||
{ id: 5, progress: 100, text: '분석 완료!', icon: '✅' }
|
||||
];
|
||||
|
||||
let currentStep = 0;
|
||||
|
||||
// 프로세스 단계 렌더링
|
||||
function renderProcessSteps() {
|
||||
processSteps.innerHTML = analysisSteps.map((step, index) => {
|
||||
let statusIcon = '⏳';
|
||||
let statusClass = 'step-waiting';
|
||||
let statusText = '대기';
|
||||
|
||||
if (index < currentStep) {
|
||||
statusIcon = '✅';
|
||||
statusClass = 'step-completed';
|
||||
statusText = '완료';
|
||||
} else if (index === currentStep) {
|
||||
statusIcon = '🔄';
|
||||
statusClass = 'step-progress';
|
||||
statusText = '진행중';
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="process-step ${statusClass}" data-step="${step.id}">
|
||||
<div class="process-step__icon">${statusIcon}</div>
|
||||
<div class="process-step__content">
|
||||
<div class="process-step__title">${step.icon} ${step.text.replace(' 중...', '').replace('!', '')}</div>
|
||||
<div class="process-step__status">${statusText}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// 프로그레스 업데이트
|
||||
function updateProgress() {
|
||||
if (currentStep < analysisSteps.length) {
|
||||
const step = analysisSteps[currentStep];
|
||||
|
||||
// 프로그레스 바 업데이트
|
||||
progressFill.style.width = step.progress + '%';
|
||||
progressText.textContent = step.text;
|
||||
|
||||
// 프로세스 단계 시각화 업데이트
|
||||
renderProcessSteps();
|
||||
|
||||
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;
|
||||
progressFill.style.width = '0%';
|
||||
progressText.textContent = '분석 시작 중...';
|
||||
|
||||
// 단계 초기화
|
||||
renderProcessSteps();
|
||||
|
||||
// 분석 재시작
|
||||
setTimeout(updateProgress, 500);
|
||||
};
|
||||
|
||||
// 다음 버튼
|
||||
nextBtn.addEventListener('click', function() {
|
||||
Loading.show('경품 추천 준비 중...');
|
||||
setTimeout(() => {
|
||||
window.location.href = '07-AI경품추천.html';
|
||||
}, 800);
|
||||
});
|
||||
|
||||
// 초기 렌더링
|
||||
renderProcessSteps();
|
||||
|
||||
// 페이지 로드 시 분석 시작
|
||||
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;
|
||||
}
|
||||
|
||||
/* Process Steps */
|
||||
.process-step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 12px;
|
||||
background: var(--color-gray-50);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.process-step__icon {
|
||||
font-size: 24px;
|
||||
min-width: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.process-step__content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.process-step__title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-700);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.process-step__status {
|
||||
font-size: 12px;
|
||||
color: var(--color-gray-500);
|
||||
}
|
||||
|
||||
/* Step States */
|
||||
.step-waiting {
|
||||
background: var(--color-gray-50);
|
||||
}
|
||||
|
||||
.step-waiting .process-step__title {
|
||||
color: var(--color-gray-400);
|
||||
}
|
||||
|
||||
.step-progress {
|
||||
background: linear-gradient(135deg, var(--color-secondary-light) 0%, var(--color-white) 100%);
|
||||
border: 2px solid var(--color-secondary-main);
|
||||
animation: pulse-border 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.step-progress .process-step__title {
|
||||
color: var(--color-secondary-main);
|
||||
}
|
||||
|
||||
.step-progress .process-step__status {
|
||||
color: var(--color-secondary-main);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-completed {
|
||||
background: var(--color-gray-100);
|
||||
}
|
||||
|
||||
.step-completed .process-step__title {
|
||||
color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-border {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 0 0 rgba(0, 102, 255, 0.4);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 0 4px rgba(0, 102, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
#progressText {
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,624 +0,0 @@
|
||||
<!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="history.back()">
|
||||
<span class="material-icons">arrow_back</span>
|
||||
</button>
|
||||
<h1 class="app-bar__title">이벤트 상세 정보</h1>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main id="main-content" class="app-content" style="padding-bottom: 80px;">
|
||||
<div class="container" style="padding-top: 16px;">
|
||||
|
||||
<!-- 헤더 카드 -->
|
||||
<div class="card" style="padding: 24px; margin-bottom: 24px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||
<div style="display: flex; align-items: center; gap: 16px; margin-bottom: 16px;">
|
||||
<div style="width: 72px; height: 72px; background: rgba(255, 255, 255, 0.2); border-radius: 18px; display: flex; align-items: center; justify-content: center;">
|
||||
<span class="material-icons" style="font-size: 42px; color: white;">tag</span>
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<h2 class="h3" style="color: white; margin-bottom: 8px;">SNS 해시태그 이벤트</h2>
|
||||
<div class="body-s" style="opacity: 0.9;">
|
||||
인스타그램 해시태그 참여로 바이럴 효과 극대화
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center; background: rgba(255, 255, 255, 0.25); padding: 8px 16px; border-radius: 16px;">
|
||||
<div style="font-size: 24px; font-weight: 700; line-height: 1;">A</div>
|
||||
<div style="font-size: 11px; opacity: 0.9;">매우높음</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 주요 지표 -->
|
||||
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; padding-top: 16px; border-top: 1px solid rgba(255, 255, 255, 0.2);">
|
||||
<div style="text-align: center;">
|
||||
<div class="body-xs" style="opacity: 0.8; margin-bottom: 4px;">예상 예산</div>
|
||||
<div class="h4" style="color: white; margin: 0;">5만원</div>
|
||||
</div>
|
||||
<div style="text-align: center; border-left: 1px solid rgba(255, 255, 255, 0.2); border-right: 1px solid rgba(255, 255, 255, 0.2);">
|
||||
<div class="body-xs" style="opacity: 0.8; margin-bottom: 4px;">예상 순수익</div>
|
||||
<div class="h4" style="color: white; margin: 0;">+85만원</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div class="body-xs" style="opacity: 0.8; margin-bottom: 4px;">ROI</div>
|
||||
<div class="h4" style="color: white; margin: 0;">1700%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 탭 메뉴 -->
|
||||
<div class="tab-menu" style="display: flex; gap: 8px; margin-bottom: 24px; border-bottom: 1px solid var(--color-gray-200); overflow-x: auto;">
|
||||
<button class="tab-item active" data-tab="overview" onclick="switchTab('overview')">
|
||||
<span class="material-icons" style="font-size: 18px; margin-right: 4px;">info</span>
|
||||
개요
|
||||
</button>
|
||||
<button class="tab-item" data-tab="guide" onclick="switchTab('guide')">
|
||||
<span class="material-icons" style="font-size: 18px; margin-right: 4px;">menu_book</span>
|
||||
실행 가이드
|
||||
</button>
|
||||
<button class="tab-item" data-tab="cost" onclick="switchTab('cost')">
|
||||
<span class="material-icons" style="font-size: 18px; margin-right: 4px;">payments</span>
|
||||
비용 구조
|
||||
</button>
|
||||
<button class="tab-item" data-tab="success" onclick="switchTab('success')">
|
||||
<span class="material-icons" style="font-size: 18px; margin-right: 4px;">stars</span>
|
||||
성공 사례
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 개요 탭 -->
|
||||
<div class="tab-content active" id="tab-overview">
|
||||
|
||||
<!-- 기대 효과 -->
|
||||
<section class="card" style="padding: 20px; margin-bottom: 16px;">
|
||||
<h3 class="h4" style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-primary-main);">trending_up</span>
|
||||
기대 효과
|
||||
</h3>
|
||||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||||
<div style="display: flex; align-items: flex-start; gap: 12px; padding: 12px; background: var(--color-success-light); border-radius: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 20px;">check_circle</span>
|
||||
<div>
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">SNS 노출 +400~600%</div>
|
||||
<div class="body-s text-muted">해시태그를 통한 유기적 확산으로 브랜드 노출 극대화</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; align-items: flex-start; gap: 12px; padding: 12px; background: var(--color-primary-light); border-radius: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-primary-main); font-size: 20px;">group_add</span>
|
||||
<div>
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">신규 고객 +60~80%</div>
|
||||
<div class="body-s text-muted">SNS를 통한 새로운 고객 유입 및 방문 증가</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; align-items: flex-start; gap: 12px; padding: 12px; background: var(--color-warning-light); border-radius: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-warning); font-size: 20px;">campaign</span>
|
||||
<div>
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">브랜드 인지도 +120%</div>
|
||||
<div class="body-s text-muted">바이럴 효과로 지역 내 인지도 상승</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 적합한 매장 -->
|
||||
<section class="card" style="padding: 20px; margin-bottom: 16px;">
|
||||
<h3 class="h4" style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-primary-main);">store</span>
|
||||
적합한 매장
|
||||
</h3>
|
||||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 18px;">check</span>
|
||||
<span class="body-m">비주얼이 좋은 메뉴를 보유한 매장</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 18px;">check</span>
|
||||
<span class="body-m">20~40대 젊은 층을 타겟으로 하는 매장</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 18px;">check</span>
|
||||
<span class="body-m">카페, 일식당, 양식당 (인스타 특화)</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 18px;">check</span>
|
||||
<span class="body-m">매장 내 포토존 또는 감성적인 공간 보유</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 추천 계절 -->
|
||||
<section class="card" style="padding: 20px; margin-bottom: 16px;">
|
||||
<h3 class="h4" style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-primary-main);">calendar_today</span>
|
||||
추천 계절/시기
|
||||
</h3>
|
||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;">
|
||||
<div style="padding: 16px; border: 2px solid var(--color-primary-main); border-radius: 12px; text-align: center;">
|
||||
<div class="h3" style="margin-bottom: 8px;">🌸 봄</div>
|
||||
<div class="body-s text-muted">벚꽃 시즌 특히 효과적</div>
|
||||
<div class="body-m" style="font-weight: 600; color: var(--color-primary-main); margin-top: 8px;">추천도 ⭐⭐⭐⭐⭐</div>
|
||||
</div>
|
||||
<div style="padding: 16px; border: 2px solid var(--color-primary-main); border-radius: 12px; text-align: center;">
|
||||
<div class="h3" style="margin-bottom: 8px;">❄️ 겨울</div>
|
||||
<div class="body-s text-muted">크리스마스 시즌 효과</div>
|
||||
<div class="body-m" style="font-weight: 600; color: var(--color-primary-main); margin-top: 8px;">추천도 ⭐⭐⭐⭐⭐</div>
|
||||
</div>
|
||||
<div style="padding: 16px; border: 1px solid var(--color-gray-300); border-radius: 12px; text-align: center;">
|
||||
<div class="h3" style="margin-bottom: 8px;">🧊 여름</div>
|
||||
<div class="body-s text-muted">시원한 메뉴와 연계</div>
|
||||
<div class="body-m" style="font-weight: 600; color: var(--color-gray-600); margin-top: 8px;">추천도 ⭐⭐⭐⭐</div>
|
||||
</div>
|
||||
<div style="padding: 16px; border: 1px solid var(--color-gray-300); border-radius: 12px; text-align: center;">
|
||||
<div class="h3" style="margin-bottom: 8px;">🍁 가을</div>
|
||||
<div class="body-s text-muted">단풍 콘셉트 활용</div>
|
||||
<div class="body-m" style="font-weight: 600; color: var(--color-gray-600); margin-top: 8px;">추천도 ⭐⭐⭐⭐</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 실행 가이드 탭 -->
|
||||
<div class="tab-content" id="tab-guide" style="display: none;">
|
||||
|
||||
<!-- 단계별 실행 가이드 -->
|
||||
<section class="card" style="padding: 20px; margin-bottom: 16px;">
|
||||
<h3 class="h4" style="margin-bottom: 20px;">5단계 실행 가이드</h3>
|
||||
|
||||
<div class="timeline">
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-badge">1</div>
|
||||
<div class="timeline-content">
|
||||
<h4 class="body-m" style="font-weight: 600; margin-bottom: 8px;">준비 단계 (1-2일)</h4>
|
||||
<ul style="list-style: none; padding: 0; margin: 0;">
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 이벤트 이미지 제작 (Canva 무료 활용)</li>
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 해시태그 선정 (매장명 + 이벤트명 + 지역명)</li>
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 참여 조건 명확화</li>
|
||||
<li class="body-s text-muted">✓ 경품 준비</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-badge">2</div>
|
||||
<div class="timeline-content">
|
||||
<h4 class="body-m" style="font-weight: 600; margin-bottom: 8px;">게시 단계 (1일)</h4>
|
||||
<ul style="list-style: none; padding: 0; margin: 0;">
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 피드 게시: 이벤트 메인 이미지 + 상세 설명</li>
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 스토리 게시: 24시간 노출용 간단 안내</li>
|
||||
<li class="body-s text-muted">✓ 하이라이트 등록: 이벤트 기간 동안 계속 노출</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-badge">3</div>
|
||||
<div class="timeline-content">
|
||||
<h4 class="body-m" style="font-weight: 600; margin-bottom: 8px;">운영 단계 (이벤트 기간)</h4>
|
||||
<ul style="list-style: none; padding: 0; margin: 0;">
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 매일 오전 9시: 스토리 재업로드</li>
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 댓글 답변: 2시간 이내 응답</li>
|
||||
<li class="body-s text-muted">✓ 참여자 리그램: 하루 3-5건</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-badge">4</div>
|
||||
<div class="timeline-content">
|
||||
<h4 class="body-m" style="font-weight: 600; margin-bottom: 8px;">추첨 단계 (이벤트 종료 후)</h4>
|
||||
<ul style="list-style: none; padding: 0; margin: 0;">
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 참여자 목록 엑셀 정리</li>
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 무작위 추첨 (random.org 활용)</li>
|
||||
<li class="body-s text-muted">✓ 당첨자 DM 발송 + 피드 공지</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-badge">5</div>
|
||||
<div class="timeline-content">
|
||||
<h4 class="body-m" style="font-weight: 600; margin-bottom: 8px;">사후 관리</h4>
|
||||
<ul style="list-style: none; padding: 0; margin: 0;">
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 참여 감사 스토리</li>
|
||||
<li class="body-s text-muted" style="margin-bottom: 6px;">✓ 이벤트 결과 공유</li>
|
||||
<li class="body-s text-muted">✓ 다음 이벤트 예고</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 체크리스트 -->
|
||||
<section class="card" style="padding: 20px; margin-bottom: 16px; background: var(--color-warning-light);">
|
||||
<h3 class="h4" style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
|
||||
<span class="material-icons" style="color: var(--color-warning);">checklist</span>
|
||||
필수 체크리스트
|
||||
</h3>
|
||||
<div style="display: flex; flex-direction: column; gap: 10px;">
|
||||
<label class="form-check" style="background: white; padding: 12px; border-radius: 8px;">
|
||||
<input type="checkbox">
|
||||
<span class="body-m">이벤트 이미지 제작 완료</span>
|
||||
</label>
|
||||
<label class="form-check" style="background: white; padding: 12px; border-radius: 8px;">
|
||||
<input type="checkbox">
|
||||
<span class="body-m">해시태그 3개 이상 선정</span>
|
||||
</label>
|
||||
<label class="form-check" style="background: white; padding: 12px; border-radius: 8px;">
|
||||
<input type="checkbox">
|
||||
<span class="body-m">참여 조건 명확 작성</span>
|
||||
</label>
|
||||
<label class="form-check" style="background: white; padding: 12px; border-radius: 8px;">
|
||||
<input type="checkbox">
|
||||
<span class="body-m">경품 준비 완료</span>
|
||||
</label>
|
||||
<label class="form-check" style="background: white; padding: 12px; border-radius: 8px;">
|
||||
<input type="checkbox">
|
||||
<span class="body-m">추첨 방법 결정</span>
|
||||
</label>
|
||||
<label class="form-check" style="background: white; padding: 12px; border-radius: 8px;">
|
||||
<input type="checkbox">
|
||||
<span class="body-m">개인정보 동의 문구 포함</span>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 비용 구조 탭 -->
|
||||
<div class="tab-content" id="tab-cost" style="display: none;">
|
||||
|
||||
<!-- 비용 상세 -->
|
||||
<section class="card" style="padding: 20px; margin-bottom: 16px;">
|
||||
<h3 class="h4" style="margin-bottom: 20px;">비용 구조 상세</h3>
|
||||
|
||||
<div style="margin-bottom: 24px;">
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 12px;">총 비용: 50,000원</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; padding: 12px; background: var(--color-gray-50); border-radius: 8px;">
|
||||
<div>
|
||||
<div class="body-m">제작비</div>
|
||||
<div class="body-s text-muted">Canva 무료 템플릿 사용</div>
|
||||
</div>
|
||||
<div class="body-m" style="font-weight: 600;">0원</div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; padding: 12px; background: var(--color-gray-50); border-radius: 8px;">
|
||||
<div>
|
||||
<div class="body-m">광고비</div>
|
||||
<div class="body-s text-muted">자체 계정 활용 (무료)</div>
|
||||
</div>
|
||||
<div class="body-m" style="font-weight: 600;">0원</div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; padding: 12px; background: var(--color-primary-light); border-radius: 8px;">
|
||||
<div>
|
||||
<div class="body-m" style="font-weight: 600;">경품비</div>
|
||||
<div class="body-s text-muted">음료 쿠폰 50장 × 1,000원</div>
|
||||
</div>
|
||||
<div class="body-m" style="font-weight: 600; color: var(--color-primary-main);">50,000원</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding: 20px; background: var(--color-success-light); border-radius: 12px;">
|
||||
<h4 class="body-m" style="font-weight: 600; margin-bottom: 12px; color: var(--color-success);">예상 수익 분석</h4>
|
||||
<div style="display: flex; flex-direction: column; gap: 8px;">
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span class="body-s">신규 팔로워</span>
|
||||
<strong class="body-s">100-300명</strong>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span class="body-s">예상 매출 증가</span>
|
||||
<strong class="body-s">500,000-1,500,000원</strong>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span class="body-s">순수익 (원가 30% 제외)</span>
|
||||
<strong class="body-s">300,000-1,300,000원</strong>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; padding-top: 8px; border-top: 2px solid var(--color-success);">
|
||||
<span class="body-m" style="font-weight: 600;">ROI</span>
|
||||
<strong class="body-m" style="font-weight: 700; color: var(--color-success);">600-650%</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 예산별 시뮬레이션 -->
|
||||
<section class="card" style="padding: 20px; margin-bottom: 16px;">
|
||||
<h3 class="h4" style="margin-bottom: 16px;">예산별 시뮬레이션</h3>
|
||||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||||
<div style="padding: 16px; border: 2px solid var(--color-primary-main); border-radius: 12px; background: var(--color-primary-light);">
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 8px;">추천 예산: 50,000원</div>
|
||||
<div class="body-s text-muted" style="margin-bottom: 8px;">경품 50개 제공</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span class="body-s">예상 순수익</span>
|
||||
<strong style="color: var(--color-success);">+850,000원</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 16px; border: 1px solid var(--color-gray-300); border-radius: 12px;">
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 8px;">최소 예산: 30,000원</div>
|
||||
<div class="body-s text-muted" style="margin-bottom: 8px;">경품 30개 제공</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span class="body-s">예상 순수익</span>
|
||||
<strong style="color: var(--color-success);">+510,000원</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 16px; border: 1px solid var(--color-gray-300); border-radius: 12px;">
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 8px;">최대 예산: 100,000원</div>
|
||||
<div class="body-s text-muted" style="margin-bottom: 8px;">경품 100개 제공</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span class="body-s">예상 순수익</span>
|
||||
<strong style="color: var(--color-success);">+1,650,000원</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 성공 사례 탭 -->
|
||||
<div class="tab-content" id="tab-success" style="display: none;">
|
||||
|
||||
<!-- 성공 사례 1 -->
|
||||
<section class="card" style="padding: 20px; margin-bottom: 16px;">
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 16px;">
|
||||
<span class="material-icons" style="color: var(--color-warning);">star</span>
|
||||
<h3 class="h4" style="margin: 0;">수원 소재 카페 A</h3>
|
||||
</div>
|
||||
<div class="body-s text-muted" style="margin-bottom: 16px;">
|
||||
업종: 카페 | 지역: 수원 | 계절: 봄 (벚꽃 시즌)
|
||||
</div>
|
||||
<div style="padding: 16px; background: var(--color-gray-50); border-radius: 8px; margin-bottom: 16px;">
|
||||
<div class="body-m" style="margin-bottom: 12px; font-weight: 600;">이벤트 내용</div>
|
||||
<div class="body-s text-muted" style="line-height: 1.6;">
|
||||
"벚꽃 라떼 인증샷 이벤트" 진행<br>
|
||||
해시태그 #OO카페벚꽃 #수원카페 #벚꽃라떼<br>
|
||||
팔로우 + 게시물 공유 + 해시태그 3개
|
||||
</div>
|
||||
</div>
|
||||
<div class="body-m" style="margin-bottom: 12px; font-weight: 600;">결과</div>
|
||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;">
|
||||
<div style="padding: 12px; background: var(--color-success-light); border-radius: 8px; text-align: center;">
|
||||
<div class="body-s text-muted">투입 예산</div>
|
||||
<div class="h4" style="color: var(--color-success); margin: 4px 0;">5만원</div>
|
||||
</div>
|
||||
<div style="padding: 12px; background: var(--color-primary-light); border-radius: 8px; text-align: center;">
|
||||
<div class="body-s text-muted">참여율</div>
|
||||
<div class="h4" style="color: var(--color-primary-main); margin: 4px 0;">82%</div>
|
||||
</div>
|
||||
<div style="padding: 12px; background: var(--color-warning-light); border-radius: 8px; text-align: center;">
|
||||
<div class="body-s text-muted">SNS 확산</div>
|
||||
<div class="h4" style="color: var(--color-warning); margin: 4px 0;">+350%</div>
|
||||
</div>
|
||||
<div style="padding: 12px; background: var(--color-error-light); border-radius: 8px; text-align: center;">
|
||||
<div class="body-s text-muted">신규고객</div>
|
||||
<div class="h4" style="color: var(--color-error); margin: 4px 0;">+55%</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 성공 사례 2 -->
|
||||
<section class="card" style="padding: 20px; margin-bottom: 16px;">
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 16px;">
|
||||
<span class="material-icons" style="color: var(--color-warning);">star</span>
|
||||
<h3 class="h4" style="margin: 0;">강남 소재 일식당 B</h3>
|
||||
</div>
|
||||
<div class="body-s text-muted" style="margin-bottom: 16px;">
|
||||
업종: 일식당 | 지역: 강남 | 계절: 여름
|
||||
</div>
|
||||
<div style="padding: 16px; background: var(--color-gray-50); border-radius: 8px; margin-bottom: 16px;">
|
||||
<div class="body-m" style="margin-bottom: 12px; font-weight: 600;">이벤트 내용</div>
|
||||
<div class="body-s text-muted" style="line-height: 1.6;">
|
||||
"여름 초밥 플레이팅 인증샷 이벤트"<br>
|
||||
해시태그 #OO초밥 #강남맛집 #여름초밥<br>
|
||||
스토리 공유 시 음료 무료 제공
|
||||
</div>
|
||||
</div>
|
||||
<div class="body-m" style="margin-bottom: 12px; font-weight: 600;">결과</div>
|
||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;">
|
||||
<div style="padding: 12px; background: var(--color-success-light); border-radius: 8px; text-align: center;">
|
||||
<div class="body-s text-muted">투입 예산</div>
|
||||
<div class="h4" style="color: var(--color-success); margin: 4px 0;">7만원</div>
|
||||
</div>
|
||||
<div style="padding: 12px; background: var(--color-primary-light); border-radius: 8px; text-align: center;">
|
||||
<div class="body-s text-muted">참여율</div>
|
||||
<div class="h4" style="color: var(--color-primary-main); margin: 4px 0;">75%</div>
|
||||
</div>
|
||||
<div style="padding: 12px; background: var(--color-warning-light); border-radius: 8px; text-align: center;">
|
||||
<div class="body-s text-muted">SNS 도달</div>
|
||||
<div class="h4" style="color: var(--color-warning); margin: 4px 0;">18,000명</div>
|
||||
</div>
|
||||
<div style="padding: 12px; background: var(--color-error-light); border-radius: 8px; text-align: center;">
|
||||
<div class="body-s text-muted">ROI</div>
|
||||
<div class="h4" style="color: var(--color-error); margin: 4px 0;">1850%</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<div class="bottom-nav" style="padding: 16px; background: white; box-shadow: 0 -2px 8px rgba(0,0,0,0.1); position: fixed; bottom: 0; left: 0; right: 0; z-index: 100;">
|
||||
<button class="btn btn-primary btn-lg btn-block" onclick="proceedToNext()">
|
||||
<span class="material-icons" style="margin-right: 8px;">arrow_forward</span>
|
||||
이 이벤트로 기획안 만들기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="js/common.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// 탭 전환
|
||||
window.switchTab = function(tabName) {
|
||||
// 탭 버튼 활성화
|
||||
document.querySelectorAll('.tab-item').forEach(item => {
|
||||
item.classList.remove('active');
|
||||
});
|
||||
document.querySelector(`.tab-item[data-tab="${tabName}"]`).classList.add('active');
|
||||
|
||||
// 탭 컨텐츠 표시
|
||||
document.querySelectorAll('.tab-content').forEach(content => {
|
||||
content.style.display = 'none';
|
||||
});
|
||||
document.getElementById(`tab-${tabName}`).style.display = 'block';
|
||||
};
|
||||
|
||||
// 다음 단계로 진행
|
||||
window.proceedToNext = function() {
|
||||
Modal.show({
|
||||
title: '🎉 이벤트 유형 확정',
|
||||
body: `
|
||||
<div style="text-align: center;">
|
||||
<div style="width: 80px; height: 80px; background: linear-gradient(135deg, var(--color-success) 0%, var(--color-success-dark) 100%); border-radius: 20px; display: flex; align-items: center; justify-content: center; margin: 0 auto 16px;">
|
||||
<span class="material-icons" style="font-size: 48px; color: white;">check_circle</span>
|
||||
</div>
|
||||
<h3 class="h3" style="margin-bottom: 12px;">SNS 해시태그 이벤트</h3>
|
||||
<p class="body-m text-muted" style="margin-bottom: 20px;">
|
||||
이 이벤트 유형으로 AI가<br>
|
||||
경품, 참여방법, 홍보문구를 자동 생성합니다
|
||||
</p>
|
||||
<div class="card" style="padding: 16px; background: var(--color-gray-50); text-align: left;">
|
||||
<div class="body-s" style="margin-bottom: 8px;">
|
||||
<span class="material-icons" style="font-size: 14px; vertical-align: middle; margin-right: 4px; color: var(--color-primary-main);">auto_awesome</span>
|
||||
다음 단계에서 AI가 자동으로:
|
||||
</div>
|
||||
<ul style="list-style: none; padding-left: 20px; margin: 0;">
|
||||
<li class="body-s text-muted" style="margin-bottom: 4px;">✓ 예산에 맞는 경품 추천</li>
|
||||
<li class="body-s text-muted" style="margin-bottom: 4px;">✓ 참여 방법 상세 설계</li>
|
||||
<li class="body-s text-muted" style="margin-bottom: 4px;">✓ SNS 홍보 문구 생성</li>
|
||||
<li class="body-s text-muted">✓ 해시태그 추천</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '경품 추천 받기',
|
||||
cancelText: '이벤트 다시 선택',
|
||||
onConfirm: function() {
|
||||
Loading.show('AI가 경품을 추천하고 있습니다...');
|
||||
setTimeout(() => {
|
||||
Loading.hide();
|
||||
window.location.href = '05-AI경품추천.html';
|
||||
}, 2000);
|
||||
},
|
||||
onCancel: function() {
|
||||
window.location.href = '04-1-AI이벤트유형추천.html';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
console.log('이벤트 상세 정보 페이지 로드 완료');
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.tab-item {
|
||||
padding: 12px 16px;
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-gray-600);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tab-item:hover {
|
||||
color: var(--color-primary-main);
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: var(--color-primary-main);
|
||||
border-bottom-color: var(--color-primary-main);
|
||||
}
|
||||
|
||||
.timeline {
|
||||
position: relative;
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
.timeline::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 8px;
|
||||
bottom: 8px;
|
||||
width: 2px;
|
||||
background: var(--color-gray-300);
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.timeline-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.timeline-badge {
|
||||
position: absolute;
|
||||
left: -40px;
|
||||
top: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: var(--color-primary-main);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
padding: 12px;
|
||||
background: var(--color-gray-50);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.bottom-nav {
|
||||
animation: slideUp 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,696 +0,0 @@
|
||||
<!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="뒤로가기" onclick="history.back()">
|
||||
<span class="material-icons">arrow_back</span>
|
||||
</button>
|
||||
<h1 class="app-bar__title">AI 이벤트 유형 추천</h1>
|
||||
<button class="app-bar__action" aria-label="필터" onclick="showFilterModal()">
|
||||
<span class="material-icons">tune</span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main id="main-content" class="app-content">
|
||||
<div class="container" style="padding-top: 16px;">
|
||||
<!-- Progress Section -->
|
||||
<div style="margin-bottom: 24px;">
|
||||
<div class="body-m" style="margin-bottom: 8px; font-weight: 600;">단계 2/6: AI 이벤트 유형 추천</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 분석 결과 헤더 -->
|
||||
<div class="card" style="padding: 20px; margin-bottom: 24px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||
<div style="display: flex; align-items: flex-start; gap: 16px;">
|
||||
<span class="material-icons" style="font-size: 48px;">auto_awesome</span>
|
||||
<div style="flex: 1;">
|
||||
<h2 class="h3" style="color: white; margin-bottom: 8px;">수원왕갈비님을 위한 맞춤 추천</h2>
|
||||
<div class="body-m" style="opacity: 0.95; margin-bottom: 12px;">
|
||||
한식당 · 봄 시즌 · 예산 30만원
|
||||
</div>
|
||||
<div class="body-s" style="opacity: 0.85;">
|
||||
<span class="material-icons" style="font-size: 16px; vertical-align: middle;">lightbulb</span>
|
||||
13가지 이벤트 유형 중 <strong>7가지</strong>가 추천되었습니다
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 태그 필터 칩 -->
|
||||
<div style="margin-bottom: 16px;">
|
||||
<div class="body-s text-muted" style="margin-bottom: 8px;">태그로 필터링</div>
|
||||
<div id="tagFilter" style="display: flex; flex-wrap: wrap; gap: 8px;">
|
||||
<!-- 태그 칩들이 동적으로 추가됩니다 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 정렬 및 보기 옵션 -->
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
|
||||
<div class="body-m text-muted">
|
||||
총 <strong id="totalCount" style="color: var(--color-primary-main);">7개</strong> 이벤트
|
||||
</div>
|
||||
<select id="sortSelect" class="form-input" style="width: auto; padding: 8px 32px 8px 12px;">
|
||||
<option value="recommendation">추천순</option>
|
||||
<option value="costEfficiency">가성비 높은 순</option>
|
||||
<option value="difficulty">난이도 낮은 순</option>
|
||||
<option value="budget">예산 낮은 순</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 이벤트 카드 리스트 -->
|
||||
<div id="eventList" style="display: flex; flex-direction: column; gap: 16px; margin-bottom: 80px;">
|
||||
<!-- 이벤트 카드들이 동적으로 추가됩니다 -->
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<div class="bottom-nav" style="padding: 16px; background: white; box-shadow: 0 -2px 8px rgba(0,0,0,0.1); position: fixed; bottom: 0; left: 0; right: 0; z-index: 100;">
|
||||
<button class="btn btn-primary btn-lg btn-block" onclick="proceedToNext()" id="proceedBtn" disabled>
|
||||
<span class="material-icons" style="margin-right: 8px;">check_circle</span>
|
||||
선택한 이벤트로 계속하기
|
||||
</button>
|
||||
<div class="body-s text-muted" style="text-align: center; margin-top: 8px;" id="selectionInfo">
|
||||
이벤트를 선택해주세요
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="js/common.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// 이벤트 유형 데이터 (design/이벤트설계.md 기반)
|
||||
const eventTypes = [
|
||||
{
|
||||
id: 'sns-hashtag',
|
||||
name: 'SNS 해시태그 이벤트',
|
||||
category: 'digital',
|
||||
icon: 'tag',
|
||||
budget: { min: 50000, max: 100000 },
|
||||
costEfficiency: 17.0,
|
||||
difficulty: 1,
|
||||
recommendationScore: 8.3,
|
||||
estimatedProfit: 850000,
|
||||
estimatedROI: 1700,
|
||||
description: '인스타그램 해시태그 참여로 바이럴 효과 극대화',
|
||||
effects: ['SNS 노출 +400%', '신규 고객 +70%', '브랜드 인지도 +120%'],
|
||||
tags: ['디지털', 'SNS', '바이럴', '저예산'],
|
||||
duration: 14
|
||||
},
|
||||
{
|
||||
id: 'delivery-review',
|
||||
name: '배달앱 리뷰 이벤트',
|
||||
category: 'digital',
|
||||
icon: 'rate_review',
|
||||
budget: { min: 200000, max: 300000 },
|
||||
costEfficiency: 7.4,
|
||||
difficulty: 2,
|
||||
recommendationScore: 8.7,
|
||||
estimatedProfit: 1850000,
|
||||
estimatedROI: 740,
|
||||
description: '배달앱 리뷰 작성 시 혜택 제공으로 온라인 평판 향상',
|
||||
effects: ['평점 상승 +0.8점', '리뷰 수 +100건', '재방문율 +55%'],
|
||||
tags: ['디지털', '배달', '리뷰', '신뢰도'],
|
||||
duration: 30
|
||||
},
|
||||
{
|
||||
id: 'business-card-coupon',
|
||||
name: '명함형 쿠폰 배포',
|
||||
category: 'offline',
|
||||
icon: 'contact_mail',
|
||||
budget: { min: 60000, max: 100000 },
|
||||
costEfficiency: 25.9,
|
||||
difficulty: 1,
|
||||
recommendationScore: 8.9,
|
||||
estimatedProfit: 1685000,
|
||||
estimatedROI: 2592,
|
||||
description: '주변 상가/오피스 대상 명함 쿠폰 배포',
|
||||
effects: ['쿠폰 사용률 15%', '신규 고객 +150명', '재방문율 +60%'],
|
||||
tags: ['오프라인', '쿠폰', '지역마케팅', '최고가성비'],
|
||||
duration: 30
|
||||
},
|
||||
{
|
||||
id: 'table-tent',
|
||||
name: '테이블 텐트 카드',
|
||||
category: 'offline',
|
||||
icon: 'view_in_ar',
|
||||
budget: { min: 20000, max: 30000 },
|
||||
costEfficiency: 25.25,
|
||||
difficulty: 1,
|
||||
recommendationScore: 8.1,
|
||||
estimatedProfit: 505000,
|
||||
estimatedROI: 2525,
|
||||
description: '테이블 위 안내카드로 추가 주문 유도',
|
||||
effects: ['추가 주문률 +30%', '세트 메뉴 판매 +50%', '객단가 +5,000원'],
|
||||
tags: ['오프라인', '저예산', '추가주문', '최고가성비'],
|
||||
duration: 180
|
||||
},
|
||||
{
|
||||
id: 'pop-ad',
|
||||
name: '매장 내 POP 광고',
|
||||
category: 'offline',
|
||||
icon: 'storefront',
|
||||
budget: { min: 20000, max: 50000 },
|
||||
costEfficiency: 15.5,
|
||||
difficulty: 1,
|
||||
recommendationScore: 7.8,
|
||||
estimatedProfit: 475000,
|
||||
estimatedROI: 1900,
|
||||
description: '즉석 할인/1+1 등 매장 내 프로모션',
|
||||
effects: ['매장 방문 인지 80%', '이벤트 참여율 40%', '추가 매출 +40만원'],
|
||||
tags: ['오프라인', '즉석할인', '1+1', '저예산'],
|
||||
duration: 14
|
||||
},
|
||||
{
|
||||
id: 'spring-menu',
|
||||
name: '봄 시즌 특선 메뉴',
|
||||
category: 'prize',
|
||||
icon: 'local_florist',
|
||||
budget: { min: 100000, max: 300000 },
|
||||
costEfficiency: 6.8,
|
||||
difficulty: 3,
|
||||
recommendationScore: 8.5,
|
||||
estimatedProfit: 950000,
|
||||
estimatedROI: 475,
|
||||
description: '봄나물/제철 식재료 활용 한정 메뉴 출시',
|
||||
effects: ['신메뉴 매출 +35%', 'SNS 화제성 +150%', '재방문 유도 +50%'],
|
||||
tags: ['시즌메뉴', '봄', '한정판', '차별화'],
|
||||
duration: 60
|
||||
},
|
||||
{
|
||||
id: 'stamp',
|
||||
name: '스탬프 적립 이벤트',
|
||||
category: 'prize',
|
||||
icon: 'card_giftcard',
|
||||
budget: { min: 50000, max: 200000 },
|
||||
costEfficiency: 8.9,
|
||||
difficulty: 2,
|
||||
recommendationScore: 7.5,
|
||||
estimatedProfit: 780000,
|
||||
estimatedROI: 520,
|
||||
description: '방문 횟수별 스탬프 적립 후 보상 제공',
|
||||
effects: ['재방문율 +90%', '고객 충성도 +95%', '장기 매출 안정화'],
|
||||
tags: ['재방문', '충성도', '장기전략'],
|
||||
duration: 90
|
||||
}
|
||||
];
|
||||
|
||||
let selectedEvent = null;
|
||||
let selectedTags = new Set();
|
||||
|
||||
// 모든 고유 태그 추출
|
||||
function getAllTags() {
|
||||
const tagsSet = new Set();
|
||||
eventTypes.forEach(event => {
|
||||
event.tags.forEach(tag => tagsSet.add(tag));
|
||||
});
|
||||
return Array.from(tagsSet).sort();
|
||||
}
|
||||
|
||||
// 태그 필터 칩 렌더링
|
||||
function renderTagFilter() {
|
||||
const tagFilterContainer = document.getElementById('tagFilter');
|
||||
const allTags = getAllTags();
|
||||
|
||||
tagFilterContainer.innerHTML = allTags.map(tag => {
|
||||
const isSelected = selectedTags.has(tag);
|
||||
return `
|
||||
<button class="tag-chip ${isSelected ? 'tag-chip--selected' : ''}"
|
||||
onclick="toggleTag('${tag}')"
|
||||
aria-pressed="${isSelected}">
|
||||
${tag}
|
||||
${isSelected ? '<span class="material-icons" style="font-size: 14px; margin-left: 4px;">close</span>' : ''}
|
||||
</button>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// 태그 토글
|
||||
window.toggleTag = function(tag) {
|
||||
if (selectedTags.has(tag)) {
|
||||
selectedTags.delete(tag);
|
||||
} else {
|
||||
selectedTags.add(tag);
|
||||
}
|
||||
renderTagFilter();
|
||||
renderEventCards(getFilteredAndSortedEvents());
|
||||
updateTotalCount();
|
||||
};
|
||||
|
||||
// 필터링 및 정렬된 이벤트 가져오기
|
||||
function getFilteredAndSortedEvents() {
|
||||
let filtered = [...eventTypes];
|
||||
|
||||
// 태그 필터 적용
|
||||
if (selectedTags.size > 0) {
|
||||
filtered = filtered.filter(event =>
|
||||
event.tags.some(tag => selectedTags.has(tag))
|
||||
);
|
||||
}
|
||||
|
||||
// 정렬 적용
|
||||
const sortBy = document.getElementById('sortSelect').value;
|
||||
switch(sortBy) {
|
||||
case 'recommendation':
|
||||
filtered.sort((a, b) => b.recommendationScore - a.recommendationScore);
|
||||
break;
|
||||
case 'costEfficiency':
|
||||
filtered.sort((a, b) => b.costEfficiency - a.costEfficiency);
|
||||
break;
|
||||
case 'difficulty':
|
||||
filtered.sort((a, b) => a.difficulty - b.difficulty);
|
||||
break;
|
||||
case 'budget':
|
||||
filtered.sort((a, b) => a.budget.min - b.budget.min);
|
||||
break;
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
// 총 개수 업데이트
|
||||
function updateTotalCount() {
|
||||
const totalCount = document.getElementById('totalCount');
|
||||
const filtered = getFilteredAndSortedEvents();
|
||||
totalCount.textContent = `${filtered.length}개`;
|
||||
}
|
||||
|
||||
// 가성비 등급 계산
|
||||
function getCostEfficiencyGrade(score) {
|
||||
if (score >= 20) return { grade: 'S', color: '#9333ea', label: '최고' };
|
||||
if (score >= 15) return { grade: 'A', color: '#3b82f6', label: '매우높음' };
|
||||
if (score >= 10) return { grade: 'B', color: '#10b981', label: '높음' };
|
||||
if (score >= 5) return { grade: 'C', color: '#f59e0b', label: '보통' };
|
||||
return { grade: 'D', color: '#ef4444', label: '낮음' };
|
||||
}
|
||||
|
||||
// 난이도 표시
|
||||
function getDifficultyStars(difficulty) {
|
||||
const stars = '⭐'.repeat(difficulty);
|
||||
const labels = ['쉬움', '보통', '어려움'];
|
||||
return `${stars} ${labels[difficulty - 1]}`;
|
||||
}
|
||||
|
||||
// 이벤트 카드 렌더링
|
||||
function renderEventCards(events) {
|
||||
const listContainer = document.getElementById('eventList');
|
||||
|
||||
if (events.length === 0) {
|
||||
listContainer.innerHTML = `
|
||||
<div style="text-align: center; padding: 60px 20px;">
|
||||
<span class="material-icons" style="font-size: 64px; color: var(--color-gray-400); margin-bottom: 16px;">filter_list_off</span>
|
||||
<div class="h4" style="margin-bottom: 8px;">해당하는 이벤트가 없습니다</div>
|
||||
<div class="body-m text-muted">다른 필터를 선택해주세요</div>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
listContainer.innerHTML = events.map(event => {
|
||||
const ceGrade = getCostEfficiencyGrade(event.costEfficiency);
|
||||
const isSelected = selectedEvent && selectedEvent.id === event.id;
|
||||
|
||||
return `
|
||||
<div class="card event-card ${isSelected ? 'selected' : ''}"
|
||||
style="padding: 20px; cursor: pointer; transition: all 0.3s ease; ${isSelected ? 'border: 2px solid var(--color-primary-main); box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);' : 'border: 1px solid var(--color-gray-200);'}"
|
||||
onclick="selectEvent('${event.id}')">
|
||||
|
||||
<!-- 상단: 아이콘 + 제목 + 가성비 등급 -->
|
||||
<div style="display: flex; align-items: flex-start; gap: 16px; margin-bottom: 16px;">
|
||||
<div style="width: 64px; height: 64px; background: linear-gradient(135deg, ${ceGrade.color}15 0%, ${ceGrade.color}30 100%); border-radius: 16px; display: flex; align-items: center; justify-content: center; flex-shrink: 0;">
|
||||
<span class="material-icons" style="font-size: 36px; color: ${ceGrade.color};">${event.icon}</span>
|
||||
</div>
|
||||
|
||||
<div style="flex: 1;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 8px;">
|
||||
<h3 class="h4" style="margin: 0;">${event.name}</h3>
|
||||
<div class="cost-efficiency-badge" style="background: ${ceGrade.color};">
|
||||
<div class="cost-efficiency-badge__grade">${ceGrade.grade}</div>
|
||||
<div class="cost-efficiency-badge__label">${ceGrade.label}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="body-s text-muted" style="margin-bottom: 12px;">
|
||||
${event.description}
|
||||
</div>
|
||||
|
||||
<!-- 태그 -->
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px;">
|
||||
${event.tags.map(tag => {
|
||||
const isTagSelected = selectedTags.has(tag);
|
||||
return `
|
||||
<span class="badge" style="background: ${isTagSelected ? 'var(--color-secondary-main)' : 'var(--color-gray-100)'}; color: ${isTagSelected ? 'white' : 'var(--color-gray-700)'}; font-size: 11px; padding: 3px 8px;">
|
||||
${tag}
|
||||
</span>
|
||||
`;
|
||||
}).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 중단: 주요 지표 -->
|
||||
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; padding: 16px; background: var(--color-gray-50); border-radius: 12px; margin-bottom: 16px;">
|
||||
<div style="text-align: center;">
|
||||
<div class="body-xs text-muted" style="margin-bottom: 4px;">예산</div>
|
||||
<div class="body-m" style="font-weight: 600; color: var(--color-primary-main);">
|
||||
${Utils.formatNumber(event.budget.min)}원
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center; border-left: 1px solid var(--color-gray-200); border-right: 1px solid var(--color-gray-200);">
|
||||
<div class="body-xs text-muted" style="margin-bottom: 4px;">예상 순수익</div>
|
||||
<div class="body-m" style="font-weight: 600; color: var(--color-success);">
|
||||
+${Utils.formatNumber(event.estimatedProfit)}원
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div class="body-xs text-muted" style="margin-bottom: 4px;">ROI</div>
|
||||
<div class="body-m" style="font-weight: 600; color: var(--color-error);">
|
||||
${event.estimatedROI}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 기대 효과 -->
|
||||
<div style="margin-bottom: 16px;">
|
||||
<div class="body-s" style="font-weight: 600; margin-bottom: 8px; color: var(--color-gray-800);">
|
||||
<span class="material-icons" style="font-size: 16px; vertical-align: middle; margin-right: 4px;">trending_up</span>
|
||||
기대 효과
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 6px;">
|
||||
${event.effects.map(effect => `
|
||||
<div class="body-s" style="display: flex; align-items: center; gap: 8px;">
|
||||
<span class="material-icons" style="font-size: 14px; color: var(--color-success);">check_circle</span>
|
||||
<span>${effect}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 하단: 난이도 + 추천점수 + 액션 -->
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="display: flex; flex-direction: column; gap: 4px;">
|
||||
<div class="body-xs text-muted">난이도</div>
|
||||
<div class="body-s">${getDifficultyStars(event.difficulty)}</div>
|
||||
</div>
|
||||
|
||||
<div style="flex: 1; margin: 0 16px;">
|
||||
<div class="body-xs text-muted" style="margin-bottom: 4px;">추천 점수</div>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<div style="flex: 1; height: 8px; background: var(--color-gray-200); border-radius: 4px; overflow: hidden;">
|
||||
<div style="width: ${event.recommendationScore * 10}%; height: 100%; background: linear-gradient(90deg, var(--color-primary-main) 0%, var(--color-primary-dark) 100%); transition: width 0.3s ease;"></div>
|
||||
</div>
|
||||
<span class="body-s" style="font-weight: 600; color: var(--color-primary-main); min-width: 32px;">${event.recommendationScore.toFixed(1)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn ${isSelected ? 'btn-primary' : 'btn-secondary'}"
|
||||
style="min-width: 80px;"
|
||||
onclick="event.stopPropagation(); toggleEventSelection('${event.id}')">
|
||||
${isSelected ? '선택됨' : '선택하기'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// 이벤트 선택
|
||||
window.selectEvent = function(eventId) {
|
||||
const event = eventTypes.find(e => e.id === eventId);
|
||||
if (!event) return;
|
||||
|
||||
selectedEvent = event;
|
||||
renderEventCards(getFilteredAndSortedEvents());
|
||||
updateProceedButton();
|
||||
};
|
||||
|
||||
// 이벤트 선택 토글
|
||||
window.toggleEventSelection = function(eventId) {
|
||||
if (selectedEvent && selectedEvent.id === eventId) {
|
||||
selectedEvent = null;
|
||||
} else {
|
||||
selectEvent(eventId);
|
||||
}
|
||||
};
|
||||
|
||||
// 진행 버튼 업데이트
|
||||
function updateProceedButton() {
|
||||
const proceedBtn = document.getElementById('proceedBtn');
|
||||
const selectionInfo = document.getElementById('selectionInfo');
|
||||
|
||||
if (selectedEvent) {
|
||||
proceedBtn.disabled = false;
|
||||
selectionInfo.innerHTML = `
|
||||
<strong>${selectedEvent.name}</strong> 선택됨 ·
|
||||
예산 ${Utils.formatNumber(selectedEvent.budget.min)}원 ·
|
||||
예상 순수익 +${Utils.formatNumber(selectedEvent.estimatedProfit)}원
|
||||
`;
|
||||
} else {
|
||||
proceedBtn.disabled = true;
|
||||
selectionInfo.textContent = '이벤트를 선택해주세요';
|
||||
}
|
||||
}
|
||||
|
||||
// 정렬 변경
|
||||
document.getElementById('sortSelect').addEventListener('change', function() {
|
||||
renderEventCards(getFilteredAndSortedEvents());
|
||||
});
|
||||
|
||||
// 필터 모달
|
||||
window.showFilterModal = function() {
|
||||
Modal.show({
|
||||
title: '필터 설정',
|
||||
body: `
|
||||
<div class="form-group">
|
||||
<label class="form-label">예산 범위</label>
|
||||
<div style="display: flex; gap: 8px; align-items: center;">
|
||||
<input type="number" id="minBudget" class="form-input" placeholder="최소" value="0" style="flex: 1;">
|
||||
<span>~</span>
|
||||
<input type="number" id="maxBudget" class="form-input" placeholder="최대" value="1000000" style="flex: 1;">
|
||||
<span>원</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">카테고리</label>
|
||||
<div style="display: flex; flex-direction: column; gap: 8px;">
|
||||
<label class="form-check">
|
||||
<input type="checkbox" name="category" value="digital" checked>
|
||||
<span>디지털 이벤트</span>
|
||||
</label>
|
||||
<label class="form-check">
|
||||
<input type="checkbox" name="category" value="offline" checked>
|
||||
<span>오프라인 이벤트</span>
|
||||
</label>
|
||||
<label class="form-check">
|
||||
<input type="checkbox" name="category" value="prize" checked>
|
||||
<span>경품 중심 이벤트</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">난이도</label>
|
||||
<div style="display: flex; flex-direction: column; gap: 8px;">
|
||||
<label class="form-check">
|
||||
<input type="checkbox" name="difficulty" value="1" checked>
|
||||
<span>⭐ 쉬움</span>
|
||||
</label>
|
||||
<label class="form-check">
|
||||
<input type="checkbox" name="difficulty" value="2" checked>
|
||||
<span>⭐⭐ 보통</span>
|
||||
</label>
|
||||
<label class="form-check">
|
||||
<input type="checkbox" name="difficulty" value="3" checked>
|
||||
<span>⭐⭐⭐ 어려움</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '적용',
|
||||
onConfirm: function() {
|
||||
const minBudget = parseInt(document.getElementById('minBudget').value) || 0;
|
||||
const maxBudget = parseInt(document.getElementById('maxBudget').value) || 999999999;
|
||||
const categories = Array.from(document.querySelectorAll('input[name="category"]:checked')).map(cb => cb.value);
|
||||
const difficulties = Array.from(document.querySelectorAll('input[name="difficulty"]:checked')).map(cb => parseInt(cb.value));
|
||||
|
||||
// 필터링 로직 (실제로는 서버에서 처리)
|
||||
Toast.success('필터가 적용되었습니다.');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 다음 단계로 진행
|
||||
window.proceedToNext = function() {
|
||||
if (!selectedEvent) {
|
||||
Toast.error('이벤트를 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 선택한 이벤트 정보 저장 (localStorage에 직접 저장)
|
||||
localStorage.setItem('kt_selected_event_type', JSON.stringify(selectedEvent));
|
||||
window.AppState.save();
|
||||
|
||||
// 상세 정보 화면으로 이동
|
||||
Modal.show({
|
||||
title: '✅ 이벤트 선택 완료',
|
||||
body: `
|
||||
<div style="text-align: center;">
|
||||
<div style="width: 80px; height: 80px; background: linear-gradient(135deg, var(--color-primary-main) 0%, var(--color-primary-dark) 100%); border-radius: 20px; display: flex; align-items: center; justify-content: center; margin: 0 auto 16px;">
|
||||
<span class="material-icons" style="font-size: 48px; color: white;">${selectedEvent.icon}</span>
|
||||
</div>
|
||||
<h3 class="h3" style="margin-bottom: 12px;">${selectedEvent.name}</h3>
|
||||
<p class="body-m text-muted" style="margin-bottom: 20px;">
|
||||
이 이벤트의 상세 정보를 확인하고<br>
|
||||
경품 추천을 받으시겠습니까?
|
||||
</p>
|
||||
<div class="card" style="padding: 16px; background: var(--color-gray-50); text-align: left;">
|
||||
<div class="body-s" style="display: flex; justify-content: space-between; margin-bottom: 8px;">
|
||||
<span class="text-muted">예상 예산</span>
|
||||
<strong>${Utils.formatNumber(selectedEvent.budget.min)}원</strong>
|
||||
</div>
|
||||
<div class="body-s" style="display: flex; justify-content: space-between; margin-bottom: 8px;">
|
||||
<span class="text-muted">예상 순수익</span>
|
||||
<strong style="color: var(--color-success);">+${Utils.formatNumber(selectedEvent.estimatedProfit)}원</strong>
|
||||
</div>
|
||||
<div class="body-s" style="display: flex; justify-content: space-between;">
|
||||
<span class="text-muted">ROI</span>
|
||||
<strong style="color: var(--color-error);">${selectedEvent.estimatedROI}%</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '상세 정보 보기',
|
||||
cancelText: '다시 선택',
|
||||
onConfirm: function() {
|
||||
window.location.href = '06-2-이벤트상세정보.html';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 초기화
|
||||
renderTagFilter();
|
||||
renderEventCards(getFilteredAndSortedEvents());
|
||||
updateTotalCount();
|
||||
console.log('AI 이벤트 유형 추천 페이지 로드 완료');
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.event-card {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.event-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12) !important;
|
||||
}
|
||||
|
||||
.event-card.selected {
|
||||
background: linear-gradient(135deg, rgba(102, 126, 234, 0.05) 0%, rgba(118, 75, 162, 0.05) 100%);
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
border-radius: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bottom-nav {
|
||||
animation: slideUp 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Tag Chip Styles */
|
||||
.tag-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--color-gray-300);
|
||||
border-radius: 16px;
|
||||
background: var(--color-white);
|
||||
color: var(--color-gray-700);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.tag-chip:hover {
|
||||
border-color: var(--color-secondary-main);
|
||||
background: var(--color-secondary-light);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.tag-chip--selected {
|
||||
border-color: var(--color-secondary-main);
|
||||
background: var(--color-secondary-main);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tag-chip--selected:hover {
|
||||
background: var(--color-secondary-dark);
|
||||
}
|
||||
|
||||
/* Cost Efficiency Badge */
|
||||
.cost-efficiency-badge {
|
||||
text-align: center;
|
||||
color: white;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
margin-left: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cost-efficiency-badge__grade {
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.cost-efficiency-badge__label {
|
||||
font-size: 10px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,644 +0,0 @@
|
||||
<!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="뒤로가기"
|
||||
onclick="window.history.back()"
|
||||
>
|
||||
<span class="material-icons">arrow_back</span>
|
||||
</button>
|
||||
<h1 class="app-bar__title">AI 트렌드 분석</h1>
|
||||
<button
|
||||
class="app-bar__action"
|
||||
aria-label="홈으로"
|
||||
onclick="window.location.href='21.5-홈.html'"
|
||||
>
|
||||
<span class="material-icons">close</span>
|
||||
</button>
|
||||
</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>
|
||||
@ -1,686 +0,0 @@
|
||||
<!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="뒤로가기" onclick="window.history.back()">
|
||||
<span class="material-icons">arrow_back</span>
|
||||
</button>
|
||||
<h1 class="app-bar__title">AI 경품 추천</h1>
|
||||
<button class="app-bar__action" aria-label="홈으로" onclick="window.location.href='21.5-홈.html'">
|
||||
<span class="material-icons">close</span>
|
||||
</button>
|
||||
</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>
|
||||
|
||||
<!-- Selected Event Type Context -->
|
||||
<div id="selectedEventContext" style="display: none; margin-bottom: 24px;">
|
||||
<div class="card" style="padding: 16px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 8px;">
|
||||
<span class="material-icons" style="font-size: 24px;">campaign</span>
|
||||
<div class="body-l" style="font-weight: 700;" id="eventTypeName">SNS 해시태그 이벤트</div>
|
||||
</div>
|
||||
<div class="body-s" style="opacity: 0.95; margin-bottom: 12px;" id="eventTypeDesc">
|
||||
인스타그램 해시태그 참여로 바이럴 효과 극대화
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
|
||||
<span class="chip" style="background: rgba(255,255,255,0.2); color: white; padding: 4px 12px; border-radius: 12px; font-size: 12px;" id="eventCategory">디지털</span>
|
||||
<span class="chip" style="background: rgba(255,255,255,0.2); color: white; padding: 4px 12px; border-radius: 12px; font-size: 12px;" id="eventBudget">예산: 5~10만원</span>
|
||||
</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;
|
||||
|
||||
// selectedEventType은 localStorage에서 직접 로드
|
||||
let selectedEventType = null;
|
||||
try {
|
||||
selectedEventType = JSON.parse(localStorage.getItem('kt_selected_event_type') || 'null');
|
||||
} catch (e) {
|
||||
console.error('Failed to load selected event type:', e);
|
||||
}
|
||||
|
||||
// 매장 정보가 없으면 이전 화면으로
|
||||
if (!store) {
|
||||
Toast.error('매장 정보를 먼저 등록해주세요.');
|
||||
setTimeout(() => {
|
||||
window.location.href = '02-매장정보등록.html';
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
// 이벤트 정보가 없으면 이전 화면으로
|
||||
if (!event || !selectedEventType) {
|
||||
Toast.error('이벤트 유형을 먼저 선택해주세요.');
|
||||
setTimeout(() => {
|
||||
window.location.href = '06-AI이벤트유형추천.html';
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
// 선택된 이벤트 유형 정보 표시
|
||||
if (selectedEventType) {
|
||||
document.getElementById('selectedEventContext').style.display = 'block';
|
||||
document.getElementById('eventTypeName').textContent = selectedEventType.name;
|
||||
document.getElementById('eventTypeDesc').textContent = selectedEventType.description;
|
||||
|
||||
const categoryMap = {
|
||||
'digital': '디지털',
|
||||
'offline': '오프라인',
|
||||
'prize': '경품형'
|
||||
};
|
||||
document.getElementById('eventCategory').textContent = categoryMap[selectedEventType.category] || selectedEventType.category;
|
||||
|
||||
const budgetMin = Math.floor(selectedEventType.budget.min / 10000);
|
||||
const budgetMax = Math.floor(selectedEventType.budget.max / 10000);
|
||||
document.getElementById('eventBudget').textContent = `예산: ${budgetMin}~${budgetMax}만원`;
|
||||
|
||||
// 이벤트 유형 예산 범위로 슬라이더 초기값 설정
|
||||
const avgBudget = (selectedEventType.budget.min + selectedEventType.budget.max) / 2;
|
||||
currentBudget = Math.round(avgBudget / 10000) * 10000;
|
||||
budgetSlider.value = currentBudget;
|
||||
budgetDisplay.textContent = Utils.formatNumber(currentBudget) + '원';
|
||||
}
|
||||
|
||||
// 예산 슬라이더 변경
|
||||
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 eventCategory = selectedEventType ? selectedEventType.category : null;
|
||||
|
||||
// 이벤트 유형별 경품 데이터 (디지털/오프라인/경품형)
|
||||
const eventTypePrizes = {
|
||||
digital: [
|
||||
{ name: '모바일 기프티콘', basePrice: 5000, attraction: 5, participationRate: 48, category: 'voucher', digital: true },
|
||||
{ name: 'SNS 공유 쿠폰', basePrice: 3000, attraction: 4, participationRate: 52, category: 'discount', digital: true },
|
||||
{ name: '온라인 포인트 적립', basePrice: 2000, attraction: 3, participationRate: 45, category: 'point', digital: true },
|
||||
{ name: '디지털 할인쿠폰', basePrice: 5000, attraction: 4, participationRate: 50, category: 'discount', digital: true },
|
||||
{ name: '모바일 상품권', basePrice: 10000, attraction: 5, participationRate: 46, category: 'voucher', digital: true }
|
||||
],
|
||||
offline: [
|
||||
{ name: '매장 방문 쿠폰', basePrice: 8000, attraction: 5, participationRate: 42, category: 'discount', digital: false },
|
||||
{ name: '현장 사은품', basePrice: 5000, attraction: 4, participationRate: 38, category: 'product', digital: false },
|
||||
{ name: '체험 상품권', basePrice: 15000, attraction: 5, participationRate: 35, category: 'service', digital: false },
|
||||
{ name: '무료 시음/시식권', basePrice: 3000, attraction: 3, participationRate: 40, category: 'product', digital: false },
|
||||
{ name: 'VIP 라운지 이용권', basePrice: 20000, attraction: 4, participationRate: 28, category: 'service', digital: false }
|
||||
],
|
||||
prize: [
|
||||
{ name: '추첨 경품권', basePrice: 50000, attraction: 5, participationRate: 55, category: 'raffle', digital: false },
|
||||
{ name: '럭키드로우 티켓', basePrice: 0, attraction: 4, participationRate: 50, category: 'raffle', digital: true },
|
||||
{ name: '즉석 당첨 쿠폰', basePrice: 10000, attraction: 5, participationRate: 48, category: 'instant', digital: false },
|
||||
{ name: '응모권 (자동추첨)', basePrice: 0, attraction: 3, participationRate: 45, category: 'raffle', digital: true },
|
||||
{ name: '럭키박스 교환권', basePrice: 30000, attraction: 4, participationRate: 40, category: 'product', digital: false }
|
||||
]
|
||||
};
|
||||
|
||||
// 업종별 경품 데이터
|
||||
const businessPrizes = {
|
||||
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 멤버십 포인트', basePrice: 5000, attraction: 3, participationRate: 32, category: 'point' },
|
||||
{ name: '모바일 상품권', basePrice: 5000, attraction: 4, participationRate: 38, category: 'voucher' },
|
||||
{ name: '할인쿠폰', basePrice: 3000, attraction: 4, participationRate: 36, category: 'discount' },
|
||||
{ name: '경품 추첨권', basePrice: 0, attraction: 2, participationRate: 25, category: 'raffle' },
|
||||
{ name: '재방문 할인권', basePrice: 0, attraction: 3, participationRate: 30, category: 'discount' }
|
||||
];
|
||||
|
||||
// 이벤트 유형과 업종을 조합하여 최적의 경품 선택
|
||||
let prizes = [];
|
||||
|
||||
if (eventCategory && eventTypePrizes[eventCategory]) {
|
||||
// 이벤트 유형별 경품 우선
|
||||
prizes = [...eventTypePrizes[eventCategory]];
|
||||
|
||||
// 디지털 이벤트인 경우 업종별 경품 중 디지털 가능한 것 추가
|
||||
if (eventCategory === 'digital' && businessPrizes[businessType]) {
|
||||
const digitalCompatible = businessPrizes[businessType]
|
||||
.filter(p => p.category === 'discount' || p.category === 'voucher' || p.category === 'point')
|
||||
.slice(0, 2);
|
||||
prizes = [...prizes, ...digitalCompatible];
|
||||
}
|
||||
} else {
|
||||
// 이벤트 유형이 없으면 업종별 경품 사용
|
||||
prizes = businessPrizes[businessType] || defaultPrizes;
|
||||
}
|
||||
|
||||
// 중복 제거 및 최대 8개로 제한
|
||||
prizes = prizes.slice(0, 8);
|
||||
|
||||
// 예산에 맞춰 가격 조정 및 추천
|
||||
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 style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px;">
|
||||
<span class="body-m" style="font-weight: 700;">
|
||||
${prize.rank}. ${prize.name}
|
||||
</span>
|
||||
${prize.digital !== undefined ? `
|
||||
<span style="background: ${prize.digital ? '#667eea' : '#10b981'}; color: white; font-size: 10px; padding: 2px 6px; border-radius: 4px; font-weight: 600;">
|
||||
${prize.digital ? '디지털' : '오프라인'}
|
||||
</span>
|
||||
` : ''}
|
||||
</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>
|
||||
@ -1,510 +0,0 @@
|
||||
<!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="뒤로가기" onclick="window.history.back()">
|
||||
<span class="material-icons">arrow_back</span>
|
||||
</button>
|
||||
<h1 class="app-bar__title">AI 참여방법 설계</h1>
|
||||
<button class="app-bar__action" aria-label="홈으로" onclick="window.location.href='21.5-홈.html'">
|
||||
<span class="material-icons">close</span>
|
||||
</button>
|
||||
</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>
|
||||
@ -1,504 +0,0 @@
|
||||
<!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="뒤로가기" onclick="window.history.back()">
|
||||
<span class="material-icons">arrow_back</span>
|
||||
</button>
|
||||
<h1 class="app-bar__title">AI 홍보문구 생성</h1>
|
||||
<button class="app-bar__action" aria-label="홈으로" onclick="window.location.href='21.5-홈.html'">
|
||||
<span class="material-icons">close</span>
|
||||
</button>
|
||||
</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>
|
||||
@ -1,648 +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 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>
|
||||
@ -1,619 +0,0 @@
|
||||
<!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>
|
||||
<button type="button"
|
||||
class="btn-icon"
|
||||
onclick="window.location.href='21.5-홈.html'"
|
||||
aria-label="홈으로">
|
||||
<span class="material-icons">close</span>
|
||||
</button>
|
||||
</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>
|
||||
@ -1,390 +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 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">AI 이미지 생성</h1>
|
||||
<div style="width: 40px;"></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="progress-indicator" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100">
|
||||
<div class="progress-text">콘텐츠 1/5: 이미지</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: 20%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main id="main-content" class="container" role="main">
|
||||
<section class="section" aria-labelledby="brand-settings-title">
|
||||
<h2 id="brand-settings-title" class="h3">브랜드 설정</h2>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label for="brandColor" class="form-label">브랜드 컬러</label>
|
||||
<div style="display: flex; gap: var(--spacing-s); align-items: center;">
|
||||
<input type="color" id="brandColor" value="#E31E24" style="width: 60px; height: 48px; border: 1px solid var(--color-gray-300); border-radius: var(--radius-sm); cursor: pointer;">
|
||||
<input type="text" id="brandColorText" class="input-field" value="#E31E24" placeholder="#RRGGBB" style="flex: 1;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="logoUpload" class="form-label">로고 업로드 (선택)</label>
|
||||
<div class="upload-area" id="uploadArea">
|
||||
<input type="file" id="logoUpload" accept="image/*" style="display: none;" aria-label="로고 이미지 업로드">
|
||||
<div class="upload-placeholder" id="uploadPlaceholder">
|
||||
<span class="material-icons" style="font-size: 48px; color: var(--color-gray-400);">add_photo_alternate</span>
|
||||
<div class="body-m" style="color: var(--color-gray-600); margin-top: var(--spacing-s);">이미지 선택</div>
|
||||
<div class="caption" style="color: var(--color-gray-500); margin-top: var(--spacing-xs);">PNG, JPG (최대 5MB)</div>
|
||||
</div>
|
||||
<div class="upload-preview" id="uploadPreview" style="display: none;">
|
||||
<img id="previewImage" src="" alt="업로드된 로고 미리보기" style="max-width: 100%; max-height: 200px; border-radius: var(--radius-md);">
|
||||
<button type="button" class="btn btn-text btn-sm" onclick="removeImage()" style="margin-top: var(--spacing-s);">
|
||||
<span class="material-icons">delete</span>
|
||||
<span>삭제</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-primary btn-lg" onclick="startGeneration()" style="width: 100%; margin-top: var(--spacing-m);">
|
||||
<span class="material-icons">auto_awesome</span>
|
||||
<span>AI 이미지 생성 시작</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="generationProgress" style="display: none;" aria-labelledby="progress-title">
|
||||
<h2 id="progress-title" class="h3">🤖 AI가 이미지 생성중...</h2>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="text-align: center;">
|
||||
<div class="body-m" style="color: var(--color-gray-600); margin-bottom: var(--spacing-m);">
|
||||
브랜드 컬러와 로고를 반영하여<br>3가지 스타일의 이미지를 생성합니다
|
||||
</div>
|
||||
|
||||
<div class="progress-bar-container" style="height: 12px; margin-bottom: var(--spacing-s);">
|
||||
<div class="progress-bar-fill" id="aiProgressBar" style="width: 0%; background: var(--color-primary-main);"></div>
|
||||
</div>
|
||||
|
||||
<div class="body-s" style="color: var(--color-gray-600);" id="progressText">
|
||||
진행률: 0%
|
||||
</div>
|
||||
|
||||
<div class="caption" style="color: var(--color-gray-500); margin-top: var(--spacing-xs);" id="timeEstimate">
|
||||
예상 소요: 2분 30초
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="generationResult" style="display: none;" aria-labelledby="result-title">
|
||||
<h2 id="result-title" class="h3">생성된 이미지 (3종)</h2>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="display: flex; flex-direction: column; gap: var(--spacing-m);">
|
||||
<div class="image-option" onclick="selectImage(0)">
|
||||
<div class="image-preview" style="aspect-ratio: 1/1; background: linear-gradient(135deg, #E31E24 0%, #FF6B6B 100%); border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center; position: relative;">
|
||||
<div style="text-align: center; color: white;">
|
||||
<span class="material-icons" style="font-size: 48px;">palette</span>
|
||||
<div class="body-l" style="font-weight: 600; margin-top: var(--spacing-s);">심플 스타일</div>
|
||||
</div>
|
||||
<div class="selection-badge" id="badge0" style="display: none;">
|
||||
<span class="material-icons">check_circle</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; margin-top: var(--spacing-s);">
|
||||
<button type="button" class="btn btn-text btn-sm" onclick="event.stopPropagation(); downloadImage(0)">
|
||||
<span class="material-icons">download</span>
|
||||
<span>다운로드</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-text btn-sm" onclick="event.stopPropagation(); regenerateImage(0)">
|
||||
<span class="material-icons">refresh</span>
|
||||
<span>재생성</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="image-option" onclick="selectImage(1)">
|
||||
<div class="image-preview" style="aspect-ratio: 1/1; background: linear-gradient(135deg, #E31E24 0%, #FFD700 50%, #FF1493 100%); border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center; position: relative;">
|
||||
<div style="text-align: center; color: white;">
|
||||
<span class="material-icons" style="font-size: 48px;">auto_awesome</span>
|
||||
<div class="body-l" style="font-weight: 600; margin-top: var(--spacing-s);">화려한 스타일</div>
|
||||
</div>
|
||||
<div class="selection-badge" id="badge1" style="display: none;">
|
||||
<span class="material-icons">check_circle</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; margin-top: var(--spacing-s);">
|
||||
<button type="button" class="btn btn-text btn-sm" onclick="event.stopPropagation(); downloadImage(1)">
|
||||
<span class="material-icons">download</span>
|
||||
<span>다운로드</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-text btn-sm" onclick="event.stopPropagation(); regenerateImage(1)">
|
||||
<span class="material-icons">refresh</span>
|
||||
<span>재생성</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="image-option" onclick="selectImage(2)">
|
||||
<div class="image-preview" style="aspect-ratio: 1/1; background: linear-gradient(135deg, #E31E24 0%, #0066FF 100%); border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center; position: relative;">
|
||||
<div style="text-align: center; color: white;">
|
||||
<span class="material-icons" style="font-size: 48px;">trending_up</span>
|
||||
<div class="body-l" style="font-weight: 600; margin-top: var(--spacing-s);">트렌디 스타일</div>
|
||||
</div>
|
||||
<div class="selection-badge" id="badge2" style="display: none;">
|
||||
<span class="material-icons">check_circle</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; margin-top: var(--spacing-s);">
|
||||
<button type="button" class="btn btn-text btn-sm" onclick="event.stopPropagation(); downloadImage(2)">
|
||||
<span class="material-icons">download</span>
|
||||
<span>다운로드</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-text btn-sm" onclick="event.stopPropagation(); regenerateImage(2)">
|
||||
<span class="material-icons">refresh</span>
|
||||
<span>재생성</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-primary btn-lg" onclick="goToNext()" id="nextButton" disabled style="width: 100%; margin-top: var(--spacing-l);">
|
||||
<span>다음 단계</span>
|
||||
<span class="material-icons">arrow_forward</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="js/common.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
let selectedImageIndex = -1;
|
||||
|
||||
// 컬러 피커와 텍스트 입력 동기화
|
||||
document.getElementById('brandColor').addEventListener('input', function(e) {
|
||||
document.getElementById('brandColorText').value = e.target.value.toUpperCase();
|
||||
});
|
||||
|
||||
document.getElementById('brandColorText').addEventListener('input', function(e) {
|
||||
const color = e.target.value;
|
||||
if (/^#[0-9A-F]{6}$/i.test(color)) {
|
||||
document.getElementById('brandColor').value = color;
|
||||
}
|
||||
});
|
||||
|
||||
// 파일 업로드
|
||||
document.getElementById('uploadArea').addEventListener('click', function() {
|
||||
document.getElementById('logoUpload').click();
|
||||
});
|
||||
|
||||
document.getElementById('logoUpload').addEventListener('change', function(e) {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
Toast.error('파일 크기는 5MB를 초과할 수 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(event) {
|
||||
document.getElementById('previewImage').src = event.target.result;
|
||||
document.getElementById('uploadPlaceholder').style.display = 'none';
|
||||
document.getElementById('uploadPreview').style.display = 'block';
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
});
|
||||
|
||||
window.removeImage = function() {
|
||||
document.getElementById('logoUpload').value = '';
|
||||
document.getElementById('uploadPlaceholder').style.display = 'flex';
|
||||
document.getElementById('uploadPreview').style.display = 'none';
|
||||
};
|
||||
|
||||
window.startGeneration = function() {
|
||||
document.getElementById('generationProgress').style.display = 'block';
|
||||
document.getElementById('generationProgress').scrollIntoView({ behavior: 'smooth' });
|
||||
|
||||
let progress = 0;
|
||||
const interval = setInterval(function() {
|
||||
progress += Math.random() * 15;
|
||||
if (progress > 100) progress = 100;
|
||||
|
||||
document.getElementById('aiProgressBar').style.width = progress + '%';
|
||||
document.getElementById('progressText').textContent = '진행률: ' + Math.round(progress) + '%';
|
||||
|
||||
const remainingTime = Math.max(0, Math.round(150 * (1 - progress / 100)));
|
||||
const minutes = Math.floor(remainingTime / 60);
|
||||
const seconds = remainingTime % 60;
|
||||
document.getElementById('timeEstimate').textContent =
|
||||
'예상 소요: ' + minutes + '분 ' + seconds + '초';
|
||||
|
||||
if (progress >= 100) {
|
||||
clearInterval(interval);
|
||||
setTimeout(function() {
|
||||
document.getElementById('generationResult').style.display = 'block';
|
||||
document.getElementById('generationResult').scrollIntoView({ behavior: 'smooth' });
|
||||
Toast.success('✨ 이미지 생성이 완료되었습니다!');
|
||||
}, 500);
|
||||
}
|
||||
}, 200);
|
||||
};
|
||||
|
||||
window.selectImage = function(index) {
|
||||
// 이전 선택 해제
|
||||
if (selectedImageIndex >= 0) {
|
||||
document.getElementById('badge' + selectedImageIndex).style.display = 'none';
|
||||
}
|
||||
|
||||
selectedImageIndex = index;
|
||||
document.getElementById('badge' + index).style.display = 'flex';
|
||||
document.getElementById('nextButton').disabled = false;
|
||||
|
||||
Toast.show('이미지 ' + (index + 1) + '번이 선택되었습니다');
|
||||
};
|
||||
|
||||
window.downloadImage = function(index) {
|
||||
Toast.success('📥 이미지 ' + (index + 1) + '번이 다운로드되었습니다');
|
||||
};
|
||||
|
||||
window.regenerateImage = function(index) {
|
||||
Loading.show('이미지 재생성 중...');
|
||||
setTimeout(function() {
|
||||
Loading.hide();
|
||||
Toast.success('✨ 새로운 이미지가 생성되었습니다');
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
window.goToNext = function() {
|
||||
if (selectedImageIndex < 0) {
|
||||
Toast.error('이미지를 선택해주세요');
|
||||
return;
|
||||
}
|
||||
Toast.show('다음 단계로 이동합니다');
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.progress-indicator {
|
||||
background: white;
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
border-bottom: 1px solid var(--color-gray-300);
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 14px;
|
||||
color: var(--color-gray-600);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 4px;
|
||||
background: var(--color-gray-200);
|
||||
border-radius: var(--radius-full);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: var(--color-primary-main);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
border: 2px dashed var(--color-gray-300);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--spacing-xl);
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.upload-area:hover {
|
||||
border-color: var(--color-primary-main);
|
||||
background: var(--color-gray-50);
|
||||
}
|
||||
|
||||
.upload-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.upload-preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
width: 100%;
|
||||
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;
|
||||
}
|
||||
|
||||
.image-option {
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.image-option:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.image-preview {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.selection-badge {
|
||||
position: absolute;
|
||||
top: var(--spacing-m);
|
||||
right: var(--spacing-m);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.selection-badge .material-icons {
|
||||
color: var(--color-success);
|
||||
font-size: 32px;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,483 +0,0 @@
|
||||
<!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>
|
||||
@ -1,240 +0,0 @@
|
||||
<!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>
|
||||
@ -1,325 +0,0 @@
|
||||
<!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>
|
||||
@ -1,279 +0,0 @@
|
||||
<!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>
|
||||
@ -1,386 +0,0 @@
|
||||
<!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-indicator" role="progressbar" aria-valuenow="33" aria-valuemin="0" aria-valuemax="100">
|
||||
<div class="progress-text">배포 1/3: 채널 선택</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: 33%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main id="main-content" class="container" role="main">
|
||||
<section class="section" aria-labelledby="kt-channels-title">
|
||||
<h2 id="kt-channels-title" class="h3">KT 채널</h2>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="display: flex; flex-direction: column; gap: var(--spacing-m);">
|
||||
<div class="channel-item" onclick="toggleChannel('woori')">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-m); flex: 1;">
|
||||
<input type="checkbox" id="woori" class="checkbox" aria-label="우리동네TV 선택">
|
||||
<div style="flex: 1;">
|
||||
<div class="body-l" style="font-weight: 600; margin-bottom: 4px;">
|
||||
<span class="material-icons" style="vertical-align: middle; margin-right: 4px; color: var(--color-success);">tv</span>
|
||||
우리동네TV
|
||||
</div>
|
||||
<div class="body-s" style="color: var(--color-gray-600);">지역 주변 5km 내 자동 노출</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-text btn-sm" onclick="event.stopPropagation(); openSettings('woori')" id="wooriSettings" disabled>
|
||||
<span>설정</span>
|
||||
<span class="material-icons">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="channel-item" onclick="toggleChannel('ringo')">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-m); flex: 1;">
|
||||
<input type="checkbox" id="ringo" class="checkbox" aria-label="링고비즈 연결음 선택">
|
||||
<div style="flex: 1;">
|
||||
<div class="body-l" style="font-weight: 600; margin-bottom: 4px;">
|
||||
<span class="material-icons" style="vertical-align: middle; margin-right: 4px; color: var(--color-warning);">call</span>
|
||||
링고비즈 연결음
|
||||
</div>
|
||||
<div class="body-s" style="color: var(--color-gray-600);">매장 전화 연결음으로 이벤트 홍보</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-text btn-sm" onclick="event.stopPropagation(); openSettings('ringo')" id="ringoSettings" disabled>
|
||||
<span>설정</span>
|
||||
<span class="material-icons">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="channel-item" onclick="toggleChannel('genie')">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-m); flex: 1;">
|
||||
<input type="checkbox" id="genie" class="checkbox" aria-label="지니TV 광고 선택">
|
||||
<div style="flex: 1;">
|
||||
<div class="body-l" style="font-weight: 600; margin-bottom: 4px;">
|
||||
<span class="material-icons" style="vertical-align: middle; margin-right: 4px; color: var(--color-secondary-main);">connected_tv</span>
|
||||
지니TV 광고
|
||||
</div>
|
||||
<div class="body-s" style="color: var(--color-gray-600);">TV 광고로 대규모 노출</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-text btn-sm" onclick="event.stopPropagation(); openSettings('genie')" id="genieSettings" disabled>
|
||||
<span>설정</span>
|
||||
<span class="material-icons">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="sns-channels-title">
|
||||
<h2 id="sns-channels-title" class="h3">SNS 채널</h2>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="display: flex; flex-direction: column; gap: var(--spacing-m);">
|
||||
<div class="channel-item" onclick="toggleChannel('instagram')">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-m); flex: 1;">
|
||||
<input type="checkbox" id="instagram" class="checkbox" checked disabled aria-label="Instagram (필수 채널)">
|
||||
<div style="flex: 1;">
|
||||
<div class="body-l" style="font-weight: 600; margin-bottom: 4px;">
|
||||
<span class="material-icons" style="vertical-align: middle; margin-right: 4px; color: #E4405F;">photo_camera</span>
|
||||
Instagram (필수)
|
||||
</div>
|
||||
<div class="body-s" style="color: var(--color-success); font-weight: 600;">
|
||||
<span class="material-icons" style="font-size: 14px; vertical-align: middle;">check_circle</span>
|
||||
계정 연동 완료
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="channel-item" onclick="toggleChannel('facebook')">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-m); flex: 1;">
|
||||
<input type="checkbox" id="facebook" class="checkbox" aria-label="Facebook 선택">
|
||||
<div style="flex: 1;">
|
||||
<div class="body-l" style="font-weight: 600; margin-bottom: 4px;">
|
||||
<span class="material-icons" style="vertical-align: middle; margin-right: 4px; color: #1877F2;">facebook</span>
|
||||
Facebook
|
||||
</div>
|
||||
<div class="body-s" style="color: var(--color-gray-600);">페이스북 페이지에 자동 게시</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-text btn-sm" onclick="event.stopPropagation(); openSettings('facebook')" id="facebookSettings" disabled>
|
||||
<span>설정</span>
|
||||
<span class="material-icons">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="channel-item" onclick="toggleChannel('naver')">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-m); flex: 1;">
|
||||
<input type="checkbox" id="naver" class="checkbox" aria-label="Naver Blog 선택">
|
||||
<div style="flex: 1;">
|
||||
<div class="body-l" style="font-weight: 600; margin-bottom: 4px;">
|
||||
<span class="material-icons" style="vertical-align: middle; margin-right: 4px; color: #03C75A;">article</span>
|
||||
Naver Blog
|
||||
</div>
|
||||
<div class="body-s" style="color: var(--color-gray-600);">네이버 블로그 자동 포스팅</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-text btn-sm" onclick="event.stopPropagation(); openSettings('naver')" id="naverSettings" disabled>
|
||||
<span>설정</span>
|
||||
<span class="material-icons">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="channel-item" onclick="toggleChannel('kakao')">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-m); flex: 1;">
|
||||
<input type="checkbox" id="kakao" class="checkbox" aria-label="카카오톡 채널 선택">
|
||||
<div style="flex: 1;">
|
||||
<div class="body-l" style="font-weight: 600; margin-bottom: 4px;">
|
||||
<span class="material-icons" style="vertical-align: middle; margin-right: 4px; color: #FEE500;">chat</span>
|
||||
카카오톡 채널
|
||||
</div>
|
||||
<div class="body-s" style="color: var(--color-gray-600);">카카오톡 채널 메시지 발송</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-text btn-sm" onclick="event.stopPropagation(); openSettings('kakao')" id="kakaoSettings" disabled>
|
||||
<span>설정</span>
|
||||
<span class="material-icons">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="offline-title">
|
||||
<h2 id="offline-title" class="h3">오프라인 채널</h2>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="channel-item" onclick="toggleChannel('qr')">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-m); flex: 1;">
|
||||
<input type="checkbox" id="qr" class="checkbox" checked aria-label="QR 포스터 (기본 선택)">
|
||||
<div style="flex: 1;">
|
||||
<div class="body-l" style="font-weight: 600; margin-bottom: 4px;">
|
||||
<span class="material-icons" style="vertical-align: middle; margin-right: 4px; color: var(--color-primary-main);">qr_code_2</span>
|
||||
QR 포스터 (기본)
|
||||
</div>
|
||||
<div class="body-s" style="color: var(--color-gray-600);">매장 내 부착용 QR 포스터</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="schedule-title">
|
||||
<h2 id="schedule-title" class="h3">배포 일정</h2>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="display: flex; flex-direction: column; gap: var(--spacing-m);">
|
||||
<label class="radio-item" onclick="selectSchedule('now')">
|
||||
<input type="radio" name="schedule" value="now" checked>
|
||||
<div>
|
||||
<div class="body-l" style="font-weight: 600;">즉시 배포</div>
|
||||
<div class="body-s" style="color: var(--color-gray-600);">승인 후 즉시 모든 채널에 배포</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="radio-item" onclick="selectSchedule('scheduled')">
|
||||
<input type="radio" name="schedule" value="scheduled">
|
||||
<div>
|
||||
<div class="body-l" style="font-weight: 600;">예약 배포</div>
|
||||
<div class="body-s" style="color: var(--color-gray-600);">지정한 날짜/시간에 자동 배포</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="scheduleSettings" style="display: none; margin-top: var(--spacing-m); padding-top: var(--spacing-m); border-top: 1px solid var(--color-gray-300);">
|
||||
<div class="form-group">
|
||||
<label for="scheduleDate" class="form-label">배포 날짜</label>
|
||||
<input type="date" id="scheduleDate" class="input-field" aria-label="배포 날짜 선택">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="scheduleTime" class="form-label">배포 시간</label>
|
||||
<input type="time" id="scheduleTime" class="input-field" aria-label="배포 시간 선택">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="alert alert-info" role="status">
|
||||
<div style="display: flex; align-items: flex-start; gap: var(--spacing-s);">
|
||||
<span class="material-icons">info</span>
|
||||
<div class="body-s">
|
||||
선택한 채널: <strong id="selectedCount">2개</strong><br>
|
||||
Instagram은 필수 채널이며, QR 포스터는 기본 제공됩니다.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-primary btn-lg" onclick="goToNext()" style="width: 100%;">
|
||||
<span>다음 단계</span>
|
||||
<span class="material-icons">arrow_forward</span>
|
||||
</button>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="js/common.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
function updateSelectedCount() {
|
||||
const checkboxes = document.querySelectorAll('.checkbox:checked:not([disabled])');
|
||||
const count = checkboxes.length + 1; // +1 for Instagram (required)
|
||||
document.getElementById('selectedCount').textContent = count + '개';
|
||||
}
|
||||
|
||||
window.toggleChannel = function(channelId) {
|
||||
const checkbox = document.getElementById(channelId);
|
||||
if (checkbox.disabled) return;
|
||||
|
||||
checkbox.checked = !checkbox.checked;
|
||||
|
||||
const settingsBtn = document.getElementById(channelId + 'Settings');
|
||||
if (settingsBtn) {
|
||||
settingsBtn.disabled = !checkbox.checked;
|
||||
}
|
||||
|
||||
updateSelectedCount();
|
||||
};
|
||||
|
||||
window.openSettings = function(channelId) {
|
||||
Toast.show(channelId.toUpperCase() + ' 채널 설정');
|
||||
};
|
||||
|
||||
window.selectSchedule = function(type) {
|
||||
const scheduleSettings = document.getElementById('scheduleSettings');
|
||||
if (type === 'scheduled') {
|
||||
scheduleSettings.style.display = 'block';
|
||||
|
||||
// Set default values
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
document.getElementById('scheduleDate').value = tomorrow.toISOString().split('T')[0];
|
||||
document.getElementById('scheduleTime').value = '09:00';
|
||||
} else {
|
||||
scheduleSettings.style.display = 'none';
|
||||
}
|
||||
};
|
||||
|
||||
window.goToNext = function() {
|
||||
const selectedChannels = [];
|
||||
document.querySelectorAll('.checkbox:checked').forEach(function(checkbox) {
|
||||
selectedChannels.push(checkbox.id);
|
||||
});
|
||||
|
||||
const schedule = document.querySelector('input[name="schedule"]:checked').value;
|
||||
|
||||
if (schedule === 'scheduled') {
|
||||
const date = document.getElementById('scheduleDate').value;
|
||||
const time = document.getElementById('scheduleTime').value;
|
||||
|
||||
if (!date || !time) {
|
||||
Toast.error('배포 날짜와 시간을 선택해주세요');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Toast.success('채널 선택이 완료되었습니다. (' + selectedChannels.length + '개 채널)');
|
||||
};
|
||||
|
||||
// Initialize
|
||||
updateSelectedCount();
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.progress-indicator {
|
||||
background: white;
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
border-bottom: 1px solid var(--color-gray-300);
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 14px;
|
||||
color: var(--color-gray-600);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 4px;
|
||||
background: var(--color-gray-200);
|
||||
border-radius: var(--radius-full);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: var(--color-primary-main);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.channel-item {
|
||||
padding: var(--spacing-m);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.channel-item:hover {
|
||||
border-color: var(--color-primary-main);
|
||||
background: var(--color-gray-50);
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.radio-item {
|
||||
padding: var(--spacing-m);
|
||||
border: 1px solid var(--color-gray-300);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.radio-item:hover {
|
||||
border-color: var(--color-primary-main);
|
||||
background: var(--color-gray-50);
|
||||
}
|
||||
|
||||
.radio-item input[type="radio"] {
|
||||
margin-top: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,364 +0,0 @@
|
||||
<!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>
|
||||
@ -1,166 +0,0 @@
|
||||
<!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>
|
||||
@ -1,432 +0,0 @@
|
||||
<!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>
|
||||
@ -1,330 +0,0 @@
|
||||
<!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>
|
||||
|
||||
<main id="main-content" class="container" role="main" style="padding-bottom: 80px;">
|
||||
<section class="section">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-m);">
|
||||
<div class="caption" style="color: var(--color-gray-600);">
|
||||
마지막 업데이트: <span id="lastUpdate">15:35</span>
|
||||
</div>
|
||||
<button type="button" class="btn-text btn-sm" onclick="refresh()">
|
||||
<span class="material-icons">refresh</span>
|
||||
<span>새로고침</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: var(--spacing-xs);">진행 중인 이벤트</div>
|
||||
<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" aria-labelledby="metrics-title">
|
||||
<h2 id="metrics-title" class="h3">주요 지표</h2>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-m); margin-bottom: var(--spacing-l);">
|
||||
<div style="width: 56px; height: 56px; background: var(--color-primary-lightest); border-radius: 50%; display: flex; align-items: center; justify-content: center;">
|
||||
<span class="material-icons" style="font-size: 32px; color: var(--color-primary-main);">people</span>
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">총 참여자</div>
|
||||
<div class="h3" style="color: var(--color-primary-main); margin-bottom: 4px;" id="totalParticipants">1,234명</div>
|
||||
<div class="caption" style="color: var(--color-success); font-weight: 600;">
|
||||
<span class="material-icons" style="font-size: 14px; vertical-align: middle;">trending_up</span>
|
||||
+45 (오늘)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-m); margin-bottom: var(--spacing-l);">
|
||||
<div style="width: 56px; height: 56px; background: var(--color-secondary-lightest); border-radius: 50%; display: flex; align-items: center; justify-content: center;">
|
||||
<span class="material-icons" style="font-size: 32px; color: var(--color-secondary-main);">visibility</span>
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">총 노출 수</div>
|
||||
<div class="h3" style="color: var(--color-secondary-main); margin-bottom: 4px;" id="totalViews">15,678회</div>
|
||||
<div class="caption" style="color: var(--color-success); font-weight: 600;">
|
||||
<span class="material-icons" style="font-size: 14px; vertical-align: middle;">trending_up</span>
|
||||
+230 (최근 1시간)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-m); margin-bottom: var(--spacing-l);">
|
||||
<div style="width: 56px; height: 56px; background: var(--color-warning-lightest); border-radius: 50%; display: flex; align-items: center; justify-content: center;">
|
||||
<span class="material-icons" style="font-size: 32px; color: var(--color-warning);">monetization_on</span>
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">매출 증가율</div>
|
||||
<div class="h3" style="color: var(--color-warning); margin-bottom: 4px;" id="salesIncrease">+42%</div>
|
||||
<div class="caption" style="color: var(--color-gray-600);">(이벤트 전 대비)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-m);">
|
||||
<div style="width: 56px; height: 56px; background: var(--color-success-lightest); border-radius: 50%; display: flex; align-items: center; justify-content: center;">
|
||||
<span class="material-icons" style="font-size: 32px; color: var(--color-success);">trending_up</span>
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">예상 ROI</div>
|
||||
<div class="h3" style="color: var(--color-success); margin-bottom: 4px;" id="expectedROI">245%</div>
|
||||
<div class="caption" style="color: var(--color-gray-600);">(투자 대비 수익)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="channels-title">
|
||||
<h2 id="channels-title" class="h3">채널별 참여 현황</h2>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="display: flex; flex-direction: column; gap: var(--spacing-l);">
|
||||
<div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-xs);">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span class="material-icons" style="color: var(--color-primary-main); font-size: 20px;">qr_code_2</span>
|
||||
<span class="body-m" style="font-weight: 600;">QR코드</span>
|
||||
</div>
|
||||
<span class="h4" style="color: var(--color-primary-main);">45%</span>
|
||||
</div>
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-bar-fill" style="width: 45%; background: var(--color-primary-main);"></div>
|
||||
</div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-top: var(--spacing-xs);">556명 참여</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-xs);">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span class="material-icons" style="color: var(--color-secondary-main); font-size: 20px;">photo_camera</span>
|
||||
<span class="body-m" style="font-weight: 600;">Instagram</span>
|
||||
</div>
|
||||
<span class="h4" style="color: var(--color-secondary-main);">30%</span>
|
||||
</div>
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-bar-fill" style="width: 30%; background: var(--color-secondary-main);"></div>
|
||||
</div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-top: var(--spacing-xs);">370명 참여</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-xs);">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 20px;">tv</span>
|
||||
<span class="body-m" style="font-weight: 600;">우리동네TV</span>
|
||||
</div>
|
||||
<span class="h4" style="color: var(--color-success);">15%</span>
|
||||
</div>
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-bar-fill" style="width: 15%; background: var(--color-success);"></div>
|
||||
</div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-top: var(--spacing-xs);">185명 참여</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-xs);">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span class="material-icons" style="color: var(--color-warning); font-size: 20px;">article</span>
|
||||
<span class="body-m" style="font-weight: 600;">Naver Blog</span>
|
||||
</div>
|
||||
<span class="h4" style="color: var(--color-warning);">10%</span>
|
||||
</div>
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-bar-fill" style="width: 10%; background: var(--color-warning);"></div>
|
||||
</div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-top: var(--spacing-xs);">123명 참여</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<button type="button" class="btn btn-primary btn-lg" onclick="viewDetailedAnalysis()" style="width: 100%;">
|
||||
<span class="material-icons">analytics</span>
|
||||
<span>상세 분석 보기</span>
|
||||
</button>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav" role="navigation">
|
||||
<button type="button" class="nav-item" onclick="goToHome()">
|
||||
<span class="material-icons">home</span>
|
||||
<span>홈</span>
|
||||
</button>
|
||||
<button type="button" class="nav-item" onclick="goToEvents()">
|
||||
<span class="material-icons">event</span>
|
||||
<span>이벤트</span>
|
||||
</button>
|
||||
<button type="button" class="nav-item active">
|
||||
<span class="material-icons">analytics</span>
|
||||
<span>분석</span>
|
||||
</button>
|
||||
<button type="button" class="nav-item" onclick="goToMy()">
|
||||
<span class="material-icons">person</span>
|
||||
<span>MY</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<script src="js/common.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
let autoRefreshInterval;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
updateTime();
|
||||
startAutoRefresh();
|
||||
});
|
||||
|
||||
function startAutoRefresh() {
|
||||
autoRefreshInterval = setInterval(function() {
|
||||
refresh(true);
|
||||
}, 300000); // 5분 간격
|
||||
}
|
||||
|
||||
window.refresh = function(isAuto = false) {
|
||||
const now = new Date();
|
||||
const timeStr = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0');
|
||||
document.getElementById('lastUpdate').textContent = timeStr;
|
||||
|
||||
// Simulate data update
|
||||
const participants = parseInt(document.getElementById('totalParticipants').textContent);
|
||||
const newParticipants = participants + Math.floor(Math.random() * 10);
|
||||
document.getElementById('totalParticipants').textContent = newParticipants.toLocaleString() + '명';
|
||||
|
||||
const views = parseInt(document.getElementById('totalViews').textContent.replace(',', ''));
|
||||
const newViews = views + Math.floor(Math.random() * 100);
|
||||
document.getElementById('totalViews').textContent = newViews.toLocaleString() + '회';
|
||||
|
||||
if (!isAuto) {
|
||||
Toast.success('데이터가 업데이트되었습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
function updateTime() {
|
||||
const now = new Date();
|
||||
const timeStr = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0');
|
||||
document.getElementById('lastUpdate').textContent = timeStr;
|
||||
}
|
||||
|
||||
window.viewDetailedAnalysis = function() {
|
||||
window.location.href = '22-채널별성과분석.html';
|
||||
};
|
||||
|
||||
window.toggleMenu = function() {
|
||||
Toast.show('메뉴');
|
||||
};
|
||||
|
||||
window.goToHome = function() {
|
||||
window.location.href = '21.5-홈.html';
|
||||
};
|
||||
|
||||
window.goToEvents = function() {
|
||||
window.location.href = '26-이벤트목록.html';
|
||||
};
|
||||
|
||||
window.goToMy = function() {
|
||||
window.location.href = '25-마이페이지.html';
|
||||
};
|
||||
|
||||
// Cleanup on page unload
|
||||
window.addEventListener('beforeunload', function() {
|
||||
if (autoRefreshInterval) {
|
||||
clearInterval(autoRefreshInterval);
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.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;
|
||||
}
|
||||
|
||||
.bottom-nav {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
border-top: 1px solid var(--color-gray-300);
|
||||
display: flex;
|
||||
padding: var(--spacing-xs) 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: var(--spacing-xs);
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-gray-600);
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
color: var(--color-primary-main);
|
||||
}
|
||||
|
||||
.nav-item .material-icons {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.nav-item span:last-child {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,522 +0,0 @@
|
||||
<!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: 80px;">
|
||||
<section class="section">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-m);">
|
||||
<h2 class="h3">📊 채널별 성과 비교</h2>
|
||||
<button type="button" class="btn-text btn-sm" onclick="exportReport()">
|
||||
<span class="material-icons">download</span>
|
||||
<span>리포트</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="body-s" style="color: var(--color-gray-600); margin-bottom: var(--spacing-xs);">분석 기간</div>
|
||||
<select class="select-field" id="periodSelect" onchange="changePeriod()" aria-label="분석 기간 선택">
|
||||
<option value="7">최근 7일</option>
|
||||
<option value="30" selected>최근 30일</option>
|
||||
<option value="90">최근 90일</option>
|
||||
<option value="all">전체 기간</option>
|
||||
</select>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-top: var(--spacing-xs);" id="periodDisplay">2025-12-01 ~ 현재</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="ranking-title">
|
||||
<h3 id="ranking-title" class="body-l" style="font-weight: 600; margin-bottom: var(--spacing-m);">종합 순위</h3>
|
||||
|
||||
<div style="display: flex; flex-direction: column; gap: var(--spacing-s);">
|
||||
<div class="card" style="border: 2px solid var(--color-primary-main);">
|
||||
<div class="card-body">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span style="font-size: 24px;">🥇</span>
|
||||
<div style="flex: 1;">
|
||||
<div class="body-m" style="font-weight: 600;">QR코드</div>
|
||||
<div class="caption" style="color: var(--color-success); font-weight: 600;">가장 효과적</div>
|
||||
</div>
|
||||
<span class="h4" style="color: var(--color-primary-main);">9.8%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span style="font-size: 24px;">🥈</span>
|
||||
<div style="flex: 1;">
|
||||
<div class="body-m" style="font-weight: 600;">Instagram</div>
|
||||
<div class="caption" style="color: var(--color-gray-600);">2위</div>
|
||||
</div>
|
||||
<span class="h4" style="color: var(--color-secondary-main);">8.2%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span style="font-size: 24px;">🥉</span>
|
||||
<div style="flex: 1;">
|
||||
<div class="body-m" style="font-weight: 600;">우리동네TV</div>
|
||||
<div class="caption" style="color: var(--color-gray-600);">3위</div>
|
||||
</div>
|
||||
<span class="h4" style="color: var(--color-success);">7.5%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="channels-title">
|
||||
<h3 id="channels-title" class="body-l" style="font-weight: 600; margin-bottom: var(--spacing-m);">채널별 상세 성과</h3>
|
||||
|
||||
<!-- QR코드 -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-m);">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span class="material-icons" style="color: var(--color-primary-main); font-size: 24px;">qr_code_2</span>
|
||||
<span class="body-l" style="font-weight: 600;">QR코드</span>
|
||||
</div>
|
||||
<span style="font-size: 20px;">🥇</span>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--spacing-m); margin-bottom: var(--spacing-m);">
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">노출 수</div>
|
||||
<div class="h4" style="color: var(--color-primary-main);">5,678회</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">참여자</div>
|
||||
<div class="h4" style="color: var(--color-primary-main);">556명</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">참여율</div>
|
||||
<div class="h4" style="color: var(--color-success);">9.8%</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">전환자</div>
|
||||
<div class="h4" style="color: var(--color-primary-main);">223명</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: var(--spacing-m);">
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">전환율</div>
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<div class="progress-bar-container" style="flex: 1;">
|
||||
<div class="progress-bar-fill" style="width: 40%; background: var(--color-primary-main);"></div>
|
||||
</div>
|
||||
<span class="body-m" style="font-weight: 600; color: var(--color-primary-main);">40%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="background: var(--color-gray-100); padding: var(--spacing-s); border-radius: var(--radius-md); margin-bottom: var(--spacing-m);">
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">CPA (고객 획득 비용)</div>
|
||||
<div class="body-l" style="font-weight: 600;">180원</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-outline btn-lg" onclick="viewChannelDetail('qr')" style="width: 100%;">
|
||||
<span>상세 보기</span>
|
||||
<span class="material-icons">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Instagram -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-m);">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span class="material-icons" style="color: var(--color-secondary-main); font-size: 24px;">photo_camera</span>
|
||||
<span class="body-l" style="font-weight: 600;">Instagram</span>
|
||||
</div>
|
||||
<span style="font-size: 20px;">🥈</span>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--spacing-m); margin-bottom: var(--spacing-m);">
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">노출 수</div>
|
||||
<div class="h4" style="color: var(--color-secondary-main);">4,523회</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">참여자</div>
|
||||
<div class="h4" style="color: var(--color-secondary-main);">370명</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">참여율</div>
|
||||
<div class="h4" style="color: var(--color-success);">8.2%</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">전환자</div>
|
||||
<div class="h4" style="color: var(--color-secondary-main);">148명</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: var(--spacing-m);">
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">전환율</div>
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<div class="progress-bar-container" style="flex: 1;">
|
||||
<div class="progress-bar-fill" style="width: 40%; background: var(--color-secondary-main);"></div>
|
||||
</div>
|
||||
<span class="body-m" style="font-weight: 600; color: var(--color-secondary-main);">40%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="background: var(--color-gray-100); padding: var(--spacing-s); border-radius: var(--radius-md); margin-bottom: var(--spacing-m);">
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">CPA (고객 획득 비용)</div>
|
||||
<div class="body-l" style="font-weight: 600;">270원</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-outline btn-lg" onclick="viewChannelDetail('instagram')" style="width: 100%;">
|
||||
<span>상세 보기</span>
|
||||
<span class="material-icons">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 우리동네TV -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; 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); font-size: 24px;">tv</span>
|
||||
<span class="body-l" style="font-weight: 600;">우리동네TV</span>
|
||||
</div>
|
||||
<span style="font-size: 20px;">🥉</span>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--spacing-m); margin-bottom: var(--spacing-m);">
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">노출 수</div>
|
||||
<div class="h4" style="color: var(--color-success);">3,890회</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">참여자</div>
|
||||
<div class="h4" style="color: var(--color-success);">185명</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">참여율</div>
|
||||
<div class="h4" style="color: var(--color-success);">7.5%</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">전환자</div>
|
||||
<div class="h4" style="color: var(--color-success);">74명</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: var(--spacing-m);">
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">전환율</div>
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<div class="progress-bar-container" style="flex: 1;">
|
||||
<div class="progress-bar-fill" style="width: 40%; background: var(--color-success);"></div>
|
||||
</div>
|
||||
<span class="body-m" style="font-weight: 600; color: var(--color-success);">40%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="background: var(--color-gray-100); padding: var(--spacing-s); border-radius: var(--radius-md); margin-bottom: var(--spacing-m);">
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">CPA (고객 획득 비용)</div>
|
||||
<div class="body-l" style="font-weight: 600;">405원</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-outline btn-lg" onclick="viewChannelDetail('tv')" style="width: 100%;">
|
||||
<span>상세 보기</span>
|
||||
<span class="material-icons">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Naver Blog -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; 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); font-size: 24px;">article</span>
|
||||
<span class="body-l" style="font-weight: 600;">Naver Blog</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--spacing-m); margin-bottom: var(--spacing-m);">
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">노출 수</div>
|
||||
<div class="h4" style="color: var(--color-warning);">1,587회</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">참여자</div>
|
||||
<div class="h4" style="color: var(--color-warning);">123명</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">참여율</div>
|
||||
<div class="h4" style="color: var(--color-warning);">7.8%</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">전환자</div>
|
||||
<div class="h4" style="color: var(--color-warning);">49명</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: var(--spacing-m);">
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">전환율</div>
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<div class="progress-bar-container" style="flex: 1;">
|
||||
<div class="progress-bar-fill" style="width: 40%; background: var(--color-warning);"></div>
|
||||
</div>
|
||||
<span class="body-m" style="font-weight: 600; color: var(--color-warning);">40%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="background: var(--color-gray-100); padding: var(--spacing-s); border-radius: var(--radius-md); margin-bottom: var(--spacing-m);">
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">CPA (고객 획득 비용)</div>
|
||||
<div class="body-l" style="font-weight: 600;">815원</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-outline btn-lg" onclick="viewChannelDetail('blog')" style="width: 100%;">
|
||||
<span>상세 보기</span>
|
||||
<span class="material-icons">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="comparison-title">
|
||||
<h3 id="comparison-title" class="body-l" style="font-weight: 600; margin-bottom: var(--spacing-m);">비교 차트</h3>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: var(--spacing-l);">참여율 비교</div>
|
||||
|
||||
<div style="display: flex; flex-direction: column; gap: var(--spacing-m);">
|
||||
<div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-xs);">
|
||||
<span class="body-s">QR코드</span>
|
||||
<span class="body-s" style="font-weight: 600; color: var(--color-primary-main);">9.8%</span>
|
||||
</div>
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-bar-fill" style="width: 100%; background: var(--color-primary-main);"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-xs);">
|
||||
<span class="body-s">Instagram</span>
|
||||
<span class="body-s" style="font-weight: 600; color: var(--color-secondary-main);">8.2%</span>
|
||||
</div>
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-bar-fill" style="width: 84%; background: var(--color-secondary-main);"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-xs);">
|
||||
<span class="body-s">우리동네TV</span>
|
||||
<span class="body-s" style="font-weight: 600; color: var(--color-success);">7.8%</span>
|
||||
</div>
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-bar-fill" style="width: 80%; background: var(--color-success);"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-xs);">
|
||||
<span class="body-s">Naver Blog</span>
|
||||
<span class="body-s" style="font-weight: 600; color: var(--color-warning);">7.5%</span>
|
||||
</div>
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-bar-fill" style="width: 77%; background: var(--color-warning);"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="card" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||
<div class="card-body">
|
||||
<div style="display: flex; align-items: flex-start; gap: var(--spacing-m);">
|
||||
<span class="material-icons" style="font-size: 32px;">lightbulb</span>
|
||||
<div>
|
||||
<div class="body-l" style="font-weight: 600; margin-bottom: var(--spacing-s); color: white;">💡 AI 인사이트</div>
|
||||
<div class="body-s" style="color: white; line-height: 1.6;">
|
||||
QR코드가 가장 높은 참여율(9.8%)과 전환율(40%)을 보입니다. 오프라인 홍보를 강화하면 효과가 더 좋을 것 같아요. 또한 Instagram의 노출 대비 참여율이 좋으므로 SNS 콘텐츠 제작을 늘리는 것을 추천합니다.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav" role="navigation">
|
||||
<button type="button" class="nav-item" onclick="goToHome()">
|
||||
<span class="material-icons">home</span>
|
||||
<span>홈</span>
|
||||
</button>
|
||||
<button type="button" class="nav-item" onclick="goToEvents()">
|
||||
<span class="material-icons">event</span>
|
||||
<span>이벤트</span>
|
||||
</button>
|
||||
<button type="button" class="nav-item active">
|
||||
<span class="material-icons">analytics</span>
|
||||
<span>분석</span>
|
||||
</button>
|
||||
<button type="button" class="nav-item" onclick="goToMy()">
|
||||
<span class="material-icons">person</span>
|
||||
<span>MY</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<script src="js/common.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
updatePeriodDisplay();
|
||||
});
|
||||
|
||||
function updatePeriodDisplay() {
|
||||
const select = document.getElementById('periodSelect');
|
||||
const display = document.getElementById('periodDisplay');
|
||||
const value = select.value;
|
||||
const today = new Date();
|
||||
let startDate;
|
||||
|
||||
if (value === 'all') {
|
||||
display.textContent = '2025-12-01 ~ 현재';
|
||||
} else {
|
||||
const days = parseInt(value);
|
||||
startDate = new Date(today);
|
||||
startDate.setDate(today.getDate() - days);
|
||||
|
||||
const formatDate = function(date) {
|
||||
return date.getFullYear() + '-' +
|
||||
String(date.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(date.getDate()).padStart(2, '0');
|
||||
};
|
||||
|
||||
display.textContent = formatDate(startDate) + ' ~ ' + formatDate(today);
|
||||
}
|
||||
}
|
||||
|
||||
window.changePeriod = function() {
|
||||
Loading.show('데이터 분석 중...');
|
||||
setTimeout(function() {
|
||||
Loading.hide();
|
||||
updatePeriodDisplay();
|
||||
Toast.success('기간이 변경되었습니다.');
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
window.viewChannelDetail = function(channel) {
|
||||
const channelNames = {
|
||||
qr: 'QR코드',
|
||||
instagram: 'Instagram',
|
||||
tv: '우리동네TV',
|
||||
blog: 'Naver Blog'
|
||||
};
|
||||
Toast.show('📊 ' + channelNames[channel] + ' 상세 분석');
|
||||
};
|
||||
|
||||
window.exportReport = function() {
|
||||
Loading.show('리포트 생성 중...');
|
||||
setTimeout(function() {
|
||||
Loading.hide();
|
||||
Toast.success('📄 채널 성과 리포트가 다운로드되었습니다!');
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
window.goToHome = function() {
|
||||
window.location.href = '21.5-홈.html';
|
||||
};
|
||||
|
||||
window.goToEvents = function() {
|
||||
window.location.href = '26-이벤트목록.html';
|
||||
};
|
||||
|
||||
window.goToMy = function() {
|
||||
window.location.href = '25-마이페이지.html';
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.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;
|
||||
}
|
||||
|
||||
.bottom-nav {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
border-top: 1px solid var(--color-gray-300);
|
||||
display: flex;
|
||||
padding: var(--spacing-xs) 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: var(--spacing-xs);
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-gray-600);
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
color: var(--color-primary-main);
|
||||
}
|
||||
|
||||
.nav-item .material-icons {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.nav-item span:last-child {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,393 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ROI 분석 - 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">ROI 분석</h1>
|
||||
<div style="width: 40px;"></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="main-content" class="container" role="main" style="padding-bottom: 80px;">
|
||||
<section class="section">
|
||||
<h2 class="h3">💰 투자 대비 효과</h2>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="card" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||
<div class="card-body" style="text-align: center; padding: var(--spacing-xl);">
|
||||
<div class="caption" style="color: rgba(255,255,255,0.9); margin-bottom: var(--spacing-s);">ROI (투자 대비 수익률)</div>
|
||||
<div style="font-size: 56px; font-weight: 700; margin-bottom: var(--spacing-m); color: white;" id="roiValue">245%</div>
|
||||
<div class="body-m" style="color: rgba(255,255,255,0.95);">
|
||||
투자한 금액 대비 <span style="font-weight: 700;">2.45배</span> 수익!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="cost-title">
|
||||
<h3 id="cost-title" class="body-l" style="font-weight: 600; margin-bottom: var(--spacing-m);">비용 내역</h3>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="margin-bottom: var(--spacing-l);">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-s);">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span class="material-icons" style="color: var(--color-warning);">card_giftcard</span>
|
||||
<span class="body-m" style="font-weight: 600;">경품 비용</span>
|
||||
</div>
|
||||
<span class="h4" style="color: var(--color-error);">100,000원</span>
|
||||
</div>
|
||||
<div class="caption" style="color: var(--color-gray-600); padding-left: 32px;">
|
||||
상품권 10장 × 10,000원
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: var(--spacing-l);">
|
||||
<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);">settings</span>
|
||||
<span class="body-m" style="font-weight: 600;">플랫폼 이용료</span>
|
||||
</div>
|
||||
<div style="padding-left: 32px; display: flex; flex-direction: column; gap: var(--spacing-xs);">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span class="body-s" style="color: var(--color-gray-600);">• 우리동네TV</span>
|
||||
<span class="body-s" style="color: var(--color-success); font-weight: 600;">무료</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span class="body-s" style="color: var(--color-gray-600);">• 지니TV</span>
|
||||
<span class="body-s" style="color: var(--color-success); font-weight: 600;">무료</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span class="body-s" style="color: var(--color-gray-600);">• SNS (Instagram, Blog)</span>
|
||||
<span class="body-s" style="color: var(--color-success); font-weight: 600;">무료</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border-top: 2px solid var(--color-gray-300); padding-top: var(--spacing-m);">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span class="body-l" style="font-weight: 700;">총 투자</span>
|
||||
<span class="h3" style="color: var(--color-error);">100,000원</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="revenue-title">
|
||||
<h3 id="revenue-title" class="body-l" style="font-weight: 600; margin-bottom: var(--spacing-m);">수익 내역</h3>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="margin-bottom: var(--spacing-l);">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-s);">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span class="material-icons" style="color: var(--color-success);">trending_up</span>
|
||||
<span class="body-m" style="font-weight: 600;">매출 증가액</span>
|
||||
</div>
|
||||
<span class="h4" style="color: var(--color-success);">180,000원</span>
|
||||
</div>
|
||||
<div class="caption" style="color: var(--color-gray-600); padding-left: 32px;">
|
||||
이벤트 기간 vs 평균 매출 비교
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: var(--spacing-l);">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-s);">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span class="material-icons" style="color: var(--color-primary-main);">people</span>
|
||||
<span class="body-m" style="font-weight: 600;">신규 고객 가치</span>
|
||||
</div>
|
||||
<span class="h4" style="color: var(--color-success);">65,000원</span>
|
||||
</div>
|
||||
<div class="caption" style="color: var(--color-gray-600); padding-left: 32px;">
|
||||
78명 × 예상 LTV (고객 생애 가치)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border-top: 2px solid var(--color-gray-300); padding-top: var(--spacing-m);">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span class="body-l" style="font-weight: 700;">총 수익</span>
|
||||
<span class="h3" style="color: var(--color-success);">245,000원</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-l);">
|
||||
<div>
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s); margin-bottom: var(--spacing-s);">
|
||||
<span class="material-icons" style="color: var(--color-success);">check_circle</span>
|
||||
<span class="body-m" style="font-weight: 600;">손익 분기점</span>
|
||||
</div>
|
||||
<div class="h4" style="color: var(--color-success); margin-bottom: 4px;">달성</div>
|
||||
<div class="caption" style="color: var(--color-gray-600);">이벤트 3일차</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<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 class="h4" style="color: var(--color-info); margin-bottom: 4px;">약 1.5개월</div>
|
||||
<div class="caption" style="color: var(--color-gray-600);">신규 고객 기준</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="trend-title">
|
||||
<h3 id="trend-title" class="body-l" style="font-weight: 600; margin-bottom: var(--spacing-m);">ROI 추이</h3>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="margin-bottom: var(--spacing-m);">
|
||||
<canvas id="roiChart" width="100%" height="200" aria-label="ROI 추이 그래프"></canvas>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--spacing-s); text-align: center;">
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600);">12/1</div>
|
||||
<div class="body-s" style="font-weight: 600; color: var(--color-error);">-20%</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600);">12/10</div>
|
||||
<div class="body-s" style="font-weight: 600; color: var(--color-warning);">150%</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="caption" style="color: var(--color-gray-600);">12/20</div>
|
||||
<div class="body-s" style="font-weight: 600; color: var(--color-success);">245%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="alert alert-info" role="status">
|
||||
<div style="display: flex; align-items: flex-start; gap: var(--spacing-s);">
|
||||
<span class="material-icons">info</span>
|
||||
<div class="body-s">
|
||||
ROI는 5분마다 자동으로 업데이트됩니다. POS 시스템과 연동하여 실시간 매출 데이터를 반영합니다.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav" role="navigation">
|
||||
<button type="button" class="nav-item" onclick="goToHome()">
|
||||
<span class="material-icons">home</span>
|
||||
<span>홈</span>
|
||||
</button>
|
||||
<button type="button" class="nav-item" onclick="goToEvents()">
|
||||
<span class="material-icons">event</span>
|
||||
<span>이벤트</span>
|
||||
</button>
|
||||
<button type="button" class="nav-item active">
|
||||
<span class="material-icons">analytics</span>
|
||||
<span>분석</span>
|
||||
</button>
|
||||
<button type="button" class="nav-item" onclick="goToMy()">
|
||||
<span class="material-icons">person</span>
|
||||
<span>MY</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<script src="js/common.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
let roiChart;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
drawROIChart();
|
||||
startAutoUpdate();
|
||||
});
|
||||
|
||||
function drawROIChart() {
|
||||
const canvas = document.getElementById('roiChart');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const width = canvas.width;
|
||||
const height = canvas.height;
|
||||
|
||||
// Clear canvas
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
// Chart data points
|
||||
const dataPoints = [
|
||||
{ x: 0, y: -20 },
|
||||
{ x: 30, y: 50 },
|
||||
{ x: 50, y: 100 },
|
||||
{ x: 70, y: 150 },
|
||||
{ x: 100, y: 245 }
|
||||
];
|
||||
|
||||
// Scale and translate points
|
||||
const maxY = 300;
|
||||
const minY = -50;
|
||||
const padding = 20;
|
||||
|
||||
const scaleX = (width - 2 * padding) / 100;
|
||||
const scaleY = (height - 2 * padding) / (maxY - minY);
|
||||
|
||||
// Draw axes
|
||||
ctx.strokeStyle = '#E0E0E0';
|
||||
ctx.lineWidth = 1;
|
||||
|
||||
// X axis
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(padding, height - padding);
|
||||
ctx.lineTo(width - padding, height - padding);
|
||||
ctx.stroke();
|
||||
|
||||
// Y axis (zero line)
|
||||
const zeroY = height - padding - (0 - minY) * scaleY;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(padding, zeroY);
|
||||
ctx.lineTo(width - padding, zeroY);
|
||||
ctx.stroke();
|
||||
|
||||
// Draw line
|
||||
ctx.strokeStyle = '#667eea';
|
||||
ctx.lineWidth = 3;
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.lineCap = 'round';
|
||||
|
||||
ctx.beginPath();
|
||||
dataPoints.forEach(function(point, index) {
|
||||
const x = padding + point.x * scaleX;
|
||||
const y = height - padding - (point.y - minY) * scaleY;
|
||||
|
||||
if (index === 0) {
|
||||
ctx.moveTo(x, y);
|
||||
} else {
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
});
|
||||
ctx.stroke();
|
||||
|
||||
// Draw points
|
||||
dataPoints.forEach(function(point) {
|
||||
const x = padding + point.x * scaleX;
|
||||
const y = height - padding - (point.y - minY) * scaleY;
|
||||
|
||||
ctx.fillStyle = point.y < 0 ? '#E31E24' : '#667eea';
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, 4, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
});
|
||||
|
||||
// Fill area under curve
|
||||
ctx.fillStyle = 'rgba(102, 126, 234, 0.1)';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(padding + dataPoints[0].x * scaleX, zeroY);
|
||||
dataPoints.forEach(function(point) {
|
||||
const x = padding + point.x * scaleX;
|
||||
const y = height - padding - (point.y - minY) * scaleY;
|
||||
ctx.lineTo(x, y);
|
||||
});
|
||||
ctx.lineTo(padding + dataPoints[dataPoints.length - 1].x * scaleX, zeroY);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
function startAutoUpdate() {
|
||||
// Auto-update every 5 minutes
|
||||
setInterval(function() {
|
||||
updateROI();
|
||||
}, 300000);
|
||||
}
|
||||
|
||||
function updateROI() {
|
||||
// Simulate ROI update
|
||||
const currentROI = parseInt(document.getElementById('roiValue').textContent);
|
||||
const newROI = currentROI + Math.floor(Math.random() * 10);
|
||||
|
||||
document.getElementById('roiValue').textContent = newROI + '%';
|
||||
|
||||
// Redraw chart
|
||||
drawROIChart();
|
||||
}
|
||||
|
||||
window.goToHome = function() {
|
||||
window.location.href = '21.5-홈.html';
|
||||
};
|
||||
|
||||
window.goToEvents = function() {
|
||||
window.location.href = '26-이벤트목록.html';
|
||||
};
|
||||
|
||||
window.goToMy = function() {
|
||||
window.location.href = '25-마이페이지.html';
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.bottom-nav {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
border-top: 1px solid var(--color-gray-300);
|
||||
display: flex;
|
||||
padding: var(--spacing-xs) 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: var(--spacing-xs);
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-gray-600);
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
color: var(--color-primary-main);
|
||||
}
|
||||
|
||||
.nav-item .material-icons {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.nav-item span:last-child {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#roiChart {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,480 +0,0 @@
|
||||
<!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">
|
||||
<h1 class="app-bar__title">마이페이지</h1>
|
||||
<button class="app-bar__action" aria-label="설정">
|
||||
<span class="material-icons">settings</span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main id="main-content" class="app-content" style="padding-bottom: 80px;">
|
||||
<div class="container">
|
||||
<!-- Profile Section -->
|
||||
<section style="margin-bottom: 24px;">
|
||||
<div class="card" style="padding: 24px;">
|
||||
<div style="display: flex; align-items: center; gap: 16px;">
|
||||
<!-- Profile Image -->
|
||||
<div style="width: 64px; height: 64px; background: linear-gradient(135deg, var(--color-primary-main), #C71F24); border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0;">
|
||||
<span class="material-icons" style="font-size: 36px; color: white;">person</span>
|
||||
</div>
|
||||
|
||||
<!-- Profile Info -->
|
||||
<div style="flex: 1;">
|
||||
<div class="h3" style="margin-bottom: 4px;" id="userName">홍길동</div>
|
||||
<div class="body-s text-muted" id="userEmail">hong@example.com</div>
|
||||
<div class="body-s text-muted" id="userPhone">010-1234-5678</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Button -->
|
||||
<button class="btn btn-secondary btn-sm" onclick="editProfile()">
|
||||
수정
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Store Info Section -->
|
||||
<section style="margin-bottom: 24px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||
<h2 class="h3">매장 정보</h2>
|
||||
<button class="btn btn-text btn-sm" style="color: var(--color-primary-main);" onclick="editStore()">
|
||||
수정
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card" style="padding: 20px;" id="storeInfo">
|
||||
<div style="text-align: center; padding: 20px 0; color: var(--color-gray-400);">
|
||||
<span class="material-icons" style="font-size: 48px; margin-bottom: 12px;">store</span>
|
||||
<p class="body-m text-muted">등록된 매장이 없습니다</p>
|
||||
<button class="btn btn-primary btn-sm" style="margin-top: 12px;" onclick="window.location.href='02-매장정보등록.html'">
|
||||
매장 등록하기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Menu List -->
|
||||
<section style="margin-bottom: 24px;">
|
||||
<h2 class="h3" style="margin-bottom: 12px;">서비스 관리</h2>
|
||||
|
||||
<div class="card" style="padding: 0; overflow: hidden;">
|
||||
<!-- Subscription -->
|
||||
<button class="menu-item" onclick="manageSubscription()">
|
||||
<span class="material-icons" style="color: var(--color-primary-main);">card_membership</span>
|
||||
<span style="flex: 1;">구독 관리</span>
|
||||
<span class="badge badge-primary">무료 체험</span>
|
||||
<span class="material-icons" style="color: var(--color-gray-400);">chevron_right</span>
|
||||
</button>
|
||||
|
||||
<!-- Payment History -->
|
||||
<button class="menu-item">
|
||||
<span class="material-icons" style="color: var(--color-secondary-main);">receipt_long</span>
|
||||
<span style="flex: 1;">결제 내역</span>
|
||||
<span class="material-icons" style="color: var(--color-gray-400);">chevron_right</span>
|
||||
</button>
|
||||
|
||||
<!-- Coupon -->
|
||||
<button class="menu-item">
|
||||
<span class="material-icons" style="color: var(--color-warning);">local_offer</span>
|
||||
<span style="flex: 1;">쿠폰함</span>
|
||||
<span class="badge badge-error">1</span>
|
||||
<span class="material-icons" style="color: var(--color-gray-400);">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Settings -->
|
||||
<section style="margin-bottom: 24px;">
|
||||
<h2 class="h3" style="margin-bottom: 12px;">설정</h2>
|
||||
|
||||
<div class="card" style="padding: 0; overflow: hidden;">
|
||||
<!-- Notifications -->
|
||||
<button class="menu-item">
|
||||
<span class="material-icons" style="color: var(--color-info);">notifications</span>
|
||||
<span style="flex: 1;">알림 설정</span>
|
||||
<span class="material-icons" style="color: var(--color-gray-400);">chevron_right</span>
|
||||
</button>
|
||||
|
||||
<!-- Language -->
|
||||
<button class="menu-item">
|
||||
<span class="material-icons" style="color: var(--color-success);">language</span>
|
||||
<span style="flex: 1;">언어 설정</span>
|
||||
<span class="body-s text-muted">한국어</span>
|
||||
<span class="material-icons" style="color: var(--color-gray-400);">chevron_right</span>
|
||||
</button>
|
||||
|
||||
<!-- Privacy -->
|
||||
<button class="menu-item">
|
||||
<span class="material-icons" style="color: var(--color-gray-600);">privacy_tip</span>
|
||||
<span style="flex: 1;">개인정보 처리방침</span>
|
||||
<span class="material-icons" style="color: var(--color-gray-400);">chevron_right</span>
|
||||
</button>
|
||||
|
||||
<!-- Terms -->
|
||||
<button class="menu-item">
|
||||
<span class="material-icons" style="color: var(--color-gray-600);">description</span>
|
||||
<span style="flex: 1;">서비스 이용약관</span>
|
||||
<span class="material-icons" style="color: var(--color-gray-400);">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Support -->
|
||||
<section style="margin-bottom: 24px;">
|
||||
<h2 class="h3" style="margin-bottom: 12px;">고객 지원</h2>
|
||||
|
||||
<div class="card" style="padding: 0; overflow: hidden;">
|
||||
<!-- FAQ -->
|
||||
<button class="menu-item">
|
||||
<span class="material-icons" style="color: var(--color-secondary-main);">help</span>
|
||||
<span style="flex: 1;">자주 묻는 질문</span>
|
||||
<span class="material-icons" style="color: var(--color-gray-400);">chevron_right</span>
|
||||
</button>
|
||||
|
||||
<!-- Customer Service -->
|
||||
<button class="menu-item">
|
||||
<span class="material-icons" style="color: var(--color-primary-main);">headset_mic</span>
|
||||
<span style="flex: 1;">1:1 문의</span>
|
||||
<span class="material-icons" style="color: var(--color-gray-400);">chevron_right</span>
|
||||
</button>
|
||||
|
||||
<!-- App Version -->
|
||||
<div class="menu-item" style="cursor: default;">
|
||||
<span class="material-icons" style="color: var(--color-gray-400);">info</span>
|
||||
<span style="flex: 1;">앱 버전</span>
|
||||
<span class="body-s text-muted">v1.0.0</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Logout -->
|
||||
<section style="margin-bottom: 32px;">
|
||||
<button
|
||||
class="btn btn-secondary btn-lg btn-block"
|
||||
onclick="logout()"
|
||||
style="margin-bottom: 12px;"
|
||||
>
|
||||
로그아웃
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-text btn-sm btn-block"
|
||||
style="color: var(--color-error);"
|
||||
onclick="confirmWithdraw()"
|
||||
>
|
||||
회원 탈퇴
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav">
|
||||
<button class="bottom-nav__item" data-page="home" onclick="window.location.href='21.5-홈.html'">
|
||||
<span class="material-icons">home</span>
|
||||
<span>홈</span>
|
||||
</button>
|
||||
<button class="bottom-nav__item" data-page="event" onclick="window.location.href='03-이벤트목적선택.html'">
|
||||
<span class="material-icons">campaign</span>
|
||||
<span>이벤트</span>
|
||||
</button>
|
||||
<button class="bottom-nav__item" data-page="analytics" onclick="window.location.href='21-실시간대시보드.html'">
|
||||
<span class="material-icons">analytics</span>
|
||||
<span>분석</span>
|
||||
</button>
|
||||
<button class="bottom-nav__item active" data-page="my">
|
||||
<span class="material-icons">person</span>
|
||||
<span>MY</span>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="js/common.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// 사용자 정보 로드
|
||||
const user = window.AppState.user;
|
||||
const store = window.AppState.store;
|
||||
|
||||
if (user) {
|
||||
document.getElementById('userName').textContent = user.name || '사용자';
|
||||
if (user.email) {
|
||||
document.getElementById('userEmail').textContent = user.email;
|
||||
}
|
||||
if (user.phone) {
|
||||
document.getElementById('userPhone').textContent = user.phone;
|
||||
}
|
||||
}
|
||||
|
||||
// 매장 정보 표시
|
||||
if (store) {
|
||||
const storeInfo = document.getElementById('storeInfo');
|
||||
storeInfo.innerHTML = `
|
||||
<div style="margin-bottom: 16px;">
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 8px;">${store.name}</div>
|
||||
<div class="body-s text-muted" style="margin-bottom: 4px;">
|
||||
<span class="material-icons" style="font-size: 14px; vertical-align: middle;">business</span>
|
||||
${store.businessTypeName || store.businessType}
|
||||
</div>
|
||||
<div class="body-s text-muted" style="margin-bottom: 4px;">
|
||||
<span class="material-icons" style="font-size: 14px; vertical-align: middle;">location_on</span>
|
||||
${store.address}
|
||||
</div>
|
||||
<div class="body-s text-muted">
|
||||
<span class="material-icons" style="font-size: 14px; vertical-align: middle;">credit_card</span>
|
||||
${store.businessNumber}
|
||||
</div>
|
||||
</div>
|
||||
${store.menuItems && store.menuItems.length > 0 ? `
|
||||
<div style="padding-top: 16px; border-top: 1px solid var(--color-gray-200);">
|
||||
<div class="body-s text-muted" style="margin-bottom: 8px;">대표 메뉴</div>
|
||||
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
|
||||
${store.menuItems.slice(0, 3).map(item => `
|
||||
<span class="badge" style="background: var(--color-gray-100); color: var(--color-gray-700);">
|
||||
${item.name}
|
||||
</span>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
`;
|
||||
}
|
||||
|
||||
// 프로필 수정
|
||||
window.editProfile = function() {
|
||||
Modal.show({
|
||||
title: '프로필 수정',
|
||||
body: `
|
||||
<div class="form-group">
|
||||
<label class="form-label">이름</label>
|
||||
<input
|
||||
type="text"
|
||||
id="editName"
|
||||
class="form-input"
|
||||
value="${user?.name || ''}"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">이메일</label>
|
||||
<input
|
||||
type="email"
|
||||
id="editEmail"
|
||||
class="form-input"
|
||||
value="${user?.email || ''}"
|
||||
${user?.authType === 'kt' ? 'disabled' : ''}
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">전화번호</label>
|
||||
<input
|
||||
type="tel"
|
||||
id="editPhone"
|
||||
class="form-input"
|
||||
value="${user?.phone || ''}"
|
||||
${user?.authType === 'kt' ? 'disabled' : ''}
|
||||
>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '저장',
|
||||
onConfirm: function() {
|
||||
const name = document.getElementById('editName').value;
|
||||
const email = document.getElementById('editEmail').value;
|
||||
const phone = document.getElementById('editPhone').value;
|
||||
|
||||
if (user) {
|
||||
user.name = name;
|
||||
if (user.authType !== 'kt') {
|
||||
user.email = email;
|
||||
user.phone = phone;
|
||||
}
|
||||
window.AppState.save();
|
||||
|
||||
document.getElementById('userName').textContent = name;
|
||||
if (email) document.getElementById('userEmail').textContent = email;
|
||||
if (phone) document.getElementById('userPhone').textContent = phone;
|
||||
|
||||
Toast.success('프로필이 수정되었습니다.');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 매장 정보 수정
|
||||
window.editStore = function() {
|
||||
if (!store) {
|
||||
Toast.info('먼저 매장을 등록해주세요.');
|
||||
return;
|
||||
}
|
||||
window.location.href = '02-매장정보등록.html';
|
||||
};
|
||||
|
||||
// 구독 관리
|
||||
window.manageSubscription = function() {
|
||||
Modal.show({
|
||||
title: '구독 관리',
|
||||
body: `
|
||||
<div style="text-align: center; padding: 20px;">
|
||||
<div class="h2" style="color: var(--color-primary-main); margin-bottom: 16px;">무료 체험 중</div>
|
||||
<p class="body-m text-muted" style="margin-bottom: 24px;">
|
||||
첫 이벤트 생성 시 무료 체험 쿠폰을 사용할 수 있습니다.
|
||||
</p>
|
||||
<div class="card" style="padding: 16px; background: var(--color-gray-50);">
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 12px;">프리미엄 플랜</div>
|
||||
<div class="h2" style="color: var(--color-primary-main); margin-bottom: 8px;">₩29,000 <span class="body-m text-muted">/ 월</span></div>
|
||||
<ul style="text-align: left; margin-top: 16px; padding-left: 20px;">
|
||||
<li class="body-s" style="margin-bottom: 8px;">무제한 이벤트 생성</li>
|
||||
<li class="body-s" style="margin-bottom: 8px;">AI 고급 기능 사용</li>
|
||||
<li class="body-s" style="margin-bottom: 8px;">우선 고객 지원</li>
|
||||
<li class="body-s">상세 분석 리포트</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '업그레이드',
|
||||
onConfirm: function() {
|
||||
Toast.info('결제 기능은 준비 중입니다.');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 로그아웃
|
||||
window.logout = function() {
|
||||
Modal.confirm(
|
||||
'로그아웃',
|
||||
'정말 로그아웃 하시겠습니까?',
|
||||
function() {
|
||||
Loading.show('로그아웃 중...');
|
||||
setTimeout(() => {
|
||||
window.AppState.clear();
|
||||
Loading.hide();
|
||||
Toast.success('로그아웃되었습니다.');
|
||||
setTimeout(() => {
|
||||
window.location.href = '00-로그인.html';
|
||||
}, 500);
|
||||
}, 1000);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// 회원 탈퇴
|
||||
window.confirmWithdraw = function() {
|
||||
Modal.show({
|
||||
title: '⚠️ 회원 탈퇴',
|
||||
body: `
|
||||
<div style="padding: 16px 0;">
|
||||
<p class="body-m" style="margin-bottom: 16px;">
|
||||
회원 탈퇴 시 모든 데이터가 삭제되며 복구할 수 없습니다.
|
||||
</p>
|
||||
<div class="card" style="padding: 16px; background: #FFF5F5; border: 1px solid var(--color-error);">
|
||||
<div class="body-s" style="color: var(--color-error); line-height: 1.6;">
|
||||
• 진행 중인 이벤트 정보<br>
|
||||
• 매장 정보 및 통계<br>
|
||||
• 결제 내역 및 쿠폰<br>
|
||||
• 모든 분석 데이터
|
||||
</div>
|
||||
</div>
|
||||
<p class="body-s text-muted" style="margin-top: 16px;">
|
||||
정말 탈퇴하시겠습니까?
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
confirmText: '탈퇴하기',
|
||||
cancelText: '취소',
|
||||
onConfirm: function() {
|
||||
Loading.show('회원 탈퇴 처리 중...');
|
||||
setTimeout(() => {
|
||||
window.AppState.clear();
|
||||
Loading.hide();
|
||||
Toast.success('회원 탈퇴가 완료되었습니다.');
|
||||
setTimeout(() => {
|
||||
window.location.href = '00-로그인.html';
|
||||
}, 500);
|
||||
}, 1500);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Bottom Navigation 활성화
|
||||
Navigation.updateBottomNav('my');
|
||||
|
||||
console.log('마이페이지 로드 완료');
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px 20px;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.menu-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background-color: var(--color-gray-50);
|
||||
}
|
||||
|
||||
.menu-item:active {
|
||||
background-color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.badge-primary {
|
||||
background: var(--color-primary-light);
|
||||
color: var(--color-primary-main);
|
||||
}
|
||||
|
||||
.badge-error {
|
||||
background: var(--color-error);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background: var(--color-success);
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,385 +0,0 @@
|
||||
<!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: 80px;">
|
||||
<section class="section">
|
||||
<h2 class="h3">📊 종합 분석 리포트</h2>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="body-s" style="color: var(--color-gray-600); margin-bottom: 4px;">이벤트</div>
|
||||
<div class="body-l" style="font-weight: 600; margin-bottom: var(--spacing-m);" id="eventName">연말 대박 이벤트</div>
|
||||
|
||||
<div class="body-s" style="color: var(--color-gray-600); margin-bottom: 4px;">기간</div>
|
||||
<div class="body-m" id="eventPeriod">2025-12-01 ~ 2025-12-31</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="preview-title">
|
||||
<h3 id="preview-title" class="body-l" style="font-weight: 600; margin-bottom: var(--spacing-m);">리포트 미리보기</h3>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<button type="button" class="report-preview" onclick="previewReport()" aria-label="리포트 미리보기">
|
||||
<div style="aspect-ratio: 1/1.414; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); border-radius: var(--radius-md); display: flex; flex-direction: column; align-items: center; justify-content: center; margin-bottom: var(--spacing-m);">
|
||||
<span class="material-icons" style="font-size: 64px; color: var(--color-gray-500); margin-bottom: var(--spacing-s);">description</span>
|
||||
<div class="body-s" style="color: var(--color-gray-600);">PDF 썸네일</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div style="display: flex; justify-content: center; gap: var(--spacing-l); padding: var(--spacing-m); background: var(--color-gray-100); border-radius: var(--radius-md);">
|
||||
<div style="text-align: center;">
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">페이지</div>
|
||||
<div class="body-l" style="font-weight: 600;">10페이지</div>
|
||||
</div>
|
||||
<div style="width: 1px; background: var(--color-gray-300);"></div>
|
||||
<div style="text-align: center;">
|
||||
<div class="caption" style="color: var(--color-gray-600); margin-bottom: 4px;">파일 크기</div>
|
||||
<div class="body-l" style="font-weight: 600;">5.2MB</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="content-title">
|
||||
<h3 id="content-title" class="body-l" style="font-weight: 600; margin-bottom: var(--spacing-m);">포함 내용</h3>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="display: flex; flex-direction: column; gap: var(--spacing-s);">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 20px;">check_circle</span>
|
||||
<span class="body-m">이벤트 개요</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 20px;">check_circle</span>
|
||||
<span class="body-m">참여 통계</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 20px;">check_circle</span>
|
||||
<span class="body-m">노출 통계</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 20px;">check_circle</span>
|
||||
<span class="body-m">매출 분석</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 20px;">check_circle</span>
|
||||
<span class="body-m">ROI 분석</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 20px;">check_circle</span>
|
||||
<span class="body-m">채널별 성과 비교</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 20px;">check_circle</span>
|
||||
<span class="body-m">업종 평균 벤치마킹</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-s);">
|
||||
<span class="material-icons" style="color: var(--color-success); font-size: 20px;">check_circle</span>
|
||||
<span class="body-m">그래프 시각화</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<button type="button" class="btn btn-primary btn-lg" onclick="downloadPDF()" style="width: 100%; margin-bottom: var(--spacing-s);">
|
||||
<span class="material-icons">download</span>
|
||||
<span>PDF 다운로드</span>
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-outline btn-lg" onclick="sendEmail()" style="width: 100%;">
|
||||
<span class="material-icons">email</span>
|
||||
<span>이메일로 받기</span>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="history-title">
|
||||
<h3 id="history-title" class="body-l" style="font-weight: 600; margin-bottom: var(--spacing-m);">생성 이력</h3>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-m);">
|
||||
<div>
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">최종 리포트</div>
|
||||
<div class="caption" style="color: var(--color-gray-600);">2025-12-31 16:00</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-text btn-sm" onclick="downloadHistory(0)">
|
||||
<span class="material-icons">download</span>
|
||||
<span>다운로드</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-m);">
|
||||
<div>
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">중간 리포트</div>
|
||||
<div class="caption" style="color: var(--color-gray-600);">2025-12-15 14:30</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-text btn-sm" onclick="downloadHistory(1)">
|
||||
<span class="material-icons">download</span>
|
||||
<span>다운로드</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">초기 리포트</div>
|
||||
<div class="caption" style="color: var(--color-gray-600);">2025-12-01 10:00</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-text btn-sm" onclick="downloadHistory(2)">
|
||||
<span class="material-icons">download</span>
|
||||
<span>다운로드</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="alert alert-info" role="status">
|
||||
<div style="display: flex; align-items: flex-start; gap: var(--spacing-s);">
|
||||
<span class="material-icons">info</span>
|
||||
<div class="body-s">
|
||||
리포트 생성은 최대 30초가 소요됩니다. PDF 파일은 최대 10MB까지 최적화됩니다.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Email Modal -->
|
||||
<div id="emailModal" 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="closeEmailModal()">
|
||||
<span class="material-icons">close</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="emailInput" class="form-label">받는 사람</label>
|
||||
<input type="email" id="emailInput" class="input-field" placeholder="example@email.com" value="" aria-label="이메일 주소">
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info" role="status">
|
||||
<div style="display: flex; align-items: flex-start; gap: var(--spacing-s);">
|
||||
<span class="material-icons">info</span>
|
||||
<div class="body-s">
|
||||
등록된 이메일 주소로 리포트가 발송됩니다.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline btn-lg" onclick="closeEmailModal()" style="flex: 1;">
|
||||
취소
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary btn-lg" onclick="confirmSendEmail()" style="flex: 1;">
|
||||
발송
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav" role="navigation">
|
||||
<button type="button" class="nav-item" onclick="goToHome()">
|
||||
<span class="material-icons">home</span>
|
||||
<span>홈</span>
|
||||
</button>
|
||||
<button type="button" class="nav-item" onclick="goToEvents()">
|
||||
<span class="material-icons">event</span>
|
||||
<span>이벤트</span>
|
||||
</button>
|
||||
<button type="button" class="nav-item active">
|
||||
<span class="material-icons">analytics</span>
|
||||
<span>분석</span>
|
||||
</button>
|
||||
<button type="button" class="nav-item" onclick="goToMy()">
|
||||
<span class="material-icons">person</span>
|
||||
<span>MY</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<script src="js/common.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const eventData = AppState.currentEvent || {};
|
||||
const storeData = AppState.store || {};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadEventInfo();
|
||||
loadUserEmail();
|
||||
});
|
||||
|
||||
function loadEventInfo() {
|
||||
if (eventData.name) {
|
||||
document.getElementById('eventName').textContent = eventData.name;
|
||||
}
|
||||
|
||||
if (eventData.startDate && eventData.endDate) {
|
||||
document.getElementById('eventPeriod').textContent =
|
||||
eventData.startDate + ' ~ ' + eventData.endDate;
|
||||
}
|
||||
}
|
||||
|
||||
function loadUserEmail() {
|
||||
const user = AppState.user || {};
|
||||
if (user.email) {
|
||||
document.getElementById('emailInput').value = user.email;
|
||||
}
|
||||
}
|
||||
|
||||
window.previewReport = function() {
|
||||
Toast.show('📄 리포트 미리보기');
|
||||
};
|
||||
|
||||
window.downloadPDF = function() {
|
||||
Loading.show('리포트 생성 중...');
|
||||
|
||||
// Simulate PDF generation (30 seconds max)
|
||||
setTimeout(function() {
|
||||
Loading.hide();
|
||||
Toast.success('📥 분석 리포트가 다운로드되었습니다!');
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
window.sendEmail = function() {
|
||||
Modal.open(document.getElementById('emailModal'));
|
||||
};
|
||||
|
||||
window.closeEmailModal = function() {
|
||||
Modal.close(document.getElementById('emailModal'));
|
||||
};
|
||||
|
||||
window.confirmSendEmail = function() {
|
||||
const email = document.getElementById('emailInput').value;
|
||||
|
||||
if (!email) {
|
||||
Toast.error('이메일 주소를 입력해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Basic email validation
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
Toast.error('올바른 이메일 주소를 입력해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
Modal.close(document.getElementById('emailModal'));
|
||||
Loading.show('이메일 발송 중...');
|
||||
|
||||
setTimeout(function() {
|
||||
Loading.hide();
|
||||
Toast.success('✉️ ' + email + '로 리포트가 발송되었습니다!');
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
window.downloadHistory = function(index) {
|
||||
const dates = ['2025-12-31 16:00', '2025-12-15 14:30', '2025-12-01 10:00'];
|
||||
Loading.show('리포트 다운로드 중...');
|
||||
|
||||
setTimeout(function() {
|
||||
Loading.hide();
|
||||
Toast.success('📥 ' + dates[index] + ' 리포트가 다운로드되었습니다!');
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
window.goToHome = function() {
|
||||
window.location.href = '21.5-홈.html';
|
||||
};
|
||||
|
||||
window.goToEvents = function() {
|
||||
window.location.href = '26-이벤트목록.html';
|
||||
};
|
||||
|
||||
window.goToMy = function() {
|
||||
window.location.href = '25-마이페이지.html';
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.report-preview {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.report-preview:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.bottom-nav {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
border-top: 1px solid var(--color-gray-300);
|
||||
display: flex;
|
||||
padding: var(--spacing-xs) 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: var(--spacing-xs);
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-gray-600);
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
color: var(--color-primary-main);
|
||||
}
|
||||
|
||||
.nav-item .material-icons {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.nav-item span:last-child {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,365 +0,0 @@
|
||||
<!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">
|
||||
<h1 class="app-bar__title">이벤트 목록</h1>
|
||||
<button class="app-bar__action" aria-label="검색">
|
||||
<span class="material-icons">search</span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main id="main-content" class="app-content" style="padding-bottom: 100px;">
|
||||
<div class="container">
|
||||
<!-- Filter Tabs -->
|
||||
<section style="margin-bottom: 24px;">
|
||||
<div style="display: flex; gap: 8px; overflow-x: auto; padding: 4px 0;">
|
||||
<button class="filter-chip active" onclick="filterEvents('all')">
|
||||
전체 <span class="badge" style="background: var(--color-primary-main); color: white; margin-left: 4px;">0</span>
|
||||
</button>
|
||||
<button class="filter-chip" onclick="filterEvents('active')">
|
||||
진행중 <span class="badge" style="background: var(--color-success); color: white; margin-left: 4px;">0</span>
|
||||
</button>
|
||||
<button class="filter-chip" onclick="filterEvents('scheduled')">
|
||||
예정 <span class="badge" style="background: var(--color-info); color: white; margin-left: 4px;">0</span>
|
||||
</button>
|
||||
<button class="filter-chip" onclick="filterEvents('completed')">
|
||||
종료 <span class="badge" style="background: var(--color-gray-400); color: white; margin-left: 4px;">0</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Event List -->
|
||||
<section id="eventList">
|
||||
<!-- Empty State -->
|
||||
<div class="card" style="padding: 60px 24px; text-align: center;">
|
||||
<div style="width: 80px; height: 80px; background: var(--color-gray-100); border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 24px;">
|
||||
<span class="material-icons" style="font-size: 48px; color: var(--color-gray-400);">event_busy</span>
|
||||
</div>
|
||||
<div class="h3" style="margin-bottom: 12px;">진행 중인 이벤트가 없습니다</div>
|
||||
<p class="body-m text-muted" style="margin-bottom: 24px;">
|
||||
AI가 자동으로 이벤트를 기획해드립니다.<br>
|
||||
첫 이벤트를 만들어보세요!
|
||||
</p>
|
||||
<button
|
||||
class="btn btn-primary btn-md"
|
||||
onclick="window.location.href='03-이벤트목적선택.html'"
|
||||
>
|
||||
<span class="material-icons" style="font-size: 20px; margin-right: 4px;">add_circle</span>
|
||||
새 이벤트 만들기
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Example Event Cards (주석 처리됨 - 실제 이벤트가 있을 때 표시) -->
|
||||
<!--
|
||||
<div class="card" style="padding: 20px; margin-bottom: 16px; cursor: pointer;" onclick="viewEvent(1)">
|
||||
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 12px;">
|
||||
<div style="flex: 1;">
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">설날 특별 할인 이벤트</div>
|
||||
<div class="body-s text-muted">2025.01.20 ~ 2025.02.10</div>
|
||||
</div>
|
||||
<span class="badge badge-success">진행중</span>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 16px; padding: 16px; background: var(--color-gray-50); border-radius: 8px; margin-bottom: 16px;">
|
||||
<div style="flex: 1; text-align: center;">
|
||||
<div class="body-s text-muted" style="margin-bottom: 4px;">참여자</div>
|
||||
<div class="body-l" style="font-weight: 600;">1,234</div>
|
||||
</div>
|
||||
<div style="flex: 1; text-align: center; border-left: 1px solid var(--color-gray-200);">
|
||||
<div class="body-s text-muted" style="margin-bottom: 4px;">조회수</div>
|
||||
<div class="body-l" style="font-weight: 600;">5,678</div>
|
||||
</div>
|
||||
<div style="flex: 1; text-align: center; border-left: 1px solid var(--color-gray-200);">
|
||||
<div class="body-s text-muted" style="margin-bottom: 4px;">ROI</div>
|
||||
<div class="body-l" style="font-weight: 600; color: var(--color-success);">+245%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<button class="btn btn-secondary btn-sm" style="flex: 1;" onclick="event.stopPropagation(); editEvent(1)">
|
||||
수정
|
||||
</button>
|
||||
<button class="btn btn-text btn-sm" onclick="event.stopPropagation(); viewAnalytics(1)">
|
||||
분석 보기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Floating Action Button -->
|
||||
<button
|
||||
class="fab"
|
||||
onclick="window.location.href='03-이벤트목적선택.html'"
|
||||
aria-label="새 이벤트 만들기"
|
||||
>
|
||||
<span class="material-icons">add</span>
|
||||
</button>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav">
|
||||
<button class="bottom-nav__item" data-page="home" onclick="window.location.href='21.5-홈.html'">
|
||||
<span class="material-icons">home</span>
|
||||
<span>홈</span>
|
||||
</button>
|
||||
<button class="bottom-nav__item active" data-page="event">
|
||||
<span class="material-icons">campaign</span>
|
||||
<span>이벤트</span>
|
||||
</button>
|
||||
<button class="bottom-nav__item" data-page="analytics" onclick="window.location.href='21-실시간대시보드.html'">
|
||||
<span class="material-icons">analytics</span>
|
||||
<span>분석</span>
|
||||
</button>
|
||||
<button class="bottom-nav__item" data-page="my" onclick="window.location.href='25-마이페이지.html'">
|
||||
<span class="material-icons">person</span>
|
||||
<span>MY</span>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="js/common.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
let currentFilter = 'all';
|
||||
let events = []; // 실제로는 서버에서 가져옴
|
||||
|
||||
// 이벤트 필터링
|
||||
window.filterEvents = function(filter) {
|
||||
currentFilter = filter;
|
||||
|
||||
// 필터 칩 활성화
|
||||
document.querySelectorAll('.filter-chip').forEach(chip => {
|
||||
chip.classList.remove('active');
|
||||
});
|
||||
event.target.closest('.filter-chip').classList.add('active');
|
||||
|
||||
// 이벤트 목록 다시 렌더링
|
||||
renderEventList();
|
||||
};
|
||||
|
||||
// 이벤트 목록 렌더링
|
||||
function renderEventList() {
|
||||
const eventList = document.getElementById('eventList');
|
||||
|
||||
// 필터링된 이벤트 가져오기
|
||||
let filteredEvents = events;
|
||||
if (currentFilter !== 'all') {
|
||||
filteredEvents = events.filter(e => e.status === currentFilter);
|
||||
}
|
||||
|
||||
// 이벤트가 없을 경우
|
||||
if (filteredEvents.length === 0) {
|
||||
eventList.innerHTML = `
|
||||
<div class="card" style="padding: 60px 24px; text-align: center;">
|
||||
<div style="width: 80px; height: 80px; background: var(--color-gray-100); border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 24px;">
|
||||
<span class="material-icons" style="font-size: 48px; color: var(--color-gray-400);">event_busy</span>
|
||||
</div>
|
||||
<div class="h3" style="margin-bottom: 12px;">
|
||||
${currentFilter === 'all' ? '진행 중인 이벤트가 없습니다' : `${getFilterName(currentFilter)} 이벤트가 없습니다`}
|
||||
</div>
|
||||
<p class="body-m text-muted" style="margin-bottom: 24px;">
|
||||
AI가 자동으로 이벤트를 기획해드립니다.<br>
|
||||
첫 이벤트를 만들어보세요!
|
||||
</p>
|
||||
<button
|
||||
class="btn btn-primary btn-md"
|
||||
onclick="window.location.href='03-이벤트목적선택.html'"
|
||||
>
|
||||
<span class="material-icons" style="font-size: 20px; margin-right: 4px;">add_circle</span>
|
||||
새 이벤트 만들기
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
// 이벤트 카드 렌더링
|
||||
eventList.innerHTML = filteredEvents.map(event => `
|
||||
<div class="card" style="padding: 20px; margin-bottom: 16px; cursor: pointer;" onclick="viewEvent('${event.id}')">
|
||||
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 12px;">
|
||||
<div style="flex: 1;">
|
||||
<div class="body-m" style="font-weight: 600; margin-bottom: 4px;">${event.title}</div>
|
||||
<div class="body-s text-muted">${event.startDate} ~ ${event.endDate}</div>
|
||||
</div>
|
||||
<span class="badge ${getStatusBadgeClass(event.status)}">${getStatusText(event.status)}</span>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 16px; padding: 16px; background: var(--color-gray-50); border-radius: 8px; margin-bottom: 16px;">
|
||||
<div style="flex: 1; text-align: center;">
|
||||
<div class="body-s text-muted" style="margin-bottom: 4px;">참여자</div>
|
||||
<div class="body-l" style="font-weight: 600;">${Utils.formatNumber(event.participants)}</div>
|
||||
</div>
|
||||
<div style="flex: 1; text-align: center; border-left: 1px solid var(--color-gray-200);">
|
||||
<div class="body-s text-muted" style="margin-bottom: 4px;">조회수</div>
|
||||
<div class="body-l" style="font-weight: 600;">${Utils.formatNumber(event.views)}</div>
|
||||
</div>
|
||||
<div style="flex: 1; text-align: center; border-left: 1px solid var(--color-gray-200);">
|
||||
<div class="body-s text-muted" style="margin-bottom: 4px;">ROI</div>
|
||||
<div class="body-l" style="font-weight: 600; color: var(--color-success);">+${event.roi}%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<button class="btn btn-secondary btn-sm" style="flex: 1;" onclick="event.stopPropagation(); editEvent('${event.id}')">
|
||||
수정
|
||||
</button>
|
||||
<button class="btn btn-text btn-sm" onclick="event.stopPropagation(); viewAnalytics('${event.id}')">
|
||||
분석 보기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// 필터 이름 반환
|
||||
function getFilterName(filter) {
|
||||
const names = {
|
||||
active: '진행중',
|
||||
scheduled: '예정',
|
||||
completed: '종료'
|
||||
};
|
||||
return names[filter] || '전체';
|
||||
}
|
||||
|
||||
// 상태 텍스트 반환
|
||||
function getStatusText(status) {
|
||||
const texts = {
|
||||
active: '진행중',
|
||||
scheduled: '예정',
|
||||
completed: '종료'
|
||||
};
|
||||
return texts[status] || status;
|
||||
}
|
||||
|
||||
// 상태 배지 클래스 반환
|
||||
function getStatusBadgeClass(status) {
|
||||
const classes = {
|
||||
active: 'badge-success',
|
||||
scheduled: 'badge-info',
|
||||
completed: 'badge-gray'
|
||||
};
|
||||
return classes[status] || '';
|
||||
}
|
||||
|
||||
// 이벤트 상세 보기
|
||||
window.viewEvent = function(eventId) {
|
||||
Toast.info(`이벤트 상세 페이지로 이동 (ID: ${eventId})`);
|
||||
// 실제로는 이벤트 상세 페이지로 이동
|
||||
// window.location.href = `이벤트상세.html?id=${eventId}`;
|
||||
};
|
||||
|
||||
// 이벤트 수정
|
||||
window.editEvent = function(eventId) {
|
||||
Toast.info(`이벤트 수정 (ID: ${eventId})`);
|
||||
// 실제로는 이벤트 수정 페이지로 이동
|
||||
};
|
||||
|
||||
// 분석 보기
|
||||
window.viewAnalytics = function(eventId) {
|
||||
window.location.href = '21-실시간대시보드.html';
|
||||
};
|
||||
|
||||
// Bottom Navigation 활성화
|
||||
Navigation.updateBottomNav('event');
|
||||
|
||||
// 초기화
|
||||
renderEventList();
|
||||
console.log('이벤트 목록 페이지 로드 완료');
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.filter-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--color-gray-300);
|
||||
background: white;
|
||||
color: var(--color-gray-700);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-chip:hover {
|
||||
background: var(--color-gray-50);
|
||||
}
|
||||
|
||||
.filter-chip.active {
|
||||
background: var(--color-primary-main);
|
||||
color: white;
|
||||
border-color: var(--color-primary-main);
|
||||
}
|
||||
|
||||
.fab {
|
||||
position: fixed;
|
||||
bottom: 90px;
|
||||
right: 20px;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, var(--color-primary-main), #C71F24);
|
||||
color: white;
|
||||
border: none;
|
||||
box-shadow: 0 4px 12px rgba(227, 30, 36, 0.4);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.fab:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 6px 16px rgba(227, 30, 36, 0.5);
|
||||
}
|
||||
|
||||
.fab:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.fab .material-icons {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.badge-info {
|
||||
background: var(--color-info);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-gray {
|
||||
background: var(--color-gray-400);
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,888 +0,0 @@
|
||||
/**
|
||||
* 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; /* 뒤로가기 버튼과 균형 */
|
||||
}
|
||||
|
||||
.app-bar__action {
|
||||
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__action:hover {
|
||||
background-color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
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;
|
||||
}
|
||||
}
|
||||
555
TEMP_BACKUP/prototype/backup/js/common.js
vendored
@ -1,555 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 사업자번호 필드 (name이 businessNumber인 경우도 처리)
|
||||
if ((input.name === 'business_number' || input.name === 'businessNumber') && 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);
|
||||
}
|
||||
} 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');
|
||||
|
||||
})();
|
||||
@ -1,498 +0,0 @@
|
||||
<!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;
|
||||
console.log('선택된 목적:', 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;
|
||||
nextBtn.style.opacity = '1';
|
||||
nextBtn.style.cursor = 'pointer';
|
||||
console.log('다음 버튼 활성화됨');
|
||||
|
||||
// 선택 완료 피드백
|
||||
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-1-AI이벤트유형추천.html';
|
||||
}, 800);
|
||||
});
|
||||
|
||||
// 닫기 확인
|
||||
window.confirmClose = function() {
|
||||
Modal.confirm(
|
||||
'이벤트 기획 종료',
|
||||
'현재까지 입력한 내용이 저장되지 않습니다. 정말 종료하시겠습니까?',
|
||||
function() {
|
||||
window.location.href = '21.5-홈.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>
|
||||
@ -1,478 +0,0 @@
|
||||
/**
|
||||
* KT AI 기반 소상공인 이벤트 자동 생성 서비스
|
||||
* Common Styles (전역 스타일)
|
||||
*
|
||||
* 참조: design/uiux/style-guide.md
|
||||
* 작성일: 2025-01-20
|
||||
*/
|
||||
|
||||
/* ========================================
|
||||
Global Styles
|
||||
======================================== */
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
font-size: var(--font-size-body-medium);
|
||||
font-weight: var(--font-weight-regular);
|
||||
line-height: var(--line-height-body-medium);
|
||||
color: var(--color-black);
|
||||
background-color: var(--color-white);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Typography
|
||||
======================================== */
|
||||
|
||||
/* Display */
|
||||
.display {
|
||||
font-size: var(--font-size-display);
|
||||
font-weight: var(--font-weight-bold);
|
||||
line-height: var(--line-height-display);
|
||||
letter-spacing: var(--letter-spacing-display);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
/* H1 */
|
||||
h1, .h1 {
|
||||
font-size: var(--font-size-h1);
|
||||
font-weight: var(--font-weight-bold);
|
||||
line-height: var(--line-height-h1);
|
||||
letter-spacing: var(--letter-spacing-h1);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
/* H2 */
|
||||
h2, .h2 {
|
||||
font-size: var(--font-size-h2);
|
||||
font-weight: var(--font-weight-bold);
|
||||
line-height: var(--line-height-h2);
|
||||
letter-spacing: var(--letter-spacing-h2);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
/* H3 */
|
||||
h3, .h3 {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
line-height: var(--line-height-h3);
|
||||
letter-spacing: var(--letter-spacing-h3);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
/* Body Large */
|
||||
.body-large {
|
||||
font-size: var(--font-size-body-large);
|
||||
font-weight: var(--font-weight-regular);
|
||||
line-height: var(--line-height-body-large);
|
||||
letter-spacing: var(--letter-spacing-body);
|
||||
}
|
||||
|
||||
/* Body Medium (기본) */
|
||||
.body-medium {
|
||||
font-size: var(--font-size-body-medium);
|
||||
font-weight: var(--font-weight-regular);
|
||||
line-height: var(--line-height-body-medium);
|
||||
letter-spacing: var(--letter-spacing-body);
|
||||
}
|
||||
|
||||
/* Body Small */
|
||||
.body-small {
|
||||
font-size: var(--font-size-body-small);
|
||||
font-weight: var(--font-weight-regular);
|
||||
line-height: var(--line-height-body-small);
|
||||
letter-spacing: var(--letter-spacing-body);
|
||||
}
|
||||
|
||||
/* Font Weights */
|
||||
.font-regular { font-weight: var(--font-weight-regular); }
|
||||
.font-medium { font-weight: var(--font-weight-medium); }
|
||||
.font-semibold { font-weight: var(--font-weight-semibold); }
|
||||
.font-bold { font-weight: var(--font-weight-bold); }
|
||||
|
||||
/* ========================================
|
||||
Text Colors
|
||||
======================================== */
|
||||
|
||||
.text-primary { color: var(--color-primary); }
|
||||
.text-secondary { color: var(--color-secondary); }
|
||||
.text-black { color: var(--color-black); }
|
||||
.text-gray-700 { color: var(--color-gray-700); }
|
||||
.text-gray-500 { color: var(--color-gray-500); }
|
||||
.text-white { color: var(--color-white); }
|
||||
.text-success { color: var(--color-success); }
|
||||
.text-warning { color: var(--color-warning); }
|
||||
.text-error { color: var(--color-error); }
|
||||
.text-info { color: var(--color-info); }
|
||||
|
||||
/* ========================================
|
||||
Background Colors
|
||||
======================================== */
|
||||
|
||||
.bg-white { background-color: var(--color-white); }
|
||||
.bg-gray-100 { background-color: var(--color-gray-100); }
|
||||
.bg-gray-300 { background-color: var(--color-gray-300); }
|
||||
.bg-primary { background-color: var(--color-primary); }
|
||||
.bg-secondary { background-color: var(--color-secondary); }
|
||||
.bg-success { background-color: var(--color-success); }
|
||||
.bg-warning { background-color: var(--color-warning); }
|
||||
.bg-error { background-color: var(--color-error); }
|
||||
.bg-info { background-color: var(--color-info); }
|
||||
|
||||
/* Gradient Backgrounds */
|
||||
.bg-gradient-primary { background: var(--gradient-primary); }
|
||||
.bg-gradient-secondary { background: var(--gradient-secondary); }
|
||||
|
||||
/* ========================================
|
||||
Links
|
||||
======================================== */
|
||||
|
||||
a {
|
||||
color: var(--color-primary);
|
||||
transition: color var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--color-primary-light);
|
||||
}
|
||||
|
||||
a:active {
|
||||
color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Focus States (접근성)
|
||||
======================================== */
|
||||
|
||||
*:focus-visible {
|
||||
outline: 2px solid var(--color-secondary);
|
||||
outline-offset: 2px;
|
||||
border-radius: var(--radius-s);
|
||||
}
|
||||
|
||||
button:focus-visible,
|
||||
a:focus-visible {
|
||||
outline: 2px solid var(--color-secondary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Disabled States
|
||||
======================================== */
|
||||
|
||||
.disabled,
|
||||
:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Loading State
|
||||
======================================== */
|
||||
|
||||
.loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: -10px 0 0 -10px;
|
||||
border: 2px solid var(--color-gray-300);
|
||||
border-top-color: var(--color-primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Animations
|
||||
======================================== */
|
||||
|
||||
/* Fade In */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
/* Slide Up */
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.slide-up {
|
||||
animation: slideUp var(--transition-normal) var(--ease-out);
|
||||
}
|
||||
|
||||
/* Slide Down */
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.slide-down {
|
||||
animation: slideDown var(--transition-normal) var(--ease-out);
|
||||
}
|
||||
|
||||
/* Scale In */
|
||||
@keyframes scaleIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.scale-in {
|
||||
animation: scaleIn var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Shadows
|
||||
======================================== */
|
||||
|
||||
.shadow-sm { box-shadow: var(--shadow-sm); }
|
||||
.shadow-md { box-shadow: var(--shadow-md); }
|
||||
.shadow-lg { box-shadow: var(--shadow-lg); }
|
||||
.shadow-xl { box-shadow: var(--shadow-xl); }
|
||||
.shadow-none { box-shadow: none; }
|
||||
|
||||
/* ========================================
|
||||
Border Radius
|
||||
======================================== */
|
||||
|
||||
.rounded-xs { border-radius: var(--radius-xs); }
|
||||
.rounded-s { border-radius: var(--radius-s); }
|
||||
.rounded-m { border-radius: var(--radius-m); }
|
||||
.rounded-l { border-radius: var(--radius-l); }
|
||||
.rounded-xl { border-radius: var(--radius-xl); }
|
||||
.rounded-full { border-radius: var(--radius-pill); }
|
||||
.rounded-none { border-radius: 0; }
|
||||
|
||||
/* ========================================
|
||||
Borders
|
||||
======================================== */
|
||||
|
||||
.border { border: 1px solid var(--color-gray-300); }
|
||||
.border-2 { border: 2px solid var(--color-gray-300); }
|
||||
.border-primary { border-color: var(--color-primary); }
|
||||
.border-secondary { border-color: var(--color-secondary); }
|
||||
.border-error { border-color: var(--color-error); }
|
||||
.border-none { border: none; }
|
||||
|
||||
/* ========================================
|
||||
Opacity
|
||||
======================================== */
|
||||
|
||||
.opacity-0 { opacity: 0; }
|
||||
.opacity-50 { opacity: 0.5; }
|
||||
.opacity-100 { opacity: 1; }
|
||||
|
||||
/* ========================================
|
||||
Cursor
|
||||
======================================== */
|
||||
|
||||
.cursor-pointer { cursor: pointer; }
|
||||
.cursor-not-allowed { cursor: not-allowed; }
|
||||
.cursor-default { cursor: default; }
|
||||
|
||||
/* ========================================
|
||||
User Select
|
||||
======================================== */
|
||||
|
||||
.select-none { user-select: none; }
|
||||
.select-text { user-select: text; }
|
||||
|
||||
/* ========================================
|
||||
Pointer Events
|
||||
======================================== */
|
||||
|
||||
.pointer-events-none { pointer-events: none; }
|
||||
.pointer-events-auto { pointer-events: auto; }
|
||||
|
||||
/* ========================================
|
||||
Page Layout
|
||||
======================================== */
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: var(--z-sticky);
|
||||
background-color: var(--color-white);
|
||||
border-bottom: 1px solid var(--color-gray-300);
|
||||
}
|
||||
|
||||
.page-main {
|
||||
flex: 1;
|
||||
padding-bottom: calc(var(--bottom-nav-height) + var(--spacing-l));
|
||||
}
|
||||
|
||||
.page-footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: var(--z-fixed);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Divider
|
||||
======================================== */
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background-color: var(--color-gray-300);
|
||||
margin: var(--spacing-l) 0;
|
||||
}
|
||||
|
||||
.divider-thick {
|
||||
height: 8px;
|
||||
background-color: var(--color-gray-100);
|
||||
margin: var(--spacing-l) 0;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Badge
|
||||
======================================== */
|
||||
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-xs) var(--spacing-s);
|
||||
border-radius: var(--radius-pill);
|
||||
font-size: var(--font-size-body-small);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.badge-primary {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.badge-secondary {
|
||||
background: var(--gradient-secondary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background-color: var(--color-success);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background-color: var(--color-warning);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.badge-error {
|
||||
background-color: var(--color-error);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.badge-gray {
|
||||
background-color: var(--color-gray-300);
|
||||
color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Empty State
|
||||
======================================== */
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-2xl) var(--spacing-l);
|
||||
text-align: center;
|
||||
color: var(--color-gray-500);
|
||||
}
|
||||
|
||||
.empty-state__icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin-bottom: var(--spacing-m);
|
||||
color: var(--color-gray-300);
|
||||
}
|
||||
|
||||
.empty-state__title {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-gray-700);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.empty-state__description {
|
||||
font-size: var(--font-size-body-medium);
|
||||
color: var(--color-gray-500);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Utility: Truncate Text
|
||||
======================================== */
|
||||
|
||||
.truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.line-clamp-3 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -1,247 +0,0 @@
|
||||
/**
|
||||
* KT AI 기반 소상공인 이벤트 자동 생성 서비스
|
||||
* Button Components
|
||||
*
|
||||
* 참조: design/uiux/style-guide.md (Section 6.1)
|
||||
* 작성일: 2025-01-20
|
||||
*/
|
||||
|
||||
/* ========================================
|
||||
Button Base
|
||||
======================================== */
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-s);
|
||||
border: none;
|
||||
border-radius: var(--radius-s);
|
||||
font-family: var(--font-family);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
|
||||
/* Touch Target */
|
||||
min-height: var(--touch-target-min);
|
||||
}
|
||||
|
||||
.btn:focus-visible {
|
||||
outline: 2px solid var(--color-secondary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Primary Button
|
||||
======================================== */
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
box-shadow: var(--shadow-primary);
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background-color: var(--color-primary-light);
|
||||
}
|
||||
|
||||
.btn-primary:active:not(:disabled) {
|
||||
background-color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
background-color: var(--color-gray-300);
|
||||
color: var(--color-gray-500);
|
||||
box-shadow: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Secondary Button
|
||||
======================================== */
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--color-white);
|
||||
color: var(--color-primary);
|
||||
border: 2px solid var(--color-primary);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn-secondary:hover:not(:disabled) {
|
||||
background-color: var(--color-primary-50);
|
||||
}
|
||||
|
||||
.btn-secondary:active:not(:disabled) {
|
||||
background-color: var(--color-primary-100);
|
||||
}
|
||||
|
||||
.btn-secondary:disabled {
|
||||
background-color: var(--color-white);
|
||||
border-color: var(--border-medium);
|
||||
color: var(--color-gray-500);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Text Button
|
||||
======================================== */
|
||||
|
||||
.btn-text {
|
||||
background-color: transparent;
|
||||
color: var(--color-primary);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn-text:hover:not(:disabled) {
|
||||
background-color: var(--color-primary-50);
|
||||
}
|
||||
|
||||
.btn-text:active:not(:disabled) {
|
||||
background-color: var(--color-primary-100);
|
||||
}
|
||||
|
||||
.btn-text:disabled {
|
||||
background-color: transparent;
|
||||
color: var(--color-gray-500);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Button Sizes
|
||||
======================================== */
|
||||
|
||||
/* Large - 주요 CTA */
|
||||
.btn-large {
|
||||
height: var(--button-height-large);
|
||||
padding: 16px 24px;
|
||||
font-size: var(--font-size-button);
|
||||
line-height: var(--line-height-button);
|
||||
}
|
||||
|
||||
/* Medium - 일반 액션 */
|
||||
.btn-medium {
|
||||
height: var(--button-height-medium);
|
||||
padding: 12px 20px;
|
||||
font-size: var(--font-size-body-medium);
|
||||
line-height: var(--line-height-body-medium);
|
||||
}
|
||||
|
||||
/* Small - 보조 액션 */
|
||||
.btn-small {
|
||||
height: var(--button-height-small);
|
||||
padding: 8px 16px;
|
||||
font-size: var(--font-size-body-small);
|
||||
line-height: var(--line-height-body-small);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Button with Icon
|
||||
======================================== */
|
||||
|
||||
.btn-icon-left {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.btn-icon-right {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.btn .icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.btn-large .icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.btn-small .icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Full Width Button
|
||||
======================================== */
|
||||
|
||||
.btn-block {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Loading State
|
||||
======================================== */
|
||||
|
||||
.btn-loading {
|
||||
position: relative;
|
||||
color: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.btn-loading::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: -8px 0 0 -8px;
|
||||
border: 2px solid currentColor;
|
||||
border-top-color: transparent;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.btn-secondary.btn-loading::after,
|
||||
.btn-text.btn-loading::after {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Button Group
|
||||
======================================== */
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 12px; /* S + XS */
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn-group-vertical {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.btn-group-vertical .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Icon-only Button
|
||||
======================================== */
|
||||
|
||||
.btn-icon-only {
|
||||
width: var(--button-height-medium);
|
||||
padding: 0;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.btn-icon-only.btn-large {
|
||||
width: var(--button-height-large);
|
||||
}
|
||||
|
||||
.btn-icon-only.btn-small {
|
||||
width: var(--button-height-small);
|
||||
}
|
||||
@ -1,292 +0,0 @@
|
||||
/**
|
||||
* KT AI 기반 소상공인 이벤트 자동 생성 서비스
|
||||
* Card Components
|
||||
*
|
||||
* 참조: design/uiux/style-guide.md (Section 6.2, 8.2)
|
||||
* 작성일: 2025-01-20
|
||||
*/
|
||||
|
||||
/* ========================================
|
||||
Card Base
|
||||
======================================== */
|
||||
|
||||
.card {
|
||||
background-color: var(--bg-primary);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-m);
|
||||
box-shadow: var(--shadow-md);
|
||||
padding: var(--spacing-l);
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.card-selected {
|
||||
border: 2px solid var(--color-primary);
|
||||
padding: calc(var(--spacing-l) - 1px); /* 테두리 2px 보정 */
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Event Card
|
||||
======================================== */
|
||||
|
||||
.event-card {
|
||||
background-color: var(--bg-primary);
|
||||
border-radius: var(--radius-m);
|
||||
box-shadow: var(--shadow-md);
|
||||
overflow: hidden;
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.event-card:hover {
|
||||
box-shadow: var(--shadow-lg);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.event-card__image {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.event-card__content {
|
||||
padding: var(--spacing-l);
|
||||
}
|
||||
|
||||
.event-card__title {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
line-height: var(--line-height-h3);
|
||||
color: var(--color-black);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.event-card__meta {
|
||||
font-size: var(--font-size-body-small);
|
||||
line-height: var(--line-height-body-small);
|
||||
color: var(--color-gray-500);
|
||||
margin-bottom: var(--spacing-m);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.event-card__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-s);
|
||||
margin-top: var(--spacing-m);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Stat Card (지표 카드)
|
||||
======================================== */
|
||||
|
||||
.stat-card {
|
||||
background-color: var(--bg-primary);
|
||||
border-radius: var(--radius-l);
|
||||
box-shadow: var(--shadow-md);
|
||||
padding: var(--spacing-l);
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.stat-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.stat-card__icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.stat-card__label {
|
||||
font-size: var(--font-size-body-small);
|
||||
line-height: var(--line-height-body-small);
|
||||
color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
.stat-card__value {
|
||||
font-size: var(--font-size-display);
|
||||
font-weight: var(--font-weight-bold);
|
||||
line-height: var(--line-height-display);
|
||||
color: var(--color-black);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.stat-card__change {
|
||||
font-size: var(--font-size-body-small);
|
||||
line-height: var(--line-height-body-small);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.stat-card__change--positive {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.stat-card__change--negative {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.stat-card__change--neutral {
|
||||
color: var(--color-gray-500);
|
||||
}
|
||||
|
||||
/* Gradient variant */
|
||||
.stat-card--gradient {
|
||||
background: var(--gradient-primary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.stat-card--gradient .stat-card__label,
|
||||
.stat-card--gradient .stat-card__value,
|
||||
.stat-card--gradient .stat-card__change {
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.stat-card--gradient .stat-card__icon {
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
AI Result Card (AI 생성 옵션 카드)
|
||||
======================================== */
|
||||
|
||||
.ai-card {
|
||||
position: relative;
|
||||
background-color: var(--bg-primary);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--spacing-l);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.ai-card:hover {
|
||||
border-color: var(--color-primary-light);
|
||||
box-shadow: var(--shadow-lg);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.ai-card--selected {
|
||||
border: 2px solid var(--color-primary);
|
||||
background-color: var(--color-primary-50);
|
||||
padding: calc(var(--spacing-l) - 1px);
|
||||
}
|
||||
|
||||
.ai-card__radio {
|
||||
position: absolute;
|
||||
top: var(--spacing-m);
|
||||
right: var(--spacing-m);
|
||||
}
|
||||
|
||||
.ai-card__preview {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
border-radius: var(--radius-s);
|
||||
object-fit: cover;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.ai-card__title {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
line-height: var(--line-height-h3);
|
||||
color: var(--color-black);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.ai-card__description {
|
||||
font-size: var(--font-size-body-small);
|
||||
line-height: var(--line-height-body-small);
|
||||
color: var(--color-gray-500);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.ai-card__actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
/* AI 추천 배지 */
|
||||
.ai-badge {
|
||||
position: absolute;
|
||||
top: var(--spacing-m);
|
||||
left: var(--spacing-m);
|
||||
background: var(--gradient-secondary);
|
||||
color: var(--color-white);
|
||||
font-size: var(--font-size-body-small);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
padding: 4px 12px;
|
||||
border-radius: var(--radius-pill);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Card Grid
|
||||
======================================== */
|
||||
|
||||
.card-grid {
|
||||
display: grid;
|
||||
gap: var(--spacing-m);
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.card-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.card-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Clickable Card
|
||||
======================================== */
|
||||
|
||||
.card-clickable {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.card-clickable:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Card with Image
|
||||
======================================== */
|
||||
|
||||
.card-image {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-image__img {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.card-image__content {
|
||||
padding: var(--spacing-l);
|
||||
}
|
||||
@ -1,339 +0,0 @@
|
||||
/**
|
||||
* KT AI 기반 소상공인 이벤트 자동 생성 서비스
|
||||
* Input Components
|
||||
*
|
||||
* 참조: design/uiux/style-guide.md (Section 6.3, 6.4)
|
||||
* 작성일: 2025-01-20
|
||||
*/
|
||||
|
||||
/* ========================================
|
||||
Form Group
|
||||
======================================== */
|
||||
|
||||
.form-group {
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: var(--font-size-body-medium);
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: var(--line-height-body-medium);
|
||||
color: var(--color-black);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.form-label--required::after {
|
||||
content: '*';
|
||||
color: var(--color-error);
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.form-helper {
|
||||
display: block;
|
||||
font-size: var(--font-size-body-small);
|
||||
line-height: var(--line-height-body-small);
|
||||
color: var(--color-gray-500);
|
||||
margin-top: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.form-error {
|
||||
display: block;
|
||||
font-size: var(--font-size-body-small);
|
||||
line-height: var(--line-height-body-small);
|
||||
color: var(--color-error);
|
||||
margin-top: var(--spacing-xs);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Text Input
|
||||
======================================== */
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
height: var(--input-height);
|
||||
padding: var(--spacing-m);
|
||||
border: 1px solid var(--border-medium);
|
||||
border-radius: var(--radius-s);
|
||||
background-color: var(--bg-primary);
|
||||
font-family: var(--font-family);
|
||||
font-size: var(--font-size-body-large);
|
||||
font-weight: var(--font-weight-regular);
|
||||
line-height: var(--line-height-body-large);
|
||||
color: var(--color-black);
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.input::placeholder {
|
||||
color: var(--color-gray-500);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border: 2px solid var(--color-secondary);
|
||||
box-shadow: var(--shadow-secondary);
|
||||
padding: calc(var(--spacing-m) - 1px); /* 테두리 2px 보정 */
|
||||
}
|
||||
|
||||
.input:disabled {
|
||||
background-color: var(--bg-tertiary);
|
||||
border-color: var(--border-light);
|
||||
color: var(--color-gray-500);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Error State */
|
||||
.input-error {
|
||||
border: 2px solid var(--color-error);
|
||||
box-shadow: var(--shadow-error);
|
||||
padding: calc(var(--spacing-m) - 1px);
|
||||
}
|
||||
|
||||
.input-error:focus {
|
||||
border-color: var(--color-error);
|
||||
box-shadow: var(--shadow-error);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Textarea
|
||||
======================================== */
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
min-height: var(--textarea-min-height);
|
||||
padding: var(--spacing-m);
|
||||
border: 1px solid var(--border-medium);
|
||||
border-radius: var(--radius-s);
|
||||
background-color: var(--bg-primary);
|
||||
font-family: var(--font-family);
|
||||
font-size: var(--font-size-body-medium);
|
||||
font-weight: var(--font-weight-regular);
|
||||
line-height: var(--line-height-body-medium);
|
||||
color: var(--color-black);
|
||||
resize: vertical;
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.textarea::placeholder {
|
||||
color: var(--color-gray-500);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.textarea:focus {
|
||||
outline: none;
|
||||
border: 2px solid var(--color-secondary);
|
||||
box-shadow: var(--shadow-secondary);
|
||||
padding: calc(var(--spacing-m) - 1px);
|
||||
}
|
||||
|
||||
.textarea:disabled {
|
||||
background-color: var(--bg-tertiary);
|
||||
border-color: var(--border-light);
|
||||
color: var(--color-gray-500);
|
||||
cursor: not-allowed;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Select
|
||||
======================================== */
|
||||
|
||||
.select {
|
||||
width: 100%;
|
||||
height: var(--input-height);
|
||||
padding: var(--spacing-m);
|
||||
padding-right: 40px;
|
||||
border: 1px solid var(--border-medium);
|
||||
border-radius: var(--radius-s);
|
||||
background-color: var(--bg-primary);
|
||||
background-image: url('data:image/svg+xml;charset=UTF-8,<svg width="12" height="8" viewBox="0 0 12 8" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 1L6 6L11 1" stroke="%239E9E9E" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>');
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 16px center;
|
||||
font-family: var(--font-family);
|
||||
font-size: var(--font-size-body-large);
|
||||
font-weight: var(--font-weight-regular);
|
||||
line-height: var(--line-height-body-large);
|
||||
color: var(--color-black);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.select:focus {
|
||||
outline: none;
|
||||
border: 2px solid var(--color-secondary);
|
||||
box-shadow: var(--shadow-secondary);
|
||||
padding: calc(var(--spacing-m) - 1px);
|
||||
padding-right: 39px;
|
||||
}
|
||||
|
||||
.select:disabled {
|
||||
background-color: var(--bg-tertiary);
|
||||
border-color: var(--border-light);
|
||||
color: var(--color-gray-500);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Checkbox
|
||||
======================================== */
|
||||
|
||||
.checkbox-wrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
min-height: var(--touch-target-min);
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
appearance: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid var(--border-medium);
|
||||
border-radius: var(--radius-xs);
|
||||
background-color: var(--bg-primary);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.checkbox:checked {
|
||||
background-color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.checkbox:checked::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 6px;
|
||||
width: 6px;
|
||||
height: 12px;
|
||||
border: solid var(--color-white);
|
||||
border-width: 0 2px 2px 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.checkbox:focus-visible {
|
||||
outline: 2px solid var(--color-secondary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.checkbox:disabled {
|
||||
background-color: var(--bg-tertiary);
|
||||
border-color: var(--border-light);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
font-size: var(--font-size-body-medium);
|
||||
line-height: var(--line-height-body-medium);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Radio Button
|
||||
======================================== */
|
||||
|
||||
.radio-wrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
min-height: var(--touch-target-min);
|
||||
}
|
||||
|
||||
.radio {
|
||||
appearance: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid var(--border-medium);
|
||||
border-radius: 50%;
|
||||
background-color: var(--bg-primary);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.radio:checked {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.radio:checked::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: var(--color-primary);
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.radio:focus-visible {
|
||||
outline: 2px solid var(--color-secondary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.radio:disabled {
|
||||
background-color: var(--bg-tertiary);
|
||||
border-color: var(--border-light);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.radio-label {
|
||||
font-size: var(--font-size-body-medium);
|
||||
line-height: var(--line-height-body-medium);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Radio/Checkbox Group
|
||||
======================================== */
|
||||
|
||||
.radio-group,
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.radio-group--inline,
|
||||
.checkbox-group--inline {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-l);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Input with Icon
|
||||
======================================== */
|
||||
|
||||
.input-icon-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.input-icon-wrapper .input {
|
||||
padding-left: 48px;
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: var(--color-gray-500);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.input-icon-wrapper .input:focus ~ .input-icon {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
@ -1,321 +0,0 @@
|
||||
/**
|
||||
* KT AI 기반 소상공인 이벤트 자동 생성 서비스
|
||||
* Loading Components
|
||||
*
|
||||
* 참조: design/uiux/style-guide.md (Section 8.1)
|
||||
* 작성일: 2025-01-20
|
||||
*/
|
||||
|
||||
/* ========================================
|
||||
Spinner (간단한 로딩)
|
||||
======================================== */
|
||||
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid var(--color-gray-300);
|
||||
border-top-color: var(--color-primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
.spinner--small {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.spinner--large {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-width: 4px;
|
||||
}
|
||||
|
||||
.spinner--secondary {
|
||||
border-top-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Loading Skeleton
|
||||
======================================== */
|
||||
|
||||
.skeleton {
|
||||
background-color: var(--color-gray-100);
|
||||
border-radius: var(--radius-s);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.skeleton::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.5),
|
||||
transparent
|
||||
);
|
||||
animation: shimmer 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
to { left: 100%; }
|
||||
}
|
||||
|
||||
.skeleton-text {
|
||||
height: 16px;
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.skeleton-text--title {
|
||||
height: 24px;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.skeleton-text--body {
|
||||
height: 14px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.skeleton-text--body:last-child {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.skeleton-image {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
|
||||
.skeleton-circle {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.skeleton-button {
|
||||
height: 48px;
|
||||
width: 120px;
|
||||
border-radius: var(--radius-s);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
AI Progress Indicator
|
||||
======================================== */
|
||||
|
||||
.ai-progress {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: var(--spacing-2xl) var(--spacing-l);
|
||||
background-color: var(--color-white);
|
||||
border: 1px solid #E0E0E0;
|
||||
border-radius: var(--radius-m);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ai-progress__icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin-bottom: var(--spacing-m);
|
||||
color: var(--color-secondary);
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: 0.6; transform: scale(0.95); }
|
||||
}
|
||||
|
||||
.ai-progress__title {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
line-height: var(--line-height-h3);
|
||||
color: var(--color-black);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.ai-progress__bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background-color: var(--color-gray-100);
|
||||
border-radius: var(--radius-xs);
|
||||
overflow: hidden;
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.ai-progress__fill {
|
||||
height: 100%;
|
||||
background-color: var(--color-secondary);
|
||||
border-radius: var(--radius-xs);
|
||||
transition: width var(--transition-normal) var(--ease-out);
|
||||
}
|
||||
|
||||
.ai-progress__percentage {
|
||||
font-size: var(--font-size-body-medium);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-black);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.ai-progress__time {
|
||||
font-size: var(--font-size-body-small);
|
||||
color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Skeleton Card
|
||||
======================================== */
|
||||
|
||||
.skeleton-card {
|
||||
background-color: var(--color-white);
|
||||
border: 1px solid #E0E0E0;
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--spacing-l);
|
||||
}
|
||||
|
||||
.skeleton-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.skeleton-card__avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.skeleton-card__title {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.skeleton-card__image {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.skeleton-card__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.skeleton-card__footer {
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
margin-top: var(--spacing-m);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Loading Overlay
|
||||
======================================== */
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.loading-overlay__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--spacing-m);
|
||||
background-color: var(--color-white);
|
||||
padding: var(--spacing-2xl);
|
||||
border-radius: var(--radius-m);
|
||||
box-shadow: var(--shadow-xl);
|
||||
}
|
||||
|
||||
.loading-overlay__text {
|
||||
font-size: var(--font-size-body-medium);
|
||||
color: var(--color-black);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Dots Loader
|
||||
======================================== */
|
||||
|
||||
.dots-loader {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.dots-loader__dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-primary);
|
||||
animation: dotPulse 1.4s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.dots-loader__dot:nth-child(1) {
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.dots-loader__dot:nth-child(2) {
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
@keyframes dotPulse {
|
||||
0%, 80%, 100% {
|
||||
opacity: 0.3;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
40% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Linear Progress
|
||||
======================================== */
|
||||
|
||||
.linear-progress {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background-color: var(--color-gray-100);
|
||||
border-radius: var(--radius-xs);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.linear-progress__bar {
|
||||
height: 100%;
|
||||
background-color: var(--color-primary);
|
||||
transition: width var(--transition-normal) var(--ease-out);
|
||||
}
|
||||
|
||||
.linear-progress--indeterminate .linear-progress__bar {
|
||||
width: 30%;
|
||||
animation: indeterminate 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes indeterminate {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(400%);
|
||||
}
|
||||
}
|
||||
@ -1,390 +0,0 @@
|
||||
/**
|
||||
* KT AI 기반 소상공인 이벤트 자동 생성 서비스
|
||||
* Modal Components
|
||||
*
|
||||
* 참조: design/uiux/style-guide.md
|
||||
* 작성일: 2025-01-20
|
||||
*/
|
||||
|
||||
/* ========================================
|
||||
Modal Backdrop
|
||||
======================================== */
|
||||
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--bg-overlay);
|
||||
z-index: var(--z-modal-backdrop);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-l);
|
||||
animation: fadeIn var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Modal
|
||||
======================================== */
|
||||
|
||||
.modal {
|
||||
background-color: var(--bg-primary);
|
||||
border-radius: var(--radius-m);
|
||||
box-shadow: var(--shadow-xl);
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: var(--z-modal);
|
||||
animation: scaleIn var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.modal--small {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.modal--large {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.modal--fullscreen {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.modal__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--spacing-l);
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.modal__title {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
line-height: var(--line-height-h3);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
.modal__close {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
color: var(--color-gray-500);
|
||||
cursor: pointer;
|
||||
transition: color var(--transition-fast) var(--ease-out);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.modal__close:hover {
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
.modal__body {
|
||||
flex: 1;
|
||||
padding: var(--spacing-l);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: var(--spacing-m);
|
||||
padding: var(--spacing-l);
|
||||
border-top: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Bottom Sheet
|
||||
======================================== */
|
||||
|
||||
.bottom-sheet {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--bg-primary);
|
||||
border-radius: var(--radius-l) var(--radius-l) 0 0;
|
||||
box-shadow: var(--shadow-lg);
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: var(--z-modal);
|
||||
animation: slideUp var(--transition-normal) var(--ease-out);
|
||||
}
|
||||
|
||||
.bottom-sheet__handle {
|
||||
width: 40px;
|
||||
height: 4px;
|
||||
background-color: var(--border-medium);
|
||||
border-radius: var(--radius-xs);
|
||||
margin: var(--spacing-m) auto;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.bottom-sheet__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 var(--spacing-l) var(--spacing-l);
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.bottom-sheet__title {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
line-height: var(--line-height-h3);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
.bottom-sheet__close {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
color: var(--color-gray-500);
|
||||
cursor: pointer;
|
||||
transition: color var(--transition-fast) var(--ease-out);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.bottom-sheet__close:hover {
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
.bottom-sheet__body {
|
||||
flex: 1;
|
||||
padding: var(--spacing-l);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.bottom-sheet__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: var(--spacing-m);
|
||||
padding: var(--spacing-l);
|
||||
border-top: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Toast / Snackbar
|
||||
======================================== */
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: calc(var(--bottom-nav-height) + var(--spacing-l));
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
min-width: 280px;
|
||||
max-width: 90%;
|
||||
background-color: var(--color-black);
|
||||
color: var(--color-white);
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
border-radius: var(--radius-s);
|
||||
box-shadow: var(--shadow-lg);
|
||||
z-index: var(--z-toast);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-m);
|
||||
animation: slideUp var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.toast--top {
|
||||
top: var(--spacing-l);
|
||||
bottom: auto;
|
||||
animation: slideDown var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.toast--success {
|
||||
background-color: var(--color-success);
|
||||
}
|
||||
|
||||
.toast--warning {
|
||||
background-color: var(--color-warning);
|
||||
}
|
||||
|
||||
.toast--error {
|
||||
background-color: var(--color-error);
|
||||
}
|
||||
|
||||
.toast--info {
|
||||
background-color: var(--color-info);
|
||||
}
|
||||
|
||||
.toast__icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toast__message {
|
||||
flex: 1;
|
||||
font-size: var(--font-size-body-medium);
|
||||
line-height: var(--line-height-body-medium);
|
||||
}
|
||||
|
||||
.toast__close {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: var(--color-white);
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
transition: opacity var(--transition-fast) var(--ease-out);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toast__close:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Dialog (Confirm)
|
||||
======================================== */
|
||||
|
||||
.dialog {
|
||||
background-color: var(--bg-primary);
|
||||
border-radius: var(--radius-m);
|
||||
box-shadow: var(--shadow-xl);
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
z-index: var(--z-modal);
|
||||
animation: scaleIn var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.dialog__header {
|
||||
padding: var(--spacing-l);
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.dialog__title {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
line-height: var(--line-height-h3);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
.dialog__body {
|
||||
padding: var(--spacing-l);
|
||||
}
|
||||
|
||||
.dialog__message {
|
||||
font-size: var(--font-size-body-medium);
|
||||
line-height: var(--line-height-body-medium);
|
||||
color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
.dialog__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: var(--spacing-m);
|
||||
padding: var(--spacing-l);
|
||||
border-top: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Tooltip
|
||||
======================================== */
|
||||
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
background-color: var(--color-black);
|
||||
color: var(--color-white);
|
||||
padding: var(--spacing-s) var(--spacing-m);
|
||||
border-radius: var(--radius-xs);
|
||||
font-size: var(--font-size-body-small);
|
||||
line-height: var(--line-height-body-small);
|
||||
white-space: nowrap;
|
||||
z-index: var(--z-tooltip);
|
||||
pointer-events: none;
|
||||
animation: fadeIn var(--transition-very-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.tooltip::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.tooltip--top {
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-8px);
|
||||
}
|
||||
|
||||
.tooltip--top::before {
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-width: 4px 4px 0;
|
||||
border-color: var(--color-black) transparent transparent;
|
||||
}
|
||||
|
||||
.tooltip--bottom {
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(8px);
|
||||
}
|
||||
|
||||
.tooltip--bottom::before {
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-width: 0 4px 4px;
|
||||
border-color: transparent transparent var(--color-black);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Dropdown
|
||||
======================================== */
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dropdown__menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
margin-top: var(--spacing-xs);
|
||||
background-color: var(--bg-primary);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-s);
|
||||
box-shadow: var(--shadow-md);
|
||||
min-width: 200px;
|
||||
z-index: var(--z-dropdown);
|
||||
animation: fadeIn var(--transition-very-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.dropdown__item {
|
||||
display: block;
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
font-size: var(--font-size-body-medium);
|
||||
line-height: var(--line-height-body-medium);
|
||||
color: var(--color-black);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: background-color var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.dropdown__item:hover {
|
||||
background-color: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.dropdown__item:active {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.dropdown__divider {
|
||||
height: 1px;
|
||||
background-color: var(--border-light);
|
||||
margin: var(--spacing-xs) 0;
|
||||
}
|
||||
@ -1,308 +0,0 @@
|
||||
/**
|
||||
* KT AI 기반 소상공인 이벤트 자동 생성 서비스
|
||||
* Navigation Components
|
||||
*
|
||||
* 참조: design/uiux/style-guide.md (Section 6.5)
|
||||
* 작성일: 2025-01-20
|
||||
*/
|
||||
|
||||
/* ========================================
|
||||
App Bar (상단 네비게이션)
|
||||
======================================== */
|
||||
|
||||
.app-bar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: var(--app-bar-height);
|
||||
background-color: var(--bg-primary);
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
box-shadow: var(--shadow-sm);
|
||||
z-index: var(--z-sticky);
|
||||
}
|
||||
|
||||
.app-bar__container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
padding: 0 var(--spacing-m);
|
||||
max-width: var(--container-desktop);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.app-bar__left,
|
||||
.app-bar__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.app-bar__title {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: var(--font-weight-bold);
|
||||
line-height: var(--line-height-h3);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
.app-bar__back {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
color: var(--color-black);
|
||||
cursor: pointer;
|
||||
transition: color var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.app-bar__back:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Bottom Navigation
|
||||
======================================== */
|
||||
|
||||
.bottom-nav {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: var(--bottom-nav-height);
|
||||
background-color: var(--bg-primary);
|
||||
box-shadow: var(--shadow-md);
|
||||
z-index: var(--z-fixed);
|
||||
}
|
||||
|
||||
.bottom-nav__container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
height: 100%;
|
||||
max-width: var(--container-desktop);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.bottom-nav__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-xs);
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
min-width: var(--touch-target-min);
|
||||
text-decoration: none;
|
||||
color: var(--color-gray-500);
|
||||
cursor: pointer;
|
||||
transition: color var(--transition-fast) var(--ease-out);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.bottom-nav__item:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.bottom-nav__item--active {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.bottom-nav__icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.bottom-nav__label {
|
||||
font-size: var(--font-size-body-small);
|
||||
line-height: 1;
|
||||
font-weight: var(--font-weight-regular);
|
||||
}
|
||||
|
||||
.bottom-nav__item--active .bottom-nav__label {
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Tab Navigation
|
||||
======================================== */
|
||||
|
||||
.tab-nav {
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.tab-nav__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-s);
|
||||
padding: var(--spacing-m) var(--spacing-l);
|
||||
min-height: var(--touch-target-min);
|
||||
font-size: var(--font-size-body-medium);
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: var(--line-height-body-medium);
|
||||
color: var(--color-gray-500);
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
border-bottom: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tab-nav__item:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.tab-nav__item--active {
|
||||
color: var(--color-primary);
|
||||
border-bottom-color: var(--color-primary);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Breadcrumb
|
||||
======================================== */
|
||||
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
flex-wrap: wrap;
|
||||
padding: var(--spacing-m) 0;
|
||||
}
|
||||
|
||||
.breadcrumb__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
font-size: var(--font-size-body-small);
|
||||
line-height: var(--line-height-body-small);
|
||||
color: var(--color-gray-500);
|
||||
}
|
||||
|
||||
.breadcrumb__link {
|
||||
color: var(--color-gray-500);
|
||||
text-decoration: none;
|
||||
transition: color var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.breadcrumb__link:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.breadcrumb__separator {
|
||||
color: var(--color-gray-400);
|
||||
}
|
||||
|
||||
.breadcrumb__item--active {
|
||||
color: var(--color-black);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Stepper (단계 표시)
|
||||
======================================== */
|
||||
|
||||
.stepper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.stepper__step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stepper__circle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
font-size: var(--font-size-body-small);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
transition: all var(--transition-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
/* Completed */
|
||||
.stepper__step--completed .stepper__circle {
|
||||
background-color: var(--color-success);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
/* Active */
|
||||
.stepper__step--active .stepper__circle {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
/* Pending */
|
||||
.stepper__step--pending .stepper__circle {
|
||||
background-color: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-medium);
|
||||
color: var(--color-gray-500);
|
||||
}
|
||||
|
||||
.stepper__label {
|
||||
font-size: var(--font-size-body-small);
|
||||
line-height: var(--line-height-body-small);
|
||||
text-align: center;
|
||||
color: var(--color-gray-500);
|
||||
}
|
||||
|
||||
.stepper__step--active .stepper__label,
|
||||
.stepper__step--completed .stepper__label {
|
||||
color: var(--color-black);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.stepper__line {
|
||||
flex: 1;
|
||||
height: 2px;
|
||||
background-color: var(--border-medium);
|
||||
margin: 0 var(--spacing-s);
|
||||
}
|
||||
|
||||
.stepper__line--completed {
|
||||
background-color: var(--color-success);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Progress Bar
|
||||
======================================== */
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
background-color: var(--bg-tertiary);
|
||||
border-radius: var(--radius-xl);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-bar__fill {
|
||||
height: 100%;
|
||||
background-color: var(--color-primary);
|
||||
transition: width var(--transition-normal) var(--ease-out);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar__text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: var(--font-size-body-medium);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-black);
|
||||
z-index: 1;
|
||||
}
|
||||
@ -1,304 +0,0 @@
|
||||
/**
|
||||
* KT AI 기반 소상공인 이벤트 자동 생성 서비스
|
||||
* Layout System (Grid, Container, Spacing)
|
||||
*
|
||||
* 참조: design/uiux/style-guide.md
|
||||
* 작성일: 2025-01-20
|
||||
*/
|
||||
|
||||
/* ========================================
|
||||
Container
|
||||
======================================== */
|
||||
|
||||
/* Mobile (320px ~ 767px) */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: var(--container-mobile);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
/* Tablet (768px ~ 1023px) */
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: var(--container-tablet);
|
||||
padding-left: 40px;
|
||||
padding-right: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop (1024px ~) */
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: var(--container-desktop);
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Grid System
|
||||
======================================== */
|
||||
|
||||
/* Mobile Grid (4 Columns) */
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: var(--gutter-mobile);
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
/* Tablet Grid (8 Columns) */
|
||||
@media (min-width: 768px) {
|
||||
.grid {
|
||||
gap: var(--gutter-tablet);
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop Grid (12 Columns) */
|
||||
@media (min-width: 1024px) {
|
||||
.grid {
|
||||
gap: var(--gutter-desktop);
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Grid Column Spans */
|
||||
.col-1 { grid-column: span 1; }
|
||||
.col-2 { grid-column: span 2; }
|
||||
.col-3 { grid-column: span 3; }
|
||||
.col-4 { grid-column: span 4; }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.col-md-1 { grid-column: span 1; }
|
||||
.col-md-2 { grid-column: span 2; }
|
||||
.col-md-3 { grid-column: span 3; }
|
||||
.col-md-4 { grid-column: span 4; }
|
||||
.col-md-5 { grid-column: span 5; }
|
||||
.col-md-6 { grid-column: span 6; }
|
||||
.col-md-7 { grid-column: span 7; }
|
||||
.col-md-8 { grid-column: span 8; }
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.col-lg-1 { grid-column: span 1; }
|
||||
.col-lg-2 { grid-column: span 2; }
|
||||
.col-lg-3 { grid-column: span 3; }
|
||||
.col-lg-4 { grid-column: span 4; }
|
||||
.col-lg-5 { grid-column: span 5; }
|
||||
.col-lg-6 { grid-column: span 6; }
|
||||
.col-lg-7 { grid-column: span 7; }
|
||||
.col-lg-8 { grid-column: span 8; }
|
||||
.col-lg-9 { grid-column: span 9; }
|
||||
.col-lg-10 { grid-column: span 10; }
|
||||
.col-lg-11 { grid-column: span 11; }
|
||||
.col-lg-12 { grid-column: span 12; }
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Flexbox Utilities
|
||||
======================================== */
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.inline-flex {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
/* Flex Direction */
|
||||
.flex-row { flex-direction: row; }
|
||||
.flex-row-reverse { flex-direction: row-reverse; }
|
||||
.flex-col { flex-direction: column; }
|
||||
.flex-col-reverse { flex-direction: column-reverse; }
|
||||
|
||||
/* Flex Wrap */
|
||||
.flex-wrap { flex-wrap: wrap; }
|
||||
.flex-nowrap { flex-wrap: nowrap; }
|
||||
|
||||
/* Justify Content */
|
||||
.justify-start { justify-content: flex-start; }
|
||||
.justify-end { justify-content: flex-end; }
|
||||
.justify-center { justify-content: center; }
|
||||
.justify-between { justify-content: space-between; }
|
||||
.justify-around { justify-content: space-around; }
|
||||
.justify-evenly { justify-content: space-evenly; }
|
||||
|
||||
/* Align Items */
|
||||
.items-start { align-items: flex-start; }
|
||||
.items-end { align-items: flex-end; }
|
||||
.items-center { align-items: center; }
|
||||
.items-baseline { align-items: baseline; }
|
||||
.items-stretch { align-items: stretch; }
|
||||
|
||||
/* Align Self */
|
||||
.self-start { align-self: flex-start; }
|
||||
.self-end { align-self: flex-end; }
|
||||
.self-center { align-self: center; }
|
||||
.self-stretch { align-self: stretch; }
|
||||
|
||||
/* Gap */
|
||||
.gap-xs { gap: var(--spacing-xs); }
|
||||
.gap-s { gap: var(--spacing-s); }
|
||||
.gap-m { gap: var(--spacing-m); }
|
||||
.gap-l { gap: var(--spacing-l); }
|
||||
.gap-xl { gap: var(--spacing-xl); }
|
||||
.gap-2xl { gap: var(--spacing-2xl); }
|
||||
|
||||
/* ========================================
|
||||
Spacing Utilities (Margin & Padding)
|
||||
======================================== */
|
||||
|
||||
/* Margin */
|
||||
.m-0 { margin: 0; }
|
||||
.m-xs { margin: var(--spacing-xs); }
|
||||
.m-s { margin: var(--spacing-s); }
|
||||
.m-m { margin: var(--spacing-m); }
|
||||
.m-l { margin: var(--spacing-l); }
|
||||
.m-xl { margin: var(--spacing-xl); }
|
||||
.m-2xl { margin: var(--spacing-2xl); }
|
||||
|
||||
/* Margin Top */
|
||||
.mt-0 { margin-top: 0; }
|
||||
.mt-xs { margin-top: var(--spacing-xs); }
|
||||
.mt-s { margin-top: var(--spacing-s); }
|
||||
.mt-m { margin-top: var(--spacing-m); }
|
||||
.mt-l { margin-top: var(--spacing-l); }
|
||||
.mt-xl { margin-top: var(--spacing-xl); }
|
||||
.mt-2xl { margin-top: var(--spacing-2xl); }
|
||||
|
||||
/* Margin Bottom */
|
||||
.mb-0 { margin-bottom: 0; }
|
||||
.mb-xs { margin-bottom: var(--spacing-xs); }
|
||||
.mb-s { margin-bottom: var(--spacing-s); }
|
||||
.mb-m { margin-bottom: var(--spacing-m); }
|
||||
.mb-l { margin-bottom: var(--spacing-l); }
|
||||
.mb-xl { margin-bottom: var(--spacing-xl); }
|
||||
.mb-2xl { margin-bottom: var(--spacing-2xl); }
|
||||
|
||||
/* Margin Left */
|
||||
.ml-0 { margin-left: 0; }
|
||||
.ml-xs { margin-left: var(--spacing-xs); }
|
||||
.ml-s { margin-left: var(--spacing-s); }
|
||||
.ml-m { margin-left: var(--spacing-m); }
|
||||
.ml-l { margin-left: var(--spacing-l); }
|
||||
.ml-xl { margin-left: var(--spacing-xl); }
|
||||
|
||||
/* Margin Right */
|
||||
.mr-0 { margin-right: 0; }
|
||||
.mr-xs { margin-right: var(--spacing-xs); }
|
||||
.mr-s { margin-right: var(--spacing-s); }
|
||||
.mr-m { margin-right: var(--spacing-m); }
|
||||
.mr-l { margin-right: var(--spacing-l); }
|
||||
.mr-xl { margin-right: var(--spacing-xl); }
|
||||
|
||||
/* Margin Auto */
|
||||
.mx-auto {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/* Padding */
|
||||
.p-0 { padding: 0; }
|
||||
.p-xs { padding: var(--spacing-xs); }
|
||||
.p-s { padding: var(--spacing-s); }
|
||||
.p-m { padding: var(--spacing-m); }
|
||||
.p-l { padding: var(--spacing-l); }
|
||||
.p-xl { padding: var(--spacing-xl); }
|
||||
.p-2xl { padding: var(--spacing-2xl); }
|
||||
|
||||
/* Padding Top */
|
||||
.pt-0 { padding-top: 0; }
|
||||
.pt-xs { padding-top: var(--spacing-xs); }
|
||||
.pt-s { padding-top: var(--spacing-s); }
|
||||
.pt-m { padding-top: var(--spacing-m); }
|
||||
.pt-l { padding-top: var(--spacing-l); }
|
||||
.pt-xl { padding-top: var(--spacing-xl); }
|
||||
.pt-2xl { padding-top: var(--spacing-2xl); }
|
||||
|
||||
/* Padding Bottom */
|
||||
.pb-0 { padding-bottom: 0; }
|
||||
.pb-xs { padding-bottom: var(--spacing-xs); }
|
||||
.pb-s { padding-bottom: var(--spacing-s); }
|
||||
.pb-m { padding-bottom: var(--spacing-m); }
|
||||
.pb-l { padding-bottom: var(--spacing-l); }
|
||||
.pb-xl { padding-bottom: var(--spacing-xl); }
|
||||
.pb-2xl { padding-bottom: var(--spacing-2xl); }
|
||||
|
||||
/* Padding Left */
|
||||
.pl-0 { padding-left: 0; }
|
||||
.pl-xs { padding-left: var(--spacing-xs); }
|
||||
.pl-s { padding-left: var(--spacing-s); }
|
||||
.pl-m { padding-left: var(--spacing-m); }
|
||||
.pl-l { padding-left: var(--spacing-l); }
|
||||
.pl-xl { padding-left: var(--spacing-xl); }
|
||||
|
||||
/* Padding Right */
|
||||
.pr-0 { padding-right: 0; }
|
||||
.pr-xs { padding-right: var(--spacing-xs); }
|
||||
.pr-s { padding-right: var(--spacing-s); }
|
||||
.pr-m { padding-right: var(--spacing-m); }
|
||||
.pr-l { padding-right: var(--spacing-l); }
|
||||
.pr-xl { padding-right: var(--spacing-xl); }
|
||||
|
||||
/* ========================================
|
||||
Display Utilities
|
||||
======================================== */
|
||||
|
||||
.block { display: block; }
|
||||
.inline-block { display: inline-block; }
|
||||
.inline { display: inline; }
|
||||
.hidden { display: none; }
|
||||
|
||||
/* Responsive Display */
|
||||
@media (max-width: 767px) {
|
||||
.hidden-mobile { display: none; }
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 1023px) {
|
||||
.hidden-tablet { display: none; }
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.hidden-desktop { display: none; }
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Position Utilities
|
||||
======================================== */
|
||||
|
||||
.relative { position: relative; }
|
||||
.absolute { position: absolute; }
|
||||
.fixed { position: fixed; }
|
||||
.sticky { position: sticky; }
|
||||
|
||||
/* ========================================
|
||||
Text Align
|
||||
======================================== */
|
||||
|
||||
.text-left { text-align: left; }
|
||||
.text-center { text-align: center; }
|
||||
.text-right { text-align: right; }
|
||||
|
||||
/* ========================================
|
||||
Width & Height
|
||||
======================================== */
|
||||
|
||||
.w-full { width: 100%; }
|
||||
.h-full { height: 100%; }
|
||||
|
||||
.w-screen { width: 100vw; }
|
||||
.h-screen { height: 100vh; }
|
||||
|
||||
/* ========================================
|
||||
Overflow
|
||||
======================================== */
|
||||
|
||||
.overflow-auto { overflow: auto; }
|
||||
.overflow-hidden { overflow: hidden; }
|
||||
.overflow-scroll { overflow: scroll; }
|
||||
.overflow-x-auto { overflow-x: auto; }
|
||||
.overflow-y-auto { overflow-y: auto; }
|
||||
@ -1,45 +0,0 @@
|
||||
/* ========================================
|
||||
Navigation Bar (App Bar)
|
||||
======================================== */
|
||||
|
||||
.app-bar {
|
||||
background: var(--color-white, #FFFFFF);
|
||||
padding: 16px;
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1020;
|
||||
}
|
||||
|
||||
.app-bar__container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.app-bar__left,
|
||||
.app-bar__right {
|
||||
width: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.app-bar__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary, #212121);
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.app-bar__back {
|
||||
cursor: pointer;
|
||||
color: var(--color-text-primary, #212121);
|
||||
}
|
||||
|
||||
.app-bar__back:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
@ -1,210 +0,0 @@
|
||||
/**
|
||||
* KT AI 기반 소상공인 이벤트 자동 생성 서비스
|
||||
* CSS Reset
|
||||
*
|
||||
* Modern CSS Reset + Box-sizing Fix
|
||||
* 작성일: 2025-01-20
|
||||
*/
|
||||
|
||||
/* ========================================
|
||||
Box Sizing
|
||||
======================================== */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Remove Default Margins & Paddings
|
||||
======================================== */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
HTML & Body
|
||||
======================================== */
|
||||
html {
|
||||
font-size: 16px;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
line-height: 1.5;
|
||||
text-rendering: optimizeSpeed;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Typography Defaults
|
||||
======================================== */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-weight: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
p, h1, h2, h3, h4, h5, h6 {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Lists
|
||||
======================================== */
|
||||
ul, ol {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Links
|
||||
======================================== */
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Images & Media
|
||||
======================================== */
|
||||
img,
|
||||
picture,
|
||||
video,
|
||||
canvas,
|
||||
svg {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Forms
|
||||
======================================== */
|
||||
input,
|
||||
button,
|
||||
textarea,
|
||||
select {
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Remove default input styles */
|
||||
input[type="text"],
|
||||
input[type="email"],
|
||||
input[type="password"],
|
||||
input[type="tel"],
|
||||
input[type="number"],
|
||||
input[type="search"],
|
||||
textarea,
|
||||
select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
/* Remove spinner from number inputs */
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
/* Remove search cancel button */
|
||||
input[type="search"]::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Tables
|
||||
======================================== */
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Accessibility
|
||||
======================================== */
|
||||
|
||||
/* Remove all animations for users who prefer reduced motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Screen reader only content */
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Scrollbar (Webkit)
|
||||
======================================== */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #F5F5F5;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #D9D9D9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #9E9E9E;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Selection
|
||||
======================================== */
|
||||
::selection {
|
||||
background-color: rgba(227, 30, 36, 0.2);
|
||||
color: #1A1A1A;
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background-color: rgba(227, 30, 36, 0.2);
|
||||
color: #1A1A1A;
|
||||
}
|
||||
@ -1,273 +0,0 @@
|
||||
/**
|
||||
* KT AI 기반 소상공인 이벤트 자동 생성 서비스
|
||||
* CSS Variables (Design System)
|
||||
*
|
||||
* 참조: design/uiux/style-guide.md
|
||||
* 작성일: 2025-01-20
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* ========================================
|
||||
Primary Colors (KT Red)
|
||||
======================================== */
|
||||
--color-primary: #E31E24;
|
||||
--color-primary-light: #FF5A5F;
|
||||
--color-primary-dark: #C01015;
|
||||
--color-primary-50: #FFF5F5;
|
||||
--color-primary-100: #FFE5E6;
|
||||
--color-primary-200: #FFCCCE;
|
||||
|
||||
/* ========================================
|
||||
Secondary Colors (Accent)
|
||||
======================================== */
|
||||
--color-secondary: #FF6B6B;
|
||||
--color-secondary-light: #FF8787;
|
||||
--color-secondary-dark: #FF5252;
|
||||
--color-secondary-50: #FFF5F5;
|
||||
--color-secondary-100: #FFE3E3;
|
||||
--color-secondary-200: #FFC9C9;
|
||||
|
||||
/* ========================================
|
||||
Grayscale
|
||||
======================================== */
|
||||
--color-black: #1A1A1A; /* Gray-900 */
|
||||
--color-gray-800: #333333;
|
||||
--color-gray-700: #4A4A4A;
|
||||
--color-gray-600: #6B6B6B;
|
||||
--color-gray-500: #9E9E9E;
|
||||
--color-gray-400: #BDBDBD;
|
||||
--color-gray-300: #D9D9D9;
|
||||
--color-gray-200: #EEEEEE;
|
||||
--color-gray-100: #F5F5F5;
|
||||
--color-gray-50: #FAFAFA;
|
||||
--color-white: #FFFFFF;
|
||||
|
||||
/* ========================================
|
||||
Semantic Colors
|
||||
======================================== */
|
||||
--color-success: #00C853;
|
||||
--color-success-light: #5EFC82;
|
||||
--color-success-dark: #00A344;
|
||||
--color-success-bg: #E8F5E9;
|
||||
|
||||
--color-warning: #FFA000;
|
||||
--color-warning-light: #FFB333;
|
||||
--color-warning-dark: #CC8000;
|
||||
--color-warning-bg: #FFF8E1;
|
||||
|
||||
--color-error: #E31E24;
|
||||
--color-error-light: #FF5A5F;
|
||||
--color-error-dark: #C01015;
|
||||
--color-error-bg: #FFEBEE;
|
||||
|
||||
--color-info: #FF9800;
|
||||
--color-info-light: #FFB74D;
|
||||
--color-info-dark: #F57C00;
|
||||
--color-info-bg: #FFF3E0;
|
||||
|
||||
/* ========================================
|
||||
Gradient
|
||||
======================================== */
|
||||
--gradient-primary: linear-gradient(135deg, #E31E24 0%, #FF5A5F 100%);
|
||||
--gradient-secondary: linear-gradient(135deg, #FF6B6B 0%, #FF8787 100%);
|
||||
--gradient-success: linear-gradient(135deg, #00C853 0%, #5EFC82 100%);
|
||||
--gradient-warning: linear-gradient(135deg, #FFA000 0%, #FFB333 100%);
|
||||
|
||||
/* ========================================
|
||||
Background Colors
|
||||
======================================== */
|
||||
--bg-primary: #FFFFFF;
|
||||
--bg-secondary: #FAFAFA;
|
||||
--bg-tertiary: #F5F5F5;
|
||||
--bg-overlay: rgba(0, 0, 0, 0.5);
|
||||
|
||||
/* ========================================
|
||||
Border Colors
|
||||
======================================== */
|
||||
--border-light: #EEEEEE;
|
||||
--border-medium: #D9D9D9;
|
||||
--border-dark: #BDBDBD;
|
||||
|
||||
/* ========================================
|
||||
Typography - Font Family
|
||||
======================================== */
|
||||
--font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', system-ui, sans-serif;
|
||||
|
||||
/* ========================================
|
||||
Typography - Font Sizes (Mobile First)
|
||||
======================================== */
|
||||
--font-size-display: 28px; /* Display (메인 타이틀) */
|
||||
--font-size-h1: 24px; /* H1 (화면 제목) */
|
||||
--font-size-h2: 20px; /* H2 (섹션 제목) */
|
||||
--font-size-h3: 18px; /* H3 (카드 제목) */
|
||||
--font-size-body-large: 16px; /* Body-Large (큰 본문) */
|
||||
--font-size-body-medium: 14px; /* Body-Medium (기본 본문) */
|
||||
--font-size-body-small: 12px; /* Body-Small (작은 본문) */
|
||||
--font-size-button: 16px; /* Button (버튼 레이블) */
|
||||
|
||||
/* ========================================
|
||||
Typography - Line Heights
|
||||
======================================== */
|
||||
--line-height-display: 1.3; /* 36px */
|
||||
--line-height-h1: 1.3; /* 31px */
|
||||
--line-height-h2: 1.4; /* 28px */
|
||||
--line-height-h3: 1.4; /* 25px */
|
||||
--line-height-body-large: 1.5; /* 24px */
|
||||
--line-height-body-medium: 1.5; /* 21px */
|
||||
--line-height-body-small: 1.5; /* 18px */
|
||||
--line-height-button: 1.5; /* 24px */
|
||||
|
||||
/* ========================================
|
||||
Typography - Font Weights
|
||||
======================================== */
|
||||
--font-weight-regular: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
|
||||
/* ========================================
|
||||
Typography - Letter Spacing
|
||||
======================================== */
|
||||
--letter-spacing-display: -0.5px;
|
||||
--letter-spacing-h1: -0.3px;
|
||||
--letter-spacing-h2: -0.2px;
|
||||
--letter-spacing-h3: 0px;
|
||||
--letter-spacing-body: 0px;
|
||||
|
||||
/* ========================================
|
||||
Spacing (4px Grid System)
|
||||
======================================== */
|
||||
--spacing-xs: 4px; /* Extra Small */
|
||||
--spacing-s: 8px; /* Small */
|
||||
--spacing-m: 16px; /* Medium */
|
||||
--spacing-l: 24px; /* Large */
|
||||
--spacing-xl: 32px; /* Extra Large */
|
||||
--spacing-2xl: 48px; /* 2X Large */
|
||||
|
||||
/* ========================================
|
||||
Border Radius
|
||||
======================================== */
|
||||
--radius-xs: 4px;
|
||||
--radius-s: 8px;
|
||||
--radius-m: 12px;
|
||||
--radius-l: 16px;
|
||||
--radius-xl: 24px;
|
||||
--radius-pill: 9999px; /* Pill 형태 */
|
||||
|
||||
/* ========================================
|
||||
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);
|
||||
--shadow-primary: 0 2px 4px rgba(227, 30, 36, 0.2);
|
||||
--shadow-secondary: 0 0 0 4px rgba(255, 107, 107, 0.15);
|
||||
--shadow-error: 0 0 0 4px rgba(227, 30, 36, 0.15);
|
||||
|
||||
/* ========================================
|
||||
Z-Index Scale
|
||||
======================================== */
|
||||
--z-base: 0;
|
||||
--z-dropdown: 100;
|
||||
--z-sticky: 200;
|
||||
--z-fixed: 300;
|
||||
--z-modal-backdrop: 400;
|
||||
--z-modal: 500;
|
||||
--z-toast: 600;
|
||||
--z-tooltip: 700;
|
||||
|
||||
/* ========================================
|
||||
Transitions
|
||||
======================================== */
|
||||
--transition-instant: 0ms;
|
||||
--transition-very-fast: 100ms;
|
||||
--transition-fast: 200ms;
|
||||
--transition-normal: 300ms;
|
||||
--transition-slow: 500ms;
|
||||
|
||||
/* ========================================
|
||||
Easing
|
||||
======================================== */
|
||||
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--ease-linear: linear;
|
||||
|
||||
/* ========================================
|
||||
Layout - Breakpoints
|
||||
======================================== */
|
||||
--breakpoint-mobile: 320px;
|
||||
--breakpoint-tablet: 768px;
|
||||
--breakpoint-desktop: 1024px;
|
||||
|
||||
/* ========================================
|
||||
Layout - Container
|
||||
======================================== */
|
||||
--container-mobile: calc(100% - 40px); /* 양쪽 20px 마진 */
|
||||
--container-tablet: calc(100% - 80px); /* 양쪽 40px 마진 */
|
||||
--container-desktop: 1200px; /* 최대 너비 */
|
||||
|
||||
/* ========================================
|
||||
Layout - Gutters
|
||||
======================================== */
|
||||
--gutter-mobile: 16px;
|
||||
--gutter-tablet: 24px;
|
||||
--gutter-desktop: 32px;
|
||||
|
||||
/* ========================================
|
||||
Component - Button Heights
|
||||
======================================== */
|
||||
--button-height-large: 48px;
|
||||
--button-height-medium: 44px;
|
||||
--button-height-small: 36px;
|
||||
|
||||
/* ========================================
|
||||
Component - Input Heights
|
||||
======================================== */
|
||||
--input-height: 48px;
|
||||
--textarea-min-height: 120px;
|
||||
|
||||
/* ========================================
|
||||
Component - Touch Target
|
||||
======================================== */
|
||||
--touch-target-min: 44px; /* WCAG 2.1 AA */
|
||||
--touch-target-recommended: 48px;
|
||||
|
||||
/* ========================================
|
||||
Component - Bottom Navigation
|
||||
======================================== */
|
||||
--bottom-nav-height: 60px;
|
||||
|
||||
/* ========================================
|
||||
Component - App Bar
|
||||
======================================== */
|
||||
--app-bar-height: 56px;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Responsive Typography (Tablet)
|
||||
======================================== */
|
||||
@media (min-width: 768px) {
|
||||
:root {
|
||||
--font-size-display: 32px; /* +4px */
|
||||
--font-size-h1: 28px; /* +4px */
|
||||
--font-size-h2: 22px; /* +2px */
|
||||
--font-size-h3: 20px; /* +2px */
|
||||
--font-size-body-large: 18px; /* +2px */
|
||||
--font-size-body-medium: 16px; /* +2px */
|
||||
--font-size-body-small: 14px; /* +2px */
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Responsive Typography (Desktop)
|
||||
======================================== */
|
||||
@media (min-width: 1024px) {
|
||||
:root {
|
||||
--font-size-display: 36px; /* +8px */
|
||||
--font-size-h1: 32px; /* +8px */
|
||||
--font-size-h2: 24px; /* +4px */
|
||||
--font-size-h3: 20px; /* +2px */
|
||||
}
|
||||
}
|
||||
375
TEMP_BACKUP/prototype/js/common.js
vendored
@ -1,375 +0,0 @@
|
||||
/**
|
||||
* KT AI 기반 소상공인 이벤트 자동 생성 서비스
|
||||
* Common JavaScript Utilities
|
||||
*
|
||||
* 작성일: 2025-01-20
|
||||
*/
|
||||
|
||||
// ========================================
|
||||
// DOM 유틸리티
|
||||
// ========================================
|
||||
|
||||
const $ = (selector, context = document) => context.querySelector(selector);
|
||||
const $$ = (selector, context = document) => Array.from(context.querySelectorAll(selector));
|
||||
|
||||
const createElement = (tag, className = '', attributes = {}) => {
|
||||
const element = document.createElement(tag);
|
||||
if (className) element.className = className;
|
||||
Object.entries(attributes).forEach(([key, value]) => {
|
||||
element.setAttribute(key, value);
|
||||
});
|
||||
return element;
|
||||
};
|
||||
|
||||
const addClass = (element, ...classes) => {
|
||||
if (!element) return;
|
||||
element.classList.add(...classes);
|
||||
};
|
||||
|
||||
const removeClass = (element, ...classes) => {
|
||||
if (!element) return;
|
||||
element.classList.remove(...classes);
|
||||
};
|
||||
|
||||
const toggleClass = (element, className, force) => {
|
||||
if (!element) return;
|
||||
return element.classList.toggle(className, force);
|
||||
};
|
||||
|
||||
const hasClass = (element, className) => {
|
||||
if (!element) return false;
|
||||
return element.classList.contains(className);
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// 이벤트 유틸리티
|
||||
// ========================================
|
||||
|
||||
const on = (element, event, handler, options = {}) => {
|
||||
if (!element) return;
|
||||
element.addEventListener(event, handler, options);
|
||||
};
|
||||
|
||||
const off = (element, event, handler, options = {}) => {
|
||||
if (!element) return;
|
||||
element.removeEventListener(event, handler, options);
|
||||
};
|
||||
|
||||
const delegate = (parent, eventType, selector, handler) => {
|
||||
on(parent, eventType, (event) => {
|
||||
const target = event.target.closest(selector);
|
||||
if (target && parent.contains(target)) {
|
||||
handler.call(target, event);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const once = (element, event, handler) => {
|
||||
on(element, event, handler, { once: true });
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// 애니메이션 유틸리티
|
||||
// ========================================
|
||||
|
||||
const fadeIn = (element, duration = 200) => {
|
||||
if (!element) return Promise.resolve();
|
||||
|
||||
element.style.opacity = '0';
|
||||
element.style.display = 'block';
|
||||
|
||||
return new Promise((resolve) => {
|
||||
requestAnimationFrame(() => {
|
||||
element.style.transition = `opacity ${duration}ms ease-out`;
|
||||
element.style.opacity = '1';
|
||||
|
||||
setTimeout(() => {
|
||||
element.style.transition = '';
|
||||
resolve();
|
||||
}, duration);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const fadeOut = (element, duration = 200) => {
|
||||
if (!element) return Promise.resolve();
|
||||
|
||||
element.style.transition = `opacity ${duration}ms ease-out`;
|
||||
element.style.opacity = '0';
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
element.style.display = 'none';
|
||||
element.style.transition = '';
|
||||
resolve();
|
||||
}, duration);
|
||||
});
|
||||
};
|
||||
|
||||
const slideUp = (element, duration = 300) => {
|
||||
if (!element) return Promise.resolve();
|
||||
|
||||
element.style.overflow = 'hidden';
|
||||
element.style.transition = `height ${duration}ms ease-out, opacity ${duration}ms ease-out`;
|
||||
element.style.height = element.offsetHeight + 'px';
|
||||
|
||||
return new Promise((resolve) => {
|
||||
requestAnimationFrame(() => {
|
||||
element.style.height = '0';
|
||||
element.style.opacity = '0';
|
||||
|
||||
setTimeout(() => {
|
||||
element.style.display = 'none';
|
||||
element.style.overflow = '';
|
||||
element.style.transition = '';
|
||||
element.style.height = '';
|
||||
element.style.opacity = '';
|
||||
resolve();
|
||||
}, duration);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const slideDown = (element, duration = 300) => {
|
||||
if (!element) return Promise.resolve();
|
||||
|
||||
element.style.display = 'block';
|
||||
element.style.overflow = 'hidden';
|
||||
element.style.height = '0';
|
||||
element.style.opacity = '0';
|
||||
|
||||
const height = element.scrollHeight;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
requestAnimationFrame(() => {
|
||||
element.style.transition = `height ${duration}ms ease-out, opacity ${duration}ms ease-out`;
|
||||
element.style.height = height + 'px';
|
||||
element.style.opacity = '1';
|
||||
|
||||
setTimeout(() => {
|
||||
element.style.overflow = '';
|
||||
element.style.transition = '';
|
||||
element.style.height = '';
|
||||
element.style.opacity = '';
|
||||
resolve();
|
||||
}, duration);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// 데이터 유틸리티
|
||||
// ========================================
|
||||
|
||||
const debounce = (func, wait = 300) => {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func.apply(this, args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
};
|
||||
|
||||
const throttle = (func, limit = 300) => {
|
||||
let inThrottle;
|
||||
return function executedFunction(...args) {
|
||||
if (!inThrottle) {
|
||||
func.apply(this, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => (inThrottle = false), limit);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const formatNumber = (num) => {
|
||||
if (num >= 1000000) {
|
||||
return (num / 1000000).toFixed(1) + 'M';
|
||||
}
|
||||
if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'K';
|
||||
}
|
||||
return num.toString();
|
||||
};
|
||||
|
||||
const 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}`;
|
||||
};
|
||||
|
||||
const formatDateTime = (date) => {
|
||||
const d = new Date(date);
|
||||
const hours = String(d.getHours()).padStart(2, '0');
|
||||
const minutes = String(d.getMinutes()).padStart(2, '0');
|
||||
return `${formatDate(date)} ${hours}:${minutes}`;
|
||||
};
|
||||
|
||||
const getRelativeTime = (date) => {
|
||||
const now = new Date();
|
||||
const target = new Date(date);
|
||||
const diff = Math.floor((now - target) / 1000);
|
||||
|
||||
if (diff < 60) return '방금 전';
|
||||
if (diff < 3600) return `${Math.floor(diff / 60)}분 전`;
|
||||
if (diff < 86400) return `${Math.floor(diff / 3600)}시간 전`;
|
||||
if (diff < 604800) return `${Math.floor(diff / 86400)}일 전`;
|
||||
|
||||
return formatDate(date);
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// 로컬 스토리지 유틸리티
|
||||
// ========================================
|
||||
|
||||
const storage = {
|
||||
set: (key, value) => {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Storage set error:', error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
get: (key, defaultValue = null) => {
|
||||
try {
|
||||
const item = localStorage.getItem(key);
|
||||
return item ? JSON.parse(item) : defaultValue;
|
||||
} catch (error) {
|
||||
console.error('Storage get error:', error);
|
||||
return defaultValue;
|
||||
}
|
||||
},
|
||||
|
||||
remove: (key) => {
|
||||
try {
|
||||
localStorage.removeItem(key);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Storage remove error:', error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
clear: () => {
|
||||
try {
|
||||
localStorage.clear();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Storage clear error:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// URL 유틸리티
|
||||
// ========================================
|
||||
|
||||
const navigate = (url) => {
|
||||
window.location.href = url;
|
||||
};
|
||||
|
||||
const getQueryParam = (name) => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
return params.get(name);
|
||||
};
|
||||
|
||||
const setQueryParam = (name, value) => {
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set(name, value);
|
||||
window.history.pushState({}, '', url);
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// 유효성 검사 유틸리티
|
||||
// ========================================
|
||||
|
||||
const validators = {
|
||||
email: (value) => {
|
||||
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return re.test(value);
|
||||
},
|
||||
|
||||
phone: (value) => {
|
||||
const re = /^01[0-9]-?[0-9]{3,4}-?[0-9]{4}$/;
|
||||
return re.test(value.replace(/[^0-9]/g, ''));
|
||||
},
|
||||
|
||||
required: (value) => {
|
||||
return value !== null && value !== undefined && value.trim() !== '';
|
||||
},
|
||||
|
||||
minLength: (value, length) => {
|
||||
return value.length >= length;
|
||||
},
|
||||
|
||||
maxLength: (value, length) => {
|
||||
return value.length <= length;
|
||||
},
|
||||
|
||||
number: (value) => {
|
||||
return !isNaN(value) && !isNaN(parseFloat(value));
|
||||
},
|
||||
|
||||
url: (value) => {
|
||||
try {
|
||||
new URL(value);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// 전역 공개
|
||||
// ========================================
|
||||
|
||||
window.KTEvent = {
|
||||
// DOM
|
||||
$,
|
||||
$$,
|
||||
createElement,
|
||||
addClass,
|
||||
removeClass,
|
||||
toggleClass,
|
||||
hasClass,
|
||||
|
||||
// Events
|
||||
on,
|
||||
off,
|
||||
delegate,
|
||||
once,
|
||||
|
||||
// Animation
|
||||
fadeIn,
|
||||
fadeOut,
|
||||
slideUp,
|
||||
slideDown,
|
||||
|
||||
// Data
|
||||
debounce,
|
||||
throttle,
|
||||
formatNumber,
|
||||
formatDate,
|
||||
formatDateTime,
|
||||
getRelativeTime,
|
||||
|
||||
// Storage
|
||||
storage,
|
||||
|
||||
// URL
|
||||
navigate,
|
||||
getQueryParam,
|
||||
setQueryParam,
|
||||
|
||||
// Validation
|
||||
validators
|
||||
};
|
||||
315
TEMP_BACKUP/prototype/js/form.js
vendored
@ -1,315 +0,0 @@
|
||||
/**
|
||||
* KT AI 기반 소상공인 이벤트 자동 생성 서비스
|
||||
* Form Validation JavaScript
|
||||
*
|
||||
* 작성일: 2025-01-20
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const { $, $$, on, addClass, removeClass, validators, createElement } = window.KTEvent;
|
||||
|
||||
// ========================================
|
||||
// Form Validator
|
||||
// ========================================
|
||||
|
||||
class FormValidator {
|
||||
constructor(form, options = {}) {
|
||||
if (!form) return;
|
||||
|
||||
this.form = form;
|
||||
this.options = {
|
||||
validateOnBlur: true,
|
||||
validateOnInput: false,
|
||||
scrollToError: true,
|
||||
...options
|
||||
};
|
||||
|
||||
this.fields = new Map();
|
||||
this.errors = new Map();
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
addField(name, rules) {
|
||||
this.fields.set(name, rules);
|
||||
return this;
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
// Form submit
|
||||
on(this.form, 'submit', (e) => {
|
||||
e.preventDefault();
|
||||
if (this.validate()) {
|
||||
this.onSubmit();
|
||||
}
|
||||
});
|
||||
|
||||
// Field validation
|
||||
this.fields.forEach((rules, name) => {
|
||||
const field = this.form.elements[name];
|
||||
if (!field) return;
|
||||
|
||||
if (this.options.validateOnBlur) {
|
||||
on(field, 'blur', () => {
|
||||
this.validateField(name);
|
||||
});
|
||||
}
|
||||
|
||||
if (this.options.validateOnInput) {
|
||||
on(field, 'input', () => {
|
||||
if (this.errors.has(name)) {
|
||||
this.validateField(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
validateField(name) {
|
||||
const field = this.form.elements[name];
|
||||
const rules = this.fields.get(name);
|
||||
|
||||
if (!field || !rules) return true;
|
||||
|
||||
const value = field.value.trim();
|
||||
let isValid = true;
|
||||
let errorMessage = '';
|
||||
|
||||
// Required
|
||||
if (rules.required && !validators.required(value)) {
|
||||
isValid = false;
|
||||
errorMessage = rules.messages?.required || '필수 입력 항목입니다.';
|
||||
}
|
||||
|
||||
// Email
|
||||
if (isValid && rules.email && value && !validators.email(value)) {
|
||||
isValid = false;
|
||||
errorMessage = rules.messages?.email || '올바른 이메일 형식이 아닙니다.';
|
||||
}
|
||||
|
||||
// Phone
|
||||
if (isValid && rules.phone && value && !validators.phone(value)) {
|
||||
isValid = false;
|
||||
errorMessage = rules.messages?.phone || '올바른 전화번호 형식이 아닙니다.';
|
||||
}
|
||||
|
||||
// Min Length
|
||||
if (isValid && rules.minLength && value && !validators.minLength(value, rules.minLength)) {
|
||||
isValid = false;
|
||||
errorMessage = rules.messages?.minLength || `최소 ${rules.minLength}자 이상 입력해주세요.`;
|
||||
}
|
||||
|
||||
// Max Length
|
||||
if (isValid && rules.maxLength && value && !validators.maxLength(value, rules.maxLength)) {
|
||||
isValid = false;
|
||||
errorMessage = rules.messages?.maxLength || `최대 ${rules.maxLength}자까지 입력 가능합니다.`;
|
||||
}
|
||||
|
||||
// Number
|
||||
if (isValid && rules.number && value && !validators.number(value)) {
|
||||
isValid = false;
|
||||
errorMessage = rules.messages?.number || '숫자만 입력 가능합니다.';
|
||||
}
|
||||
|
||||
// URL
|
||||
if (isValid && rules.url && value && !validators.url(value)) {
|
||||
isValid = false;
|
||||
errorMessage = rules.messages?.url || '올바른 URL 형식이 아닙니다.';
|
||||
}
|
||||
|
||||
// Custom validator
|
||||
if (isValid && rules.custom && !rules.custom(value, field)) {
|
||||
isValid = false;
|
||||
errorMessage = rules.messages?.custom || '유효하지 않은 값입니다.';
|
||||
}
|
||||
|
||||
// Update UI
|
||||
if (isValid) {
|
||||
this.clearError(name);
|
||||
} else {
|
||||
this.setError(name, errorMessage);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
validate() {
|
||||
let isValid = true;
|
||||
let firstErrorField = null;
|
||||
|
||||
this.fields.forEach((rules, name) => {
|
||||
if (!this.validateField(name)) {
|
||||
isValid = false;
|
||||
if (!firstErrorField) {
|
||||
firstErrorField = this.form.elements[name];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Scroll to first error
|
||||
if (!isValid && firstErrorField && this.options.scrollToError) {
|
||||
firstErrorField.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
firstErrorField.focus();
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
setError(name, message) {
|
||||
const field = this.form.elements[name];
|
||||
if (!field) return;
|
||||
|
||||
this.errors.set(name, message);
|
||||
|
||||
const formGroup = field.closest('.form-group');
|
||||
if (!formGroup) return;
|
||||
|
||||
addClass(field, 'input-error');
|
||||
|
||||
let errorElement = $('.form-error', formGroup);
|
||||
if (!errorElement) {
|
||||
errorElement = createElement('span', 'form-error');
|
||||
formGroup.appendChild(errorElement);
|
||||
}
|
||||
|
||||
errorElement.textContent = message;
|
||||
}
|
||||
|
||||
clearError(name) {
|
||||
const field = this.form.elements[name];
|
||||
if (!field) return;
|
||||
|
||||
this.errors.delete(name);
|
||||
|
||||
const formGroup = field.closest('.form-group');
|
||||
if (!formGroup) return;
|
||||
|
||||
removeClass(field, 'input-error');
|
||||
|
||||
const errorElement = $('.form-error', formGroup);
|
||||
if (errorElement) {
|
||||
errorElement.remove();
|
||||
}
|
||||
}
|
||||
|
||||
clearAllErrors() {
|
||||
this.errors.clear();
|
||||
|
||||
$$('.input-error', this.form).forEach(field => {
|
||||
removeClass(field, 'input-error');
|
||||
});
|
||||
|
||||
$$('.form-error', this.form).forEach(error => {
|
||||
error.remove();
|
||||
});
|
||||
}
|
||||
|
||||
getValues() {
|
||||
const values = {};
|
||||
|
||||
this.fields.forEach((rules, name) => {
|
||||
const field = this.form.elements[name];
|
||||
if (field) {
|
||||
if (field.type === 'checkbox') {
|
||||
values[name] = field.checked;
|
||||
} else if (field.type === 'radio') {
|
||||
const checked = this.form.querySelector(`input[name="${name}"]:checked`);
|
||||
values[name] = checked ? checked.value : null;
|
||||
} else {
|
||||
values[name] = field.value.trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.form.reset();
|
||||
this.clearAllErrors();
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
// Override this method
|
||||
console.log('Form submitted:', this.getValues());
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Character Counter
|
||||
// ========================================
|
||||
|
||||
class CharacterCounter {
|
||||
constructor(textarea, maxLength) {
|
||||
this.textarea = textarea;
|
||||
this.maxLength = maxLength;
|
||||
this.counter = null;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
const formGroup = this.textarea.closest('.form-group');
|
||||
if (!formGroup) return;
|
||||
|
||||
this.counter = createElement('span', 'form-helper');
|
||||
this.counter.style.textAlign = 'right';
|
||||
formGroup.appendChild(this.counter);
|
||||
|
||||
this.updateCounter();
|
||||
|
||||
on(this.textarea, 'input', () => {
|
||||
this.updateCounter();
|
||||
});
|
||||
}
|
||||
|
||||
updateCounter() {
|
||||
const length = this.textarea.value.length;
|
||||
this.counter.textContent = `${length} / ${this.maxLength}`;
|
||||
|
||||
if (length > this.maxLength) {
|
||||
this.counter.style.color = 'var(--color-error)';
|
||||
} else {
|
||||
this.counter.style.color = 'var(--color-gray-500)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Auto Initialize
|
||||
// ========================================
|
||||
|
||||
function initForms() {
|
||||
// Character counters
|
||||
$$('textarea[maxlength]').forEach(textarea => {
|
||||
const maxLength = parseInt(textarea.getAttribute('maxlength'));
|
||||
if (maxLength > 0) {
|
||||
new CharacterCounter(textarea, maxLength);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize on DOMContentLoaded
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initForms);
|
||||
} else {
|
||||
initForms();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Export
|
||||
// ========================================
|
||||
|
||||
window.KTEvent = window.KTEvent || {};
|
||||
Object.assign(window.KTEvent, {
|
||||
FormValidator,
|
||||
CharacterCounter
|
||||
});
|
||||
|
||||
})();
|
||||
418
TEMP_BACKUP/prototype/js/modal.js
vendored
@ -1,418 +0,0 @@
|
||||
/**
|
||||
* KT AI 기반 소상공인 이벤트 자동 생성 서비스
|
||||
* Modal JavaScript
|
||||
*
|
||||
* 작성일: 2025-01-20
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const { $, createElement, addClass, removeClass, on, fadeIn, fadeOut } = window.KTEvent;
|
||||
|
||||
// ========================================
|
||||
// Modal
|
||||
// ========================================
|
||||
|
||||
class Modal {
|
||||
constructor(options = {}) {
|
||||
this.options = {
|
||||
title: '',
|
||||
content: '',
|
||||
size: 'medium', // small, medium, large, fullscreen
|
||||
showClose: true,
|
||||
footer: null,
|
||||
onOpen: null,
|
||||
onClose: null,
|
||||
...options
|
||||
};
|
||||
|
||||
this.backdrop = null;
|
||||
this.modal = null;
|
||||
this.isOpen = false;
|
||||
|
||||
this.create();
|
||||
}
|
||||
|
||||
create() {
|
||||
// Create backdrop
|
||||
this.backdrop = createElement('div', 'modal-backdrop');
|
||||
|
||||
// Create modal
|
||||
this.modal = createElement('div', `modal modal--${this.options.size}`);
|
||||
|
||||
// Header
|
||||
if (this.options.title || this.options.showClose) {
|
||||
const header = createElement('div', 'modal__header');
|
||||
|
||||
if (this.options.title) {
|
||||
const title = createElement('h3', 'modal__title');
|
||||
title.textContent = this.options.title;
|
||||
header.appendChild(title);
|
||||
}
|
||||
|
||||
if (this.options.showClose) {
|
||||
const closeBtn = createElement('button', 'modal__close');
|
||||
closeBtn.innerHTML = '×';
|
||||
closeBtn.setAttribute('aria-label', '닫기');
|
||||
on(closeBtn, 'click', () => this.close());
|
||||
header.appendChild(closeBtn);
|
||||
}
|
||||
|
||||
this.modal.appendChild(header);
|
||||
}
|
||||
|
||||
// Body
|
||||
const body = createElement('div', 'modal__body');
|
||||
if (typeof this.options.content === 'string') {
|
||||
body.innerHTML = this.options.content;
|
||||
} else if (this.options.content instanceof HTMLElement) {
|
||||
body.appendChild(this.options.content);
|
||||
}
|
||||
this.modal.appendChild(body);
|
||||
|
||||
// Footer
|
||||
if (this.options.footer) {
|
||||
const footer = createElement('div', 'modal__footer');
|
||||
footer.innerHTML = this.options.footer;
|
||||
this.modal.appendChild(footer);
|
||||
}
|
||||
|
||||
this.backdrop.appendChild(this.modal);
|
||||
|
||||
// Close on backdrop click
|
||||
on(this.backdrop, 'click', (e) => {
|
||||
if (e.target === this.backdrop) {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
||||
// Close on ESC key
|
||||
this.handleEscKey = (e) => {
|
||||
if (e.key === 'Escape' && this.isOpen) {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
open() {
|
||||
if (this.isOpen) return;
|
||||
|
||||
document.body.appendChild(this.backdrop);
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
// Animation
|
||||
requestAnimationFrame(() => {
|
||||
fadeIn(this.backdrop, 200);
|
||||
});
|
||||
|
||||
this.isOpen = true;
|
||||
|
||||
on(document, 'keydown', this.handleEscKey);
|
||||
|
||||
if (this.options.onOpen) {
|
||||
this.options.onOpen(this);
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
if (!this.isOpen) return;
|
||||
|
||||
fadeOut(this.backdrop, 200).then(() => {
|
||||
if (this.backdrop.parentNode) {
|
||||
document.body.removeChild(this.backdrop);
|
||||
}
|
||||
document.body.style.overflow = '';
|
||||
});
|
||||
|
||||
this.isOpen = false;
|
||||
|
||||
document.removeEventListener('keydown', this.handleEscKey);
|
||||
|
||||
if (this.options.onClose) {
|
||||
this.options.onClose(this);
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.close();
|
||||
this.backdrop = null;
|
||||
this.modal = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Bottom Sheet
|
||||
// ========================================
|
||||
|
||||
class BottomSheet {
|
||||
constructor(options = {}) {
|
||||
this.options = {
|
||||
title: '',
|
||||
content: '',
|
||||
showClose: true,
|
||||
footer: null,
|
||||
onOpen: null,
|
||||
onClose: null,
|
||||
...options
|
||||
};
|
||||
|
||||
this.backdrop = null;
|
||||
this.sheet = null;
|
||||
this.isOpen = false;
|
||||
|
||||
this.create();
|
||||
}
|
||||
|
||||
create() {
|
||||
// Create backdrop
|
||||
this.backdrop = createElement('div', 'modal-backdrop');
|
||||
|
||||
// Create bottom sheet
|
||||
this.sheet = createElement('div', 'bottom-sheet');
|
||||
|
||||
// Handle
|
||||
const handle = createElement('div', 'bottom-sheet__handle');
|
||||
this.sheet.appendChild(handle);
|
||||
|
||||
// Header
|
||||
if (this.options.title || this.options.showClose) {
|
||||
const header = createElement('div', 'bottom-sheet__header');
|
||||
|
||||
if (this.options.title) {
|
||||
const title = createElement('h3', 'bottom-sheet__title');
|
||||
title.textContent = this.options.title;
|
||||
header.appendChild(title);
|
||||
}
|
||||
|
||||
if (this.options.showClose) {
|
||||
const closeBtn = createElement('button', 'bottom-sheet__close');
|
||||
closeBtn.innerHTML = '×';
|
||||
closeBtn.setAttribute('aria-label', '닫기');
|
||||
on(closeBtn, 'click', () => this.close());
|
||||
header.appendChild(closeBtn);
|
||||
}
|
||||
|
||||
this.sheet.appendChild(header);
|
||||
}
|
||||
|
||||
// Body
|
||||
const body = createElement('div', 'bottom-sheet__body');
|
||||
if (typeof this.options.content === 'string') {
|
||||
body.innerHTML = this.options.content;
|
||||
} else if (this.options.content instanceof HTMLElement) {
|
||||
body.appendChild(this.options.content);
|
||||
}
|
||||
this.sheet.appendChild(body);
|
||||
|
||||
// Footer
|
||||
if (this.options.footer) {
|
||||
const footer = createElement('div', 'bottom-sheet__footer');
|
||||
footer.innerHTML = this.options.footer;
|
||||
this.sheet.appendChild(footer);
|
||||
}
|
||||
|
||||
this.backdrop.appendChild(this.sheet);
|
||||
|
||||
// Close on backdrop click
|
||||
on(this.backdrop, 'click', (e) => {
|
||||
if (e.target === this.backdrop) {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
open() {
|
||||
if (this.isOpen) return;
|
||||
|
||||
document.body.appendChild(this.backdrop);
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
fadeIn(this.backdrop, 200);
|
||||
});
|
||||
|
||||
this.isOpen = true;
|
||||
|
||||
if (this.options.onOpen) {
|
||||
this.options.onOpen(this);
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
if (!this.isOpen) return;
|
||||
|
||||
fadeOut(this.backdrop, 200).then(() => {
|
||||
if (this.backdrop.parentNode) {
|
||||
document.body.removeChild(this.backdrop);
|
||||
}
|
||||
document.body.style.overflow = '';
|
||||
});
|
||||
|
||||
this.isOpen = false;
|
||||
|
||||
if (this.options.onClose) {
|
||||
this.options.onClose(this);
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.close();
|
||||
this.backdrop = null;
|
||||
this.sheet = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Toast
|
||||
// ========================================
|
||||
|
||||
class Toast {
|
||||
static show(message, options = {}) {
|
||||
const defaults = {
|
||||
type: 'default', // default, success, warning, error, info
|
||||
duration: 3000,
|
||||
position: 'bottom', // top, bottom
|
||||
icon: null
|
||||
};
|
||||
|
||||
const config = { ...defaults, ...options };
|
||||
|
||||
// Create toast
|
||||
const toast = createElement('div', `toast toast--${config.type} toast--${config.position}`);
|
||||
|
||||
// Icon
|
||||
if (config.icon) {
|
||||
const icon = createElement('span', 'toast__icon');
|
||||
icon.innerHTML = config.icon;
|
||||
toast.appendChild(icon);
|
||||
}
|
||||
|
||||
// Message
|
||||
const msg = createElement('span', 'toast__message');
|
||||
msg.textContent = message;
|
||||
toast.appendChild(msg);
|
||||
|
||||
// Close button
|
||||
const closeBtn = createElement('button', 'toast__close');
|
||||
closeBtn.innerHTML = '×';
|
||||
closeBtn.setAttribute('aria-label', '닫기');
|
||||
toast.appendChild(closeBtn);
|
||||
|
||||
// Add to DOM
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// Animation
|
||||
requestAnimationFrame(() => {
|
||||
fadeIn(toast, 200);
|
||||
});
|
||||
|
||||
// Auto close
|
||||
const timeout = setTimeout(() => {
|
||||
close();
|
||||
}, config.duration);
|
||||
|
||||
// Close function
|
||||
const close = () => {
|
||||
clearTimeout(timeout);
|
||||
fadeOut(toast, 200).then(() => {
|
||||
if (toast.parentNode) {
|
||||
document.body.removeChild(toast);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Close on button click
|
||||
on(closeBtn, 'click', close);
|
||||
|
||||
return { close };
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Dialog (Confirm)
|
||||
// ========================================
|
||||
|
||||
class Dialog {
|
||||
static confirm(options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
const config = {
|
||||
title: '확인',
|
||||
message: '',
|
||||
confirmText: '확인',
|
||||
cancelText: '취소',
|
||||
...options
|
||||
};
|
||||
|
||||
const backdrop = createElement('div', 'modal-backdrop');
|
||||
const dialog = createElement('div', 'dialog');
|
||||
|
||||
// Header
|
||||
const header = createElement('div', 'dialog__header');
|
||||
const title = createElement('h3', 'dialog__title');
|
||||
title.textContent = config.title;
|
||||
header.appendChild(title);
|
||||
dialog.appendChild(header);
|
||||
|
||||
// Body
|
||||
const body = createElement('div', 'dialog__body');
|
||||
const message = createElement('p', 'dialog__message');
|
||||
message.textContent = config.message;
|
||||
body.appendChild(message);
|
||||
dialog.appendChild(body);
|
||||
|
||||
// Footer
|
||||
const footer = createElement('div', 'dialog__footer');
|
||||
|
||||
const cancelBtn = createElement('button', 'btn btn-secondary btn-medium');
|
||||
cancelBtn.textContent = config.cancelText;
|
||||
on(cancelBtn, 'click', () => {
|
||||
close(false);
|
||||
});
|
||||
footer.appendChild(cancelBtn);
|
||||
|
||||
const confirmBtn = createElement('button', 'btn btn-primary btn-medium');
|
||||
confirmBtn.textContent = config.confirmText;
|
||||
on(confirmBtn, 'click', () => {
|
||||
close(true);
|
||||
});
|
||||
footer.appendChild(confirmBtn);
|
||||
|
||||
dialog.appendChild(footer);
|
||||
backdrop.appendChild(dialog);
|
||||
|
||||
// Close function
|
||||
const close = (result) => {
|
||||
fadeOut(backdrop, 200).then(() => {
|
||||
if (backdrop.parentNode) {
|
||||
document.body.removeChild(backdrop);
|
||||
}
|
||||
document.body.style.overflow = '';
|
||||
resolve(result);
|
||||
});
|
||||
};
|
||||
|
||||
// Add to DOM
|
||||
document.body.appendChild(backdrop);
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
fadeIn(backdrop, 200);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Export
|
||||
// ========================================
|
||||
|
||||
window.KTEvent = window.KTEvent || {};
|
||||
Object.assign(window.KTEvent, {
|
||||
Modal,
|
||||
BottomSheet,
|
||||
Toast,
|
||||
Dialog
|
||||
});
|
||||
|
||||
})();
|
||||
296
TEMP_BACKUP/prototype/js/navigation.js
vendored
@ -1,296 +0,0 @@
|
||||
/**
|
||||
* KT AI 기반 소상공인 이벤트 자동 생성 서비스
|
||||
* Navigation JavaScript
|
||||
*
|
||||
* 작성일: 2025-01-20
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const { $, $$, on, addClass, removeClass } = window.KTEvent;
|
||||
|
||||
// ========================================
|
||||
// Bottom Navigation
|
||||
// ========================================
|
||||
|
||||
class BottomNavigation {
|
||||
constructor(element) {
|
||||
if (!element) return;
|
||||
|
||||
this.element = element;
|
||||
this.items = $$('.bottom-nav__item', element);
|
||||
this.currentPath = window.location.pathname;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.setActiveItem();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
setActiveItem() {
|
||||
this.items.forEach(item => {
|
||||
const href = item.getAttribute('href');
|
||||
if (href && this.currentPath.includes(href.replace('.html', ''))) {
|
||||
addClass(item, 'bottom-nav__item--active');
|
||||
} else {
|
||||
removeClass(item, 'bottom-nav__item--active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.items.forEach(item => {
|
||||
on(item, 'click', (e) => {
|
||||
const href = item.getAttribute('href');
|
||||
if (!href || href === '#') {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
this.items.forEach(i => removeClass(i, 'bottom-nav__item--active'));
|
||||
addClass(item, 'bottom-nav__item--active');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Tab Navigation
|
||||
// ========================================
|
||||
|
||||
class TabNavigation {
|
||||
constructor(element) {
|
||||
if (!element) return;
|
||||
|
||||
this.element = element;
|
||||
this.items = $$('.tab-nav__item', element);
|
||||
this.panels = [];
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.setActiveTa();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
setActiveTab() {
|
||||
const activeItem = $('.tab-nav__item--active', this.element);
|
||||
if (!activeItem && this.items.length > 0) {
|
||||
addClass(this.items[0], 'tab-nav__item--active');
|
||||
}
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.items.forEach((item, index) => {
|
||||
on(item, 'click', (e) => {
|
||||
e.preventDefault();
|
||||
this.activateTab(index);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
activateTab(index) {
|
||||
this.items.forEach(item => removeClass(item, 'tab-nav__item--active'));
|
||||
addClass(this.items[index], 'tab-nav__item--active');
|
||||
|
||||
// Emit custom event
|
||||
const event = new CustomEvent('tabchange', {
|
||||
detail: { index, item: this.items[index] }
|
||||
});
|
||||
this.element.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// App Bar
|
||||
// ========================================
|
||||
|
||||
class AppBar {
|
||||
constructor(element) {
|
||||
if (!element) return;
|
||||
|
||||
this.element = element;
|
||||
this.backButton = $('.app-bar__back', element);
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
if (this.backButton) {
|
||||
on(this.backButton, 'click', () => {
|
||||
window.history.back();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Stepper
|
||||
// ========================================
|
||||
|
||||
class Stepper {
|
||||
constructor(element) {
|
||||
if (!element) return;
|
||||
|
||||
this.element = element;
|
||||
this.steps = $$('.stepper__step', element);
|
||||
this.currentStep = 0;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.updateSteps();
|
||||
}
|
||||
|
||||
updateSteps() {
|
||||
this.steps.forEach((step, index) => {
|
||||
removeClass(step, 'stepper__step--completed', 'stepper__step--active', 'stepper__step--pending');
|
||||
|
||||
if (index < this.currentStep) {
|
||||
addClass(step, 'stepper__step--completed');
|
||||
} else if (index === this.currentStep) {
|
||||
addClass(step, 'stepper__step--active');
|
||||
} else {
|
||||
addClass(step, 'stepper__step--pending');
|
||||
}
|
||||
|
||||
// Update line
|
||||
const line = $('.stepper__line', step);
|
||||
if (line) {
|
||||
if (index < this.currentStep) {
|
||||
addClass(line, 'stepper__line--completed');
|
||||
} else {
|
||||
removeClass(line, 'stepper__line--completed');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
next() {
|
||||
if (this.currentStep < this.steps.length - 1) {
|
||||
this.currentStep++;
|
||||
this.updateSteps();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
previous() {
|
||||
if (this.currentStep > 0) {
|
||||
this.currentStep--;
|
||||
this.updateSteps();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
goTo(step) {
|
||||
if (step >= 0 && step < this.steps.length) {
|
||||
this.currentStep = step;
|
||||
this.updateSteps();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Progress Bar
|
||||
// ========================================
|
||||
|
||||
class ProgressBar {
|
||||
constructor(element) {
|
||||
if (!element) return;
|
||||
|
||||
this.element = element;
|
||||
this.fill = $('.progress-bar__fill', element);
|
||||
this.text = $('.progress-bar__text', element);
|
||||
this.progress = 0;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.setProgress(this.progress);
|
||||
}
|
||||
|
||||
setProgress(value) {
|
||||
this.progress = Math.max(0, Math.min(100, value));
|
||||
|
||||
if (this.fill) {
|
||||
this.fill.style.width = this.progress + '%';
|
||||
}
|
||||
|
||||
if (this.text) {
|
||||
this.text.textContent = `${this.progress}%`;
|
||||
}
|
||||
}
|
||||
|
||||
increment(value = 1) {
|
||||
this.setProgress(this.progress + value);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.setProgress(0);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Auto Initialize
|
||||
// ========================================
|
||||
|
||||
function initNavigation() {
|
||||
// Bottom Navigation
|
||||
const bottomNav = $('.bottom-nav');
|
||||
if (bottomNav) {
|
||||
new BottomNavigation(bottomNav);
|
||||
}
|
||||
|
||||
// Tab Navigation
|
||||
$$('.tab-nav').forEach(tabNav => {
|
||||
new TabNavigation(tabNav);
|
||||
});
|
||||
|
||||
// App Bar
|
||||
const appBar = $('.app-bar');
|
||||
if (appBar) {
|
||||
new AppBar(appBar);
|
||||
}
|
||||
|
||||
// Stepper
|
||||
$$('.stepper').forEach(stepper => {
|
||||
new Stepper(stepper);
|
||||
});
|
||||
|
||||
// Progress Bar
|
||||
$$('.progress-bar').forEach(progressBar => {
|
||||
new ProgressBar(progressBar);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize on DOMContentLoaded
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initNavigation);
|
||||
} else {
|
||||
initNavigation();
|
||||
}
|
||||
|
||||
// Export classes
|
||||
window.KTEvent = window.KTEvent || {};
|
||||
Object.assign(window.KTEvent, {
|
||||
BottomNavigation,
|
||||
TabNavigation,
|
||||
AppBar,
|
||||
Stepper,
|
||||
ProgressBar
|
||||
});
|
||||
|
||||
})();
|
||||
@ -1,303 +0,0 @@
|
||||
# KT AI 기반 소상공인 이벤트 자동 생성 서비스 - 사용자 여정 맵
|
||||
|
||||
## 문서 정보
|
||||
- **작성일**: 2025-01-20
|
||||
- **버전**: 2.0
|
||||
- **작성자**: UI/UX Designer
|
||||
- **최종 업데이트**: 파일명 정리 및 여정 재구성
|
||||
|
||||
---
|
||||
|
||||
## 📱 전체 화면 목록 (26개 + 추가 화면)
|
||||
|
||||
### ✅ 구현 완료 (24개)
|
||||
|
||||
| 번호 | 화면명 | 파일명 | 유저스토리 | 중요도 | 상태 |
|
||||
|-----|--------|--------|-----------|--------|------|
|
||||
| **User Service** |
|
||||
| 01 | 로그인 | 01-로그인.html | UFR-USER-001 | Must | ✅ |
|
||||
| 02 | 홈화면 | 02-홈화면.html | UFR-USER-002 | Must | ✅ |
|
||||
| 03 | 회원가입 | 03-회원가입.html | UFR-USER-010 | Must | ✅ |
|
||||
| 04 | 매장정보등록 | 04-매장정보등록.html | UFR-USER-020 | Must | ✅ |
|
||||
| **Event Planning Service** |
|
||||
| 05 | 이벤트목적선택 | 05-이벤트목적선택.html | UFR-PLAN-010 | Must | ✅ |
|
||||
| 06 | AI트렌드분석결과 | 06-AI트렌드분석결과.html | UFR-PLAN-020 | Must | ✅ |
|
||||
| 06-1 | AI이벤트유형추천 | 06-AI이벤트유형추천.html | - | 추가 | ✅ 최근수정 |
|
||||
| 07 | AI이벤트상품추천 | 07-AI이벤트상품추천.html | UFR-PLAN-030 | Must | ✅ |
|
||||
| 08 | AI참여방법설계 | 08-AI참여방법설계.html | UFR-PLAN-040 | Must | ✅ |
|
||||
| 09 | AI홍보문구생성 | 09-AI홍보문구생성.html | UFR-PLAN-050 | Must | ✅ |
|
||||
| 10 | 이벤트기획안승인 | 10-이벤트기획안승인.html | UFR-PLAN-060 | Must | ✅ |
|
||||
| **Content Generation Service** |
|
||||
| 11 | AI이미지생성 | 11-AI이미지생성.html | UFR-CONT-010 | Must | ✅ |
|
||||
| 12 | SNS콘텐츠생성 | 12-SNS콘텐츠생성.html | UFR-CONT-030 | Must | ✅ |
|
||||
| 13 | QR포스터생성선택 | 13-QR포스터생성선택.html | UFR-CONT-040 | Must | ✅ |
|
||||
| 14 | QR포스터상세설정 | - | UFR-CONT-040 | Must | ⏳ 13번에서 분리 필요 |
|
||||
| 15 | 콘텐츠편집 | 15-콘텐츠편집.html | UFR-CONT-050 | Should | ✅ |
|
||||
| 16 | 콘텐츠최종승인 | 16-콘텐츠최종승인.html | UFR-CONT-060 | Must | ✅ |
|
||||
| **Distribution Service** |
|
||||
| 17 | 배포채널선택 | 17-배포채널선택.html | UFR-DIST-010 | Must | ✅ |
|
||||
| 18 | 배포진행상태 | 18-배포진행상태.html | UFR-DIST-020~050 | Must | ✅ |
|
||||
| 19 | 오프라인자료다운로드 | 19-오프라인자료다운로드.html | UFR-DIST-060 | Should | ✅ |
|
||||
| **Participation Service** |
|
||||
| 20 | 이벤트참여 | - | UFR-PART-010 | Must | ⏳ 고객용 화면 |
|
||||
| 21 | 참여완료 | - | UFR-PART-010 | Must | ⏳ 고객용 화면 |
|
||||
| **Analytics & Management Service** |
|
||||
| 22 | 당첨자명단관리 | 22-당첨자명단관리.html | UFR-PART-050 | Must | ✅ |
|
||||
| 23 | 실시간대시보드 | 23-실시간대시보드.html | UFR-ANAL-010 | Must | ✅ |
|
||||
| 24 | 채널별성과분석 | 24-채널별성과분석.html | UFR-ANAL-020 | Must | ✅ |
|
||||
| 25 | 광고수익률분석 | 25-광고수익률분석.html | UFR-ANAL-030 | Must | ✅ |
|
||||
| 26 | 분석리포트 | 26-분석리포트.html | UFR-ANAL-040 | Should | ✅ |
|
||||
| **추가 화면 (네비게이션용)** |
|
||||
| - | 마이페이지 | 25-마이페이지.html | - | 추가 | ✅ |
|
||||
| - | 이벤트목록 | 26-이벤트목록.html | - | 추가 | ✅ |
|
||||
|
||||
### ⏳ 작성 필요 (3개)
|
||||
|
||||
- **14-QR포스터상세설정**: 13번 화면에서 기능 분리 필요
|
||||
- **20-이벤트참여**: 고객용 참여 화면
|
||||
- **21-참여완료**: 고객용 완료 화면
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ 소상공인 여정 (Main Journey)
|
||||
|
||||
### 1️⃣ 온보딩 여정
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[01-로그인] --> B{최초 방문?}
|
||||
B -->|Yes| C[03-회원가입]
|
||||
B -->|No| D[02-홈화면]
|
||||
C --> E[04-매장정보등록]
|
||||
E --> D
|
||||
```
|
||||
|
||||
**화면 구성:**
|
||||
- **01-로그인**: JWT 인증, 이메일/비밀번호
|
||||
- **03-회원가입**: 기본 정보 입력
|
||||
- **04-매장정보등록**: 사업자번호 검증, 업종, 주소, 영업시간
|
||||
- **02-홈화면**: 대시보드 요약, 새 이벤트 생성 버튼
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣ 이벤트 기획 여정 (Event Planning)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[02-홈화면] --> B[05-이벤트목적선택]
|
||||
B --> C[06-AI트렌드분석결과]
|
||||
C --> D[06-1-AI이벤트유형추천]
|
||||
D --> E[07-AI이벤트상품추천]
|
||||
E --> F[08-AI참여방법설계]
|
||||
F --> G[09-AI홍보문구생성]
|
||||
G --> H[10-이벤트기획안승인]
|
||||
|
||||
E -->|수정| E
|
||||
G -->|편집| G
|
||||
```
|
||||
|
||||
**화면별 주요 기능:**
|
||||
|
||||
| 화면 | 주요 기능 | AI 역할 | 사용자 액션 |
|
||||
|-----|----------|---------|-----------|
|
||||
| 05-이벤트목적선택 | 목적 선택 (신규고객/재방문/브랜드인지도) | - | 목적 선택 |
|
||||
| 06-AI트렌드분석결과 | 시장 트렌드, 경쟁사 분석 | 트렌드 분석 | 확인 |
|
||||
| 06-1-AI이벤트유형추천 | 이벤트 유형 7개 추천 | 맞춤 추천 | 유형 선택 |
|
||||
| 07-AI이벤트상품추천 | Top 5 경품 추천 | 경품 추천 | 선택/수정 |
|
||||
| 08-AI참여방법설계 | 3가지 참여 방법 제시 | 방법 설계 | 방법 선택 |
|
||||
| 09-AI홍보문구생성 | 5개 버전 문구 생성 | 문구 생성 | 선택/편집 |
|
||||
| 10-이벤트기획안승인 | 최종 확인 및 승인 | - | 승인/수정 |
|
||||
|
||||
**🔄 특이사항:**
|
||||
- **06-1-AI이벤트유형추천**: 최근 추가된 화면으로 localStorage 저장 로직 포함
|
||||
- **07-AI이벤트상품추천**: localStorage에서 선택한 이벤트 유형 불러옴
|
||||
- **순환 가능**: 경품 추천(07), 홍보문구(09)는 수정 가능
|
||||
|
||||
---
|
||||
|
||||
### 3️⃣ 콘텐츠 생성 여정 (Content Generation)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[10-이벤트기획안승인] --> B[11-AI이미지생성]
|
||||
B --> C[12-SNS콘텐츠생성]
|
||||
C --> D[13-QR포스터생성선택]
|
||||
D -->|생성 안함| F
|
||||
D -->|생성| E[14-QR포스터상세설정]
|
||||
E --> F[15-콘텐츠편집]
|
||||
F --> G[16-콘텐츠최종승인]
|
||||
```
|
||||
|
||||
**화면별 주요 기능:**
|
||||
|
||||
| 화면 | 주요 기능 | 출력물 | 사용자 액션 |
|
||||
|-----|----------|--------|-----------|
|
||||
| 11-AI이미지생성 | 3종 이미지 생성 | 정사각형/세로/가로 | 선택/재생성 |
|
||||
| 12-SNS콘텐츠생성 | SNS별 최적화 콘텐츠 | Instagram, Blog, Kakao | 채널 선택 |
|
||||
| 13-QR포스터생성선택 | QR 포스터 생성 여부 | - | 생성 여부 선택 |
|
||||
| 14-QR포스터상세설정 | 크기/형식 설정 | A4/A3 PDF | 옵션 선택 |
|
||||
| 15-콘텐츠편집 | 간단한 수정 | - | 텍스트/이미지 수정 |
|
||||
| 16-콘텐츠최종승인 | 최종 확인 | - | 승인/수정 |
|
||||
|
||||
**⏳ TODO:**
|
||||
- 14번 화면 분리: 13번에서 QR 포스터 상세 설정 기능 분리
|
||||
|
||||
---
|
||||
|
||||
### 4️⃣ 배포 여정 (Distribution)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[16-콘텐츠최종승인] --> B[17-배포채널선택]
|
||||
B --> C[18-배포진행상태]
|
||||
C --> D{오프라인 자료?}
|
||||
D -->|Yes| E[19-오프라인자료다운로드]
|
||||
D -->|No| F[02-홈화면]
|
||||
E --> F
|
||||
```
|
||||
|
||||
**화면별 주요 기능:**
|
||||
|
||||
| 화면 | 주요 기능 | 배포 채널 | 실시간 상태 |
|
||||
|-----|----------|----------|-----------|
|
||||
| 17-배포채널선택 | 다중 채널 선택 (최소 1개) | Instagram, 우리동네TV, 지니TV, 링고비즈 | - |
|
||||
| 18-배포진행상태 | 채널별 배포 상태 모니터링 | 전체 채널 | 5분 갱신 |
|
||||
| 19-오프라인자료다운로드 | QR/PDF 다운로드 | - | - |
|
||||
|
||||
---
|
||||
|
||||
### 5️⃣ 모니터링 & 분석 여정 (Analytics)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[02-홈화면] --> B[23-실시간대시보드]
|
||||
B --> C[24-채널별성과분석]
|
||||
C --> D[25-광고수익률분석]
|
||||
D --> E[22-당첨자명단관리]
|
||||
E --> F{리포트 필요?}
|
||||
F -->|Yes| G[26-분석리포트]
|
||||
F -->|No| H[02-홈화면]
|
||||
G --> H
|
||||
```
|
||||
|
||||
**화면별 주요 기능:**
|
||||
|
||||
| 화면 | 주요 기능 | 갱신 주기 | 주요 지표 |
|
||||
|-----|----------|----------|----------|
|
||||
| 23-실시간대시보드 | 종합 성과 한눈에 보기 | 5분 | 참여수, 조회수, 전환율 |
|
||||
| 24-채널별성과분석 | 채널별 비교 분석 | 실시간 | 채널별 ROI, 전환율 |
|
||||
| 25-광고수익률분석 | 투자 대비 효과 분석 | 일별 | ROI, 순수익, 비용 |
|
||||
| 22-당첨자명단관리 | 경품 지급 관리 | 수동 | 당첨자 명단, 지급 상태 |
|
||||
| 26-분석리포트 | PDF 리포트 생성 | 수동 | 종합 분석 |
|
||||
|
||||
---
|
||||
|
||||
## 👥 고객 여정 (Customer Journey)
|
||||
|
||||
### 6️⃣ 이벤트 참여 여정
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[이벤트 발견] --> B[20-이벤트참여]
|
||||
B --> C[21-참여완료]
|
||||
C --> D[응모번호 발급]
|
||||
D --> E[당첨 발표 대기]
|
||||
E --> F[당첨 알림 수신]
|
||||
```
|
||||
|
||||
**발견 채널:**
|
||||
- QR코드 (오프라인 매장)
|
||||
- SNS (Instagram, Blog, Kakao)
|
||||
- 우리동네TV
|
||||
- 지니TV
|
||||
- 링고비즈
|
||||
|
||||
**⏳ TODO:**
|
||||
- **20-이벤트참여**: 고객용 참여 화면 제작
|
||||
- **21-참여완료**: 응모번호 발급 화면 제작
|
||||
|
||||
---
|
||||
|
||||
## 🧭 네비게이션 구조
|
||||
|
||||
### 바텀 네비게이션 (소상공인용)
|
||||
|
||||
| 아이콘 | 메뉴명 | 이동 화면 | 기능 |
|
||||
|-------|--------|----------|------|
|
||||
| 🏠 | 홈 | 23-실시간대시보드 | 실시간 현황 |
|
||||
| 📋 | 이벤트 | 26-이벤트목록 | 이벤트 관리 + 새 이벤트 |
|
||||
| 📊 | 분석 | 24-채널별성과분석 | 성과 분석 |
|
||||
| 👤 | 마이페이지 | 25-마이페이지 | 설정, 매장정보 |
|
||||
|
||||
### 상단 앱바
|
||||
|
||||
- **제목**: 컨텍스트별 화면명
|
||||
- **뒤로가기**: 이전 화면 (하위 화면만)
|
||||
- **액션 버튼**: 저장, 공유, 필터 등
|
||||
|
||||
---
|
||||
|
||||
## 📂 Backup 파일 (향후 검토)
|
||||
|
||||
다음 파일들이 `backup/` 폴더로 이동되었습니다:
|
||||
|
||||
| 파일명 | 이유 | 검토 사항 |
|
||||
|--------|------|----------|
|
||||
| 04-1-AI이벤트유형추천.html | 설계서 미포함 | 06-1과 통합 검토 |
|
||||
| 04-2-이벤트상세정보.html | 설계서 미포함 | 기능 통합 검토 |
|
||||
| 05-이벤트목적선택.html | 중복 파일 | 삭제 |
|
||||
| 06-1-AI트렌드분석결과.html | 버전 파일 | 06번과 통합 검토 |
|
||||
| 06-2-이벤트상세정보.html | 버전 파일 | 기능 통합 검토 |
|
||||
| 10-AI영상제작.html | 설계서에서 삭제됨 | 향후 재사용 가능성 |
|
||||
|
||||
---
|
||||
|
||||
## 🔄 최근 수정 내역
|
||||
|
||||
### 2025-01-20 (파일명 정리)
|
||||
|
||||
1. **파일명 재번호**: 설계서 기준으로 모든 화면 번호 재정렬
|
||||
2. **용어 통일**:
|
||||
- `AI경품추천` → `AI이벤트상품추천`
|
||||
- `ROI분석` → `광고수익률분석`
|
||||
3. **추가 화면 식별**:
|
||||
- `06-1-AI이벤트유형추천` (최근 작업, localStorage 로직 포함)
|
||||
- `25-마이페이지`, `26-이벤트목록` (네비게이션용)
|
||||
4. **중복 파일 정리**: backup 폴더로 이동
|
||||
|
||||
### 버그 수정 (06-1-AI이벤트유형추천.html)
|
||||
|
||||
**문제**: 이벤트 유형 선택 후 07번 화면에서 자동 리다이렉트 발생
|
||||
|
||||
**원인**: `selectedEventType`이 localStorage에 저장되지 않음
|
||||
|
||||
**해결**: `proceedToNext` 함수에서 localStorage 직접 저장 로직 추가 (560-562줄)
|
||||
|
||||
```javascript
|
||||
// 수정 전
|
||||
window.AppState.selectedEventType = selectedEvent;
|
||||
window.AppState.save();
|
||||
|
||||
// 수정 후
|
||||
localStorage.setItem('kt_selected_event_type', JSON.stringify(selectedEvent));
|
||||
window.AppState.save();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 통계
|
||||
|
||||
- **총 화면 수**: 26개 (설계서 기준) + 3개 (추가 화면)
|
||||
- **구현 완료**: 24개 ✅
|
||||
- **작성 필요**: 3개 ⏳
|
||||
- **Backup 파일**: 6개
|
||||
|
||||
---
|
||||
|
||||
## ✅ Next Steps
|
||||
|
||||
1. **14-QR포스터상세설정**: 13번 화면에서 기능 분리
|
||||
2. **20-이벤트참여**: 고객용 참여 화면 제작
|
||||
3. **21-참여완료**: 응모번호 발급 화면 제작
|
||||
4. **06-1-AI이벤트유형추천**: 설계서에 정식 추가 여부 검토
|
||||
5. **Backup 파일 검토**: 통합 또는 삭제 결정
|
||||
@ -1,64 +0,0 @@
|
||||
# 프로토타입 파일명 변경 계획
|
||||
|
||||
## 변경 대상 (설계서 기준으로 정리)
|
||||
|
||||
### 1단계: 번호 재정렬 (역순으로 변경하여 충돌 방지)
|
||||
|
||||
| 현재 파일명 | 변경 파일명 | 비고 |
|
||||
|------------|------------|------|
|
||||
| 24-분석리포트.html | 26-분석리포트.html | |
|
||||
| 23-ROI분석.html | 25-광고수익률분석.html | 이름도 변경 |
|
||||
| 22-채널별성과분석.html | 24-채널별성과분석.html | |
|
||||
| 21-실시간대시보드.html | 23-실시간대시보드.html | |
|
||||
| 20-당첨자명단관리.html | 22-당첨자명단관리.html | |
|
||||
| 17-오프라인자료다운로드.html | 19-오프라인자료다운로드.html | |
|
||||
| 16-배포진행상태.html | 18-배포진행상태.html | |
|
||||
| 15-배포채널선택.html | 17-배포채널선택.html | |
|
||||
| 14-콘텐츠최종승인.html | 16-콘텐츠최종승인.html | |
|
||||
| 13-콘텐츠편집.html | 15-콘텐츠편집.html | |
|
||||
| 12-QR포스터생성.html | 13-QR포스터생성선택.html | 이름도 변경 |
|
||||
| 11-SNS콘텐츠생성.html | 12-SNS콘텐츠생성.html | |
|
||||
| 09-AI이미지생성.html | 11-AI이미지생성.html | |
|
||||
| 08-이벤트기획안승인.html | 10-이벤트기획안승인.html | |
|
||||
| 07-AI홍보문구생성.html | 09-AI홍보문구생성.html | |
|
||||
| 06-AI참여방법설계.html | 08-AI참여방법설계.html | |
|
||||
| 05-AI경품추천.html | 07-AI이벤트상품추천.html | 이름도 변경 |
|
||||
| 04-AI트렌드분석결과.html | 06-AI트렌드분석결과.html | |
|
||||
| 03-이벤트목적선택.html | 05-이벤트목적선택.html | |
|
||||
| 02-매장정보등록.html | 04-매장정보등록.html | |
|
||||
| 01-회원가입.html | 03-회원가입.html | |
|
||||
| 21.5-홈.html | 02-홈화면.html | 이름도 변경 |
|
||||
| 00-로그인.html | 01-로그인.html | |
|
||||
|
||||
### 2단계: 중복/삭제 파일 처리
|
||||
|
||||
| 파일명 | 처리 방법 | 사유 |
|
||||
|--------|----------|------|
|
||||
| 04-1-AI이벤트유형추천.html | backup 폴더로 이동 | 설계서에 없음, 추후 통합 검토 |
|
||||
| 04-2-이벤트상세정보.html | backup 폴더로 이동 | 설계서에 없음, 추후 통합 검토 |
|
||||
| 05-이벤트목적선택.html | 삭제 (03-과 중복) | 중복 파일 |
|
||||
| 06-1-AI트렌드분석결과.html | backup 폴더로 이동 | 버전 파일, 추후 검토 |
|
||||
| 06-2-이벤트상세정보.html | backup 폴더로 이동 | 버전 파일, 추후 검토 |
|
||||
| 06-AI이벤트유형추천.html | 현재 사용 중 | 이벤트 유형 선택 화면 |
|
||||
| 10-AI영상제작.html | backup 폴더로 이동 | 설계서에서 삭제됨 |
|
||||
|
||||
### 3단계: 추가 화면 정리 (유지)
|
||||
|
||||
| 파일명 | 비고 |
|
||||
|--------|------|
|
||||
| 25-마이페이지.html | 네비게이션용 추가 화면 |
|
||||
| 26-이벤트목록.html | 네비게이션용 추가 화면 |
|
||||
|
||||
### 4단계: 누락 화면 (향후 작성 필요)
|
||||
|
||||
| 화면번호 | 화면명 | 비고 |
|
||||
|---------|--------|------|
|
||||
| 14 | QR포스터상세설정 | 13번에서 분리 필요 |
|
||||
| 20 | 이벤트참여 | 고객용 화면 |
|
||||
| 21 | 참여완료 | 고객용 화면 |
|
||||
|
||||
## 특이사항
|
||||
|
||||
1. **06-AI이벤트유형추천.html**: 이 파일이 최근 수정되었고 localStorage 저장 로직이 추가됨. 설계서의 어느 단계에 해당하는지 확인 필요.
|
||||
2. **07-AI이벤트상품추천.html**: "AI경품추천"에서 "AI이벤트상품추천"으로 용어 통일 필요.
|
||||
3. **13-QR포스터생성선택.html + 14-QR포스터상세설정.html**: 현재 하나의 파일(12-QR포스터생성.html)을 두 개로 분리 필요.
|
||||
3312
TEMP_BACKUP/uiux.md
@ -1,358 +0,0 @@
|
||||
# KT AI 기반 소상공인 이벤트 자동 생성 서비스 - 유저스토리 목록
|
||||
|
||||
## 전체 유저스토리 요약
|
||||
|
||||
| 서비스 | ID | 유저스토리 | 우선순위 | 복잡도 |
|
||||
|--------|-----|-----------|----------|--------|
|
||||
| **User** | UFR-USER-001 | [로그인] 소상공인으로서 등록된 계정으로 로그인하고 싶다 | M | 5 |
|
||||
| | UFR-USER-002 | [홈화면] 소상공인으로서 로그인 후 내 이벤트 현황을 한눈에 보고 새 이벤트를 시작하고 싶다 | M | 8 |
|
||||
| | UFR-USER-010 | [회원가입] 소상공인으로서 간편하게 회원가입하고 싶다 | M | 5 |
|
||||
| | UFR-USER-020 | [매장정보등록] 맞춤형 이벤트를 위해 매장 정보를 등록하고 싶다 | M | 13 |
|
||||
| **Event Planning** | UFR-PLAN-010 | [이벤트목적선택] 효과적인 이벤트를 위해 목적을 선택하고 싶다 | M | 3 |
|
||||
| | UFR-PLAN-020 | [AI업종트렌드분석] 성공률 높은 이벤트를 위해 AI 트렌드 분석을 원한다 | M | 13 |
|
||||
| | UFR-PLAN-030 | [AI이벤트상품추천] 예산 내 최적 이벤트상품을 AI가 추천하고 이벤트 진행 방법을 선택하고 싶다 | M | 21 |
|
||||
| | UFR-PLAN-040 | [AI참여방법설계] 재방문 유도하는 참여 방법을 AI가 설계해주기를 원한다 | M | 21 |
|
||||
| | UFR-PLAN-050 | [AI홍보문구생성] 매력적인 홍보 문구를 AI가 자동 생성해주기를 원한다 | M | 13 |
|
||||
| | UFR-PLAN-060 | [이벤트기획안승인] 완성된 기획안을 검토하고 승인하고 싶다 | M | 5 |
|
||||
| **Content Generation** | UFR-CONT-010 | [AI이미지생성] 브랜드 컬러와 로고를 반영한 이미지를 AI가 생성해주기를 원한다 | M | 34 |
|
||||
| | UFR-CONT-030 | [SNS콘텐츠생성] 플랫폼별 최적화된 콘텐츠를 자동 생성해주기를 원한다 | M | 21 |
|
||||
| | UFR-CONT-040 | [QR포스터생성] 오프라인 홍보용 QR 포스터를 생성하고 싶다 | M | 8 |
|
||||
| | UFR-CONT-050 | [콘텐츠편집] 생성된 콘텐츠를 개인화하기 위해 수정하고 싶다 | S | 13 |
|
||||
| | UFR-CONT-060 | [콘텐츠최종승인] 완성된 콘텐츠를 검토하고 승인하고 싶다 | M | 3 |
|
||||
| **Distribution** | UFR-DIST-010 | [다중채널배포설정] 여러 채널에 한 번에 배포하고 싶다 | M | 13 |
|
||||
| | UFR-DIST-020 | [우리동네TV배포] 주변 지역 고객에게 우리동네TV로 도달하고 싶다 | M | 21 |
|
||||
| | UFR-DIST-030 | [링고비즈연결음업데이트] 매장 전화 연결음을 이벤트 내용으로 업데이트하고 싶다 | S | 13 |
|
||||
| | UFR-DIST-040 | [지니TV광고배포] 가정의 TV를 통해 지니TV 광고를 배포하고 싶다 | M | 21 |
|
||||
| | UFR-DIST-050 | [SNS자동포스팅] Instagram, Naver Blog, Kakao에 자동 포스팅하고 싶다 | M | 13 |
|
||||
| | UFR-DIST-060 | [오프라인자료다운로드] QR 포스터와 인쇄용 PDF를 다운로드하고 싶다 | S | 5 |
|
||||
| **Participation** | UFR-PART-010 | [이벤트참여신청] 고객이 간편하게 이벤트에 참여하고 싶다 | M | 8 |
|
||||
| | UFR-PART-020 | [중복참여방지] 공정한 이벤트 운영을 위해 1인 1회만 참여 허용하고 싶다 | M | 8 |
|
||||
| | UFR-PART-030 | [자동당첨자추첨] 추첨형 이벤트 종료 시 자동으로 당첨자가 추첨되기를 원한다 | M | 13 |
|
||||
| | UFR-PART-040 | [당첨알림발송] 고객이 SMS나 카카오 알림톡으로 당첨 알림을 받고 싶다 | M | 8 |
|
||||
| | UFR-PART-050 | [당첨자명단관리] 당첨자 명단을 확인하고 관리하고 싶다 | M | 5 |
|
||||
| **Analytics** | UFR-ANAL-010 | [실시간대시보드조회] 실시간으로 참여자 수, 노출 수, 매출 증가율을 확인하고 싶다 | M | 21 |
|
||||
| | UFR-ANAL-020 | [채널별성과분석] 채널별 노출 수와 참여율을 비교하고 싶다 | M | 13 |
|
||||
| | UFR-ANAL-030 | [광고수익률자동계산] 총비용 대비 수익(광고 수익률)이 자동으로 계산되기를 원한다 | M | 13 |
|
||||
| | UFR-ANAL-040 | [분석리포트생성] 종합 분석 리포트를 PDF로 다운로드하고 싶다 | S | 8 |
|
||||
| **AI Learning** | UFR-AIMPR-010 | [AI개선안생성] AI가 분석 결과를 바탕으로 구체적인 개선안을 제안해주기를 원한다 | M | 21 |
|
||||
| | UFR-AIMPR-020 | [다음이벤트아이디어제안] AI가 시즌별 성공 이벤트 아이디어를 제안해주기를 원한다 | S | 13 |
|
||||
| | UFR-AIMPR-030 | [성공패턴학습] AI 시스템이 이벤트 결과를 학습하여 성공 패턴을 축적하고 싶다 | M | 34 |
|
||||
| **비기능 요구사항** | NFR-PERF-010 | [AI기획속도] AI 이벤트 기획을 10초 이내에 완료하고 싶다 | M | 21 |
|
||||
| | NFR-PERF-020 | [콘텐츠생성속도] 이미지와 영상 생성을 5-8분 내에 완료하고 싶다 | M | 34 |
|
||||
| | NFR-PERF-030 | [배포속도] 다중 채널 배포를 1분 이내에 완료하고 싶다 | M | 21 |
|
||||
| | NFR-PERF-040 | [실시간데이터수집] 5분 간격으로 실시간 데이터를 수집하고 싶다 | M | 13 |
|
||||
| | NFR-SECU-010 | [개인정보보호] 개인정보 보호 규정을 준수하고 암호화를 적용하고 싶다 | M | 13 |
|
||||
| | NFR-RELI-010 | [배포실패자동재시도] 배포 실패 시 자동으로 3회까지 재시도하고 싶다 | M | 8 |
|
||||
| | NFR-SCAL-010 | [동시이벤트처리] 최소 100개의 이벤트를 동시에 처리할 수 있어야 한다 | S | 21 |
|
||||
|
||||
---
|
||||
|
||||
## 우선순위별 통계
|
||||
|
||||
| 우선순위 | 개수 | 비율 |
|
||||
|---------|------|------|
|
||||
| M (필수) | 35 | 81.4% |
|
||||
| S (선택) | 8 | 18.6% |
|
||||
| **총계** | **43** | **100%** |
|
||||
|
||||
---
|
||||
|
||||
## 서비스별 통계
|
||||
|
||||
| 서비스 | 유저스토리 수 | 평균 복잡도 | 필수(M) | 선택(S) |
|
||||
|--------|-------------|-----------|---------|---------|
|
||||
| User | 4 | 7.8 | 4 | 0 |
|
||||
| Event Planning | 6 | 12.7 | 6 | 0 |
|
||||
| Content Generation | 5 | 15.8 | 4 | 1 |
|
||||
| Distribution | 6 | 14.3 | 4 | 2 |
|
||||
| Participation | 5 | 8.4 | 5 | 0 |
|
||||
| Analytics | 4 | 13.8 | 3 | 1 |
|
||||
| AI Learning | 3 | 22.7 | 2 | 1 |
|
||||
| 비기능 요구사항 | 7 | 18.7 | 6 | 1 |
|
||||
| **총계** | **40** | **13.4** | **34** | **6** |
|
||||
|
||||
---
|
||||
|
||||
## 복잡도별 통계
|
||||
|
||||
| 복잡도 범위 | 개수 | 유저스토리 |
|
||||
|-----------|------|-----------|
|
||||
| 1-5 (낮음) | 6 | UFR-USER-001, UFR-USER-010, UFR-PLAN-010, UFR-PLAN-060, UFR-CONT-060, UFR-DIST-060, UFR-PART-050 |
|
||||
| 6-13 (중간) | 24 | UFR-USER-002, UFR-USER-020, UFR-PLAN-020, UFR-PLAN-050, UFR-CONT-030, UFR-CONT-040, UFR-CONT-050, UFR-DIST-010, UFR-DIST-030, UFR-DIST-050, UFR-PART-010, UFR-PART-020, UFR-PART-030, UFR-PART-040, UFR-ANAL-020, UFR-ANAL-030, UFR-AIMPR-020, NFR-PERF-040, NFR-SECU-010, NFR-RELI-010 |
|
||||
| 14-21 (높음) | 10 | UFR-PLAN-030, UFR-PLAN-040, UFR-DIST-020, UFR-DIST-040, UFR-ANAL-010, UFR-AIMPR-010, NFR-PERF-010, NFR-PERF-030, NFR-SCAL-010 |
|
||||
| 22+ (매우 높음) | 3 | UFR-CONT-010, UFR-AIMPR-030, NFR-PERF-020 |
|
||||
|
||||
---
|
||||
|
||||
## 주요 기능별 상세 목록
|
||||
|
||||
### 1. User 서비스 (회원 인증 및 매장 정보 관리)
|
||||
|
||||
| ID | 기능 | 우선순위 | 복잡도 | 핵심 요구사항 |
|
||||
|----|------|----------|--------|-------------|
|
||||
| UFR-USER-001 | 로그인 | M | 5 | 이메일/비밀번호 입력, JWT 토큰 발급, 세션 생성, 자동 로그인 |
|
||||
| UFR-USER-002 | 홈화면 | M | 8 | 진행 중인 이벤트 목록, 대시보드 요약, 새 이벤트 생성 버튼 |
|
||||
| UFR-USER-010 | 회원가입 | M | 5 | 이름, 전화번호, 이메일, 비밀번호, 중복 가입 방지 |
|
||||
| UFR-USER-020 | 매장정보등록 | M | 13 | 매장명, 업종, 주소, 사업자번호 검증, 메뉴/상품 정보 |
|
||||
|
||||
**검증 로직:**
|
||||
- 사업자번호 형식 검증 (XXX-XX-XXXXX)
|
||||
- 사업자번호 유효성 확인 및 휴폐업 여부 확인
|
||||
- 매장명과 사업자 정보 일치 확인
|
||||
|
||||
**주요 흐름:**
|
||||
1. UFR-USER-001 (로그인) → UFR-USER-002 (홈화면)
|
||||
2. 신규 사용자: 회원가입(UFR-USER-010) → 매장정보등록(UFR-USER-020) → 홈화면(UFR-USER-002)
|
||||
3. 홈화면에서 "새 이벤트 생성" → UFR-PLAN-010
|
||||
|
||||
---
|
||||
|
||||
### 2. Event Planning 서비스 (AI 기반 이벤트 자동 기획)
|
||||
|
||||
| ID | 기능 | 우선순위 | 복잡도 | 핵심 요구사항 | AI 모델 |
|
||||
|----|------|----------|--------|-------------|---------|
|
||||
| UFR-PLAN-010 | 이벤트목적선택 | M | 3 | 신규고객 유치/재방문 유도/매출 증대/인지도 향상 선택 | - |
|
||||
| UFR-PLAN-020 | AI업종트렌드분석 | M | 13 | 업종, 지역, 시즌 기반 트렌드 분석, 성공 사례 분석 | Claude API |
|
||||
| UFR-PLAN-030 | AI이벤트상품추천 | M | 21 | 예산, 타겟 고객층 기반 이벤트상품 Top 5 추천, 이벤트 진행 방법 선택 (추첨형/선착순형) | Claude API |
|
||||
| UFR-PLAN-040 | AI참여방법설계 | M | 21 | 간단한 참여 방법 + 재방문 유도 + 바이럴 확산 3가지 옵션 | Claude API |
|
||||
| UFR-PLAN-050 | AI홍보문구생성 | M | 13 | 5개 버전 홍보 문구 + SNS 해시태그 + 플랫폼별 최적화 | GPT-4 API |
|
||||
| UFR-PLAN-060 | 이벤트기획안승인 | M | 5 | 기획안 요약 제공, 승인 처리, 수정 기능 | - |
|
||||
|
||||
**이벤트 진행 방법:**
|
||||
- **추첨형**: 기간 내 참여한 고객 중 이벤트 종료 후 당첨자 랜덤 추첨
|
||||
- **선착순형**: 선착순 쿠폰 소진 방식, 소진 시 자동 이벤트 종료
|
||||
|
||||
**성능 목표:** 전체 기획 과정 10초 이내 완료
|
||||
|
||||
---
|
||||
|
||||
### 3. Content Generation 서비스 (AI 기반 콘텐츠 자동 생성)
|
||||
|
||||
| ID | 기능 | 우선순위 | 복잡도 | 핵심 요구사항 | AI 모델 |
|
||||
|----|------|----------|--------|-------------|---------|
|
||||
| UFR-CONT-010 | AI이미지생성 | M | 34 | 브랜드 컬러, 로고 반영, 3가지 스타일 (심플/화려/트렌디), 1080x1080 | Stable Diffusion |
|
||||
| UFR-CONT-030 | SNS콘텐츠생성 | M | 21 | Instagram (1080x1080), Naver Blog (800x600), Kakao (800x800) | - |
|
||||
| UFR-CONT-040 | QR포스터생성 | M | 8 | QR 코드 포함, A4/A3 PDF, 인쇄 최적화 (300dpi), 생성 선택 옵션 | - |
|
||||
| UFR-CONT-050 | 콘텐츠편집 | S | 13 | 텍스트 수정, 색상 조정, 레이아웃 변경, 편집 이력 관리 (최대 3개 버전) | - |
|
||||
| UFR-CONT-060 | 콘텐츠최종승인 | M | 3 | 전체 콘텐츠 미리보기, 승인 처리 | - |
|
||||
|
||||
**QR 포스터 생성 옵션:**
|
||||
- 생성 선택: QR 포스터 생성 화면으로 이동
|
||||
- 생성 안 함: 콘텐츠 편집 단계로 바로 이동
|
||||
|
||||
**성능 목표:**
|
||||
- 이미지 생성: 2-3분 이내
|
||||
- SNS 콘텐츠 생성: 30초 이내
|
||||
- QR 포스터 생성: 20초 이내
|
||||
|
||||
---
|
||||
|
||||
### 4. Distribution 서비스 (다중 채널 자동 배포)
|
||||
|
||||
| ID | 기능 | 우선순위 | 복잡도 | 핵심 요구사항 | 연동 API |
|
||||
|----|------|----------|--------|-------------|---------|
|
||||
| UFR-DIST-010 | 다중채널배포설정 | M | 13 | 채널 선택 (우리동네TV, 링고비즈, 지니TV, SNS), 즉시/예약 배포 | - |
|
||||
| UFR-DIST-020 | 우리동네TV배포 | M | 21 | 반경 (500m/1km), 송출 시간대, 15초 영상 업로드 | 우리동네TV API |
|
||||
| UFR-DIST-030 | 링고비즈연결음업데이트 | S | 13 | 네이버 클로바 TTS 음성 생성, 연결음 업데이트, 미리듣기 | 링고비즈 API, Clova TTS |
|
||||
| UFR-DIST-040 | 지니TV광고배포 | M | 21 | 타겟 지역 (1km/3km/5km), 노출 시간대, 예산 기반 노출량 | 지니TV API |
|
||||
| UFR-DIST-050 | SNS자동포스팅 | M | 13 | Instagram, Naver Blog, Kakao OAuth 연동, 예약 포스팅 | SNS API (Instagram, Naver, Kakao) |
|
||||
| UFR-DIST-060 | 오프라인자료다운로드 | S | 5 | QR 포스터 (A4/A3 PDF), QR 코드 이미지, 인쇄용 이미지 | - |
|
||||
|
||||
**성능 목표:** 전체 배포 과정 1분 이내 완료
|
||||
|
||||
---
|
||||
|
||||
### 5. Participation 서비스 (이벤트 참여 및 접수 관리)
|
||||
|
||||
| ID | 기능 | 우선순위 | 복잡도 | 핵심 요구사항 |
|
||||
|----|------|----------|--------|-------------|
|
||||
| UFR-PART-010 | 이벤트참여신청 | M | 8 | 고객명, 전화번호, 참여 경로 추적, 응모번호 발급 |
|
||||
| UFR-PART-020 | 중복참여방지 | M | 8 | 전화번호 기반 중복 체크, 매장 방문 고객 가산점 |
|
||||
| UFR-PART-030 | 자동당첨자추첨 | M | 13 | 추첨형: 종료 시 자동 추첨, 선착순형: 쿠폰 소진 시 자동 종료 |
|
||||
| UFR-PART-040 | 당첨알림발송 | M | 8 | SMS/카카오 알림톡 발송, 이벤트상품 수령 방법 안내, 실패 시 재발송 (최대 3회) |
|
||||
| UFR-PART-050 | 당첨자명단관리 | M | 5 | 당첨자 목록, 이벤트상품 지급 상태 관리, 엑셀 다운로드 |
|
||||
|
||||
**이벤트 진행 방법별 프로세스:**
|
||||
- **추첨형**: 이벤트 종료 시각 도래 → 자동 추첨 시작 → 당첨자 선정 → 알림 발송
|
||||
- **선착순형**: 참여 순서대로 자동 확정 → 쿠폰 소진 시 자동 종료 → 추첨 과정 없음
|
||||
|
||||
**정책:**
|
||||
- 1인 1회 참여 제한
|
||||
- 개인정보 보호 규정 준수
|
||||
- 매장 방문 고객 가산점 부여 (추첨형만)
|
||||
|
||||
---
|
||||
|
||||
### 6. Analytics 서비스 (실시간 효과 측정 및 분석)
|
||||
|
||||
| ID | 기능 | 우선순위 | 복잡도 | 핵심 요구사항 | 데이터 소스 |
|
||||
|----|------|----------|--------|-------------|------------|
|
||||
| UFR-ANAL-010 | 실시간대시보드조회 | M | 21 | 참여자 수, 채널별 노출 수, 매출 증가율, 예상 ROI (5분 간격 업데이트) | KT 채널 API, POS, SNS API |
|
||||
| UFR-ANAL-020 | 채널별성과분석 | M | 13 | 노출 수, 참여율, 전환율, 비용 대비 효과 (CPA) 비교 | 각 채널 API |
|
||||
| UFR-ANAL-030 | 광고수익률자동계산 | M | 13 | 총 투자 비용 vs 수익, 손익분기점, 회수 기간, ROI 계산 | - |
|
||||
| UFR-ANAL-040 | 분석리포트생성 | S | 8 | 종합 리포트 PDF 생성, 업종 평균 대비 벤치마킹 | - |
|
||||
|
||||
**데이터 수집:**
|
||||
- 실시간 데이터 수집 (5분 간격)
|
||||
- Instagram Graph API, Kakao API 활용
|
||||
- Naver Blog (조회 수만 수집 가능, 체류 시간 불가)
|
||||
- QR 코드 스캔 횟수 자체 추적
|
||||
|
||||
---
|
||||
|
||||
### 7. AI Learning 서비스 (AI 학습 및 개선 제안)
|
||||
|
||||
| ID | 기능 | 우선순위 | 복잡도 | 핵심 요구사항 | AI 모델 |
|
||||
|----|------|----------|--------|-------------|---------|
|
||||
| UFR-AIMPR-010 | AI개선안생성 | M | 21 | 성공/실패 요인 분석, 3가지 개선안 제시 (이벤트상품 변경, 참여 방법 단순화, 채널 조정) | AI 머신러닝 엔진 |
|
||||
| UFR-AIMPR-020 | 다음이벤트아이디어제안 | S | 13 | 시즌별 성공 이벤트 아이디어 5가지, 예상 성과 제공 | 트렌드 DB |
|
||||
| UFR-AIMPR-030 | 성공패턴학습 | M | 34 | 이벤트 결과 데이터 학습, 성공 패턴 축적, 추천 정확도 개선 | 빅데이터 분석 시스템 |
|
||||
|
||||
**학습 전략:**
|
||||
- 성공 패턴 자동 학습 및 재활용
|
||||
- 실패 요인 회피 로직 적용
|
||||
- 업종별/지역별 데이터 축적
|
||||
- 지속적 성능 개선 알고리즘
|
||||
|
||||
---
|
||||
|
||||
### 8. 비기능 요구사항 (성능, 보안, 안정성, 확장성)
|
||||
|
||||
| 카테고리 | ID | 기능 | 우선순위 | 복잡도 | 핵심 요구사항 |
|
||||
|---------|-----|------|----------|--------|-------------|
|
||||
| 성능 | NFR-PERF-010 | AI기획속도 | M | 21 | AI 이벤트 기획 10초 이내 (병렬 호출, 캐싱) |
|
||||
| | NFR-PERF-020 | 콘텐츠생성속도 | M | 34 | 이미지 2-3분, 영상 3-5분, 총 5-8분 이내 (병렬 처리, GPU 가속) |
|
||||
| | NFR-PERF-030 | 배포속도 | M | 21 | 다중 채널 배포 1분 이내 (채널별 병렬 배포) |
|
||||
| | NFR-PERF-040 | 실시간데이터수집 | M | 13 | 5분 간격 자동 데이터 수집 (스케줄러, 재시도 3회) |
|
||||
| 보안 | NFR-SECU-010 | 개인정보보호 | M | 13 | 전화번호 AES-256 암호화, GDPR/개인정보보호법 준수, RBAC |
|
||||
| 안정성 | NFR-RELI-010 | 배포실패자동재시도 | M | 8 | 배포 실패 시 자동 재시도 (최대 3회, 지수 백오프) |
|
||||
| 확장성 | NFR-SCAL-010 | 동시이벤트처리 | S | 21 | 100개 이벤트 동시 처리 (Auto Scaling, 로드 밸런싱, 메시지 큐) |
|
||||
|
||||
---
|
||||
|
||||
## 기술 스택 요약
|
||||
|
||||
### AI/ML 모델
|
||||
| 모델 | 용도 | 관련 유저스토리 |
|
||||
|------|------|----------------|
|
||||
| Claude API | 트렌드 분석, 이벤트상품 추천, 참여 방법 설계 | UFR-PLAN-020, UFR-PLAN-030, UFR-PLAN-040 |
|
||||
| GPT-4 API | 홍보 문구 생성 | UFR-PLAN-050 |
|
||||
| Stable Diffusion | 이미지 생성 | UFR-CONT-010 |
|
||||
| 네이버 클로바 TTS | 연결음 음성 합성 | UFR-DIST-030 |
|
||||
| AI 머신러닝 엔진 | 개선안 생성, 성공 패턴 학습 | UFR-AIMPR-010, UFR-AIMPR-030 |
|
||||
|
||||
### 외부 API 연동
|
||||
| API | 용도 | 관련 유저스토리 |
|
||||
|-----|------|----------------|
|
||||
| 사업자번호 검증 시스템 | 사업자번호 유효성 확인 | UFR-USER-020 |
|
||||
| 우리동네TV API | 지역 타겟팅 영상 송출 | UFR-DIST-020 |
|
||||
| 링고비즈 API | 연결음 업데이트 | UFR-DIST-030 |
|
||||
| 지니TV API | TV 광고 배포 | UFR-DIST-040 |
|
||||
| Instagram Graph API | SNS 자동 포스팅, 성과 데이터 수집 | UFR-DIST-050, UFR-ANAL-020 |
|
||||
| Naver Blog API | 블로그 자동 포스팅 | UFR-DIST-050 |
|
||||
| Kakao Channel API | 카카오 채널 자동 포스팅, 성과 데이터 수집 | UFR-DIST-050, UFR-ANAL-020 |
|
||||
| SMS/카카오 알림톡 | 당첨 알림 발송 | UFR-PART-040 |
|
||||
| POS 시스템 | 매출 데이터 수집 | UFR-ANAL-010 |
|
||||
|
||||
---
|
||||
|
||||
## 마이크로서비스 아키텍처
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ API Gateway │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────┼─────────────────────┐
|
||||
│ │ │
|
||||
┌───────▼────────┐ ┌────────▼────────┐ ┌────────▼────────┐
|
||||
│ User Service │ │ Event Planning │ │ Content │
|
||||
│ (UFR-USER) │ │ (UFR-PLAN) │ │ Generation │
|
||||
│ │ │ │ │ (UFR-CONT) │
|
||||
└────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
│ │ │
|
||||
│ │ │
|
||||
┌───────▼────────┐ ┌────────▼────────┐ ┌────────▼────────┐
|
||||
│ Distribution │ │ Participation │ │ Analytics │
|
||||
│ (UFR-DIST) │ │ (UFR-PART) │ │ (UFR-ANAL) │
|
||||
└────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
│ │ │
|
||||
└─────────────────────┼─────────────────────┘
|
||||
│
|
||||
┌─────────▼─────────┐
|
||||
│ AI Learning │
|
||||
│ (UFR-AIMPR) │
|
||||
└───────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 개발 우선순위 로드맵
|
||||
|
||||
### Phase 1: MVP (8주)
|
||||
**목표:** 기본 이벤트 기획 및 배포 기능
|
||||
|
||||
| 순서 | 서비스 | 유저스토리 | 비고 |
|
||||
|------|--------|-----------|------|
|
||||
| 1 | User | UFR-USER-001, UFR-USER-002, UFR-USER-010, UFR-USER-020 | 로그인, 홈화면, 회원가입, 매장 정보 등록 |
|
||||
| 2 | Event Planning | UFR-PLAN-010, UFR-PLAN-030, UFR-PLAN-060 | 목적 선택, 이벤트상품 추천, 승인 |
|
||||
| 3 | Content Generation | UFR-CONT-010, UFR-CONT-040, UFR-CONT-060 | 이미지 생성, QR 포스터, 승인 |
|
||||
| 4 | Distribution | UFR-DIST-010, UFR-DIST-050 | 다중 채널 설정, SNS 포스팅 |
|
||||
| 5 | Participation | UFR-PART-010, UFR-PART-020, UFR-PART-030 | 참여 신청, 중복 방지, 자동 추첨 |
|
||||
|
||||
### Phase 2: 고도화 (6주)
|
||||
**목표:** AI 기능 강화 및 분석 기능 추가
|
||||
|
||||
| 순서 | 서비스 | 유저스토리 | 비고 |
|
||||
|------|--------|-----------|------|
|
||||
| 6 | Event Planning | UFR-PLAN-020, UFR-PLAN-040, UFR-PLAN-050 | 트렌드 분석, 참여 방법, 홍보 문구 |
|
||||
| 7 | Content Generation | UFR-CONT-030 | SNS 콘텐츠 |
|
||||
| 8 | Distribution | UFR-DIST-020, UFR-DIST-040 | 우리동네TV, 지니TV |
|
||||
| 9 | Participation | UFR-PART-040, UFR-PART-050 | 당첨 알림, 명단 관리 |
|
||||
| 10 | Analytics | UFR-ANAL-010, UFR-ANAL-020, UFR-ANAL-030 | 실시간 대시보드, 채널별 분석, ROI |
|
||||
|
||||
### Phase 3: 완성 (4주)
|
||||
**목표:** AI 학습 및 선택 기능 추가
|
||||
|
||||
| 순서 | 서비스 | 유저스토리 | 비고 |
|
||||
|------|--------|-----------|------|
|
||||
| 11 | Distribution | UFR-DIST-030, UFR-DIST-060 | 링고비즈 연결음, 오프라인 자료 |
|
||||
| 12 | Content Generation | UFR-CONT-050 | 콘텐츠 편집 |
|
||||
| 13 | Analytics | UFR-ANAL-040 | 분석 리포트 |
|
||||
| 14 | AI Learning | UFR-AIMPR-010, UFR-AIMPR-030 | 개선안 생성, 성공 패턴 학습 |
|
||||
| 15 | AI Learning | UFR-AIMPR-020 | 다음 이벤트 아이디어 |
|
||||
|
||||
---
|
||||
|
||||
## 성능 요구사항 요약
|
||||
|
||||
| 단계 | 목표 시간 | 관련 유저스토리 |
|
||||
|------|----------|----------------|
|
||||
| AI 이벤트 기획 | 10초 이내 | UFR-PLAN-020~050, NFR-PERF-010 |
|
||||
| 콘텐츠 생성 | 5-8분 이내 | UFR-CONT-010~030, NFR-PERF-020 |
|
||||
| 다중 채널 배포 | 1분 이내 | UFR-DIST-010~050, NFR-PERF-030 |
|
||||
| 실시간 데이터 수집 | 5분 간격 | UFR-ANAL-010, NFR-PERF-040 |
|
||||
|
||||
---
|
||||
|
||||
## 보안 요구사항 요약
|
||||
|
||||
| 항목 | 요구사항 | 관련 유저스토리 |
|
||||
|------|----------|----------------|
|
||||
| 개인정보 암호화 | AES-256 (전화번호, 이름) | NFR-SECU-010 |
|
||||
| 전송 보안 | HTTPS/TLS | NFR-SECU-010 |
|
||||
| 접근 제어 | RBAC (역할 기반) | NFR-SECU-010 |
|
||||
| 개인정보 보호 | GDPR, 개인정보보호법 준수 | NFR-SECU-010 |
|
||||
| 보유 기간 | 이벤트 종료 후 3개월 | NFR-SECU-010 |
|
||||
|
||||
---
|
||||
|
||||
## 안정성 및 확장성 요구사항
|
||||
|
||||
| 항목 | 요구사항 | 관련 유저스토리 |
|
||||
|------|----------|----------------|
|
||||
| 자동 재시도 | 배포 실패 시 최대 3회 (지수 백오프) | NFR-RELI-010 |
|
||||
| 동시 처리 | 최소 100개 이벤트 동시 처리 | NFR-SCAL-010 |
|
||||
| Auto Scaling | 마이크로서비스 수평 확장 | NFR-SCAL-010 |
|
||||
| 로드 밸런싱 | 트래픽 분산 | NFR-SCAL-010 |
|
||||
| 메시지 큐 | 비동기 처리 (RabbitMQ/Kafka) | NFR-SCAL-010 |
|
||||
@ -1,339 +0,0 @@
|
||||
# 프로토타입 분석 보고서
|
||||
|
||||
**분석일**: 2025-10-21
|
||||
**분석자**: Frontend Architect
|
||||
**분석 범위**: design/uiux/prototype/ 디렉토리 (01번~17번 화면)
|
||||
|
||||
---
|
||||
|
||||
## 📊 전체 화면 구성
|
||||
|
||||
프로토타입은 총 **17개 화면**으로 구성되어 있으며, 다음과 같은 주요 영역으로 분류됩니다:
|
||||
|
||||
### 1. 인증 영역 (01~04)
|
||||
- **01-로그인.html**: 이메일/비밀번호 로그인
|
||||
- **02-회원가입.html**: 신규 사용자 등록
|
||||
- **03-프로필.html**: 사용자 프로필 관리
|
||||
- **04-로그아웃확인.html**: 로그아웃 확인 모달
|
||||
|
||||
### 2. 대시보드 영역 (05~06)
|
||||
- **05-대시보드.html**: 메인 홈 화면
|
||||
- KPI 요약 (진행 중 이벤트, 총 참여자, 평균 ROI)
|
||||
- 빠른 시작 (새 이벤트, 분석)
|
||||
- 진행 중인 이벤트 목록
|
||||
- 최근 활동 타임라인
|
||||
- **06-이벤트목록.html**: 전체 이벤트 목록 및 필터링
|
||||
|
||||
### 3. 이벤트 생성 플로우 (07~12)
|
||||
- **07-이벤트목적선택.html**: 이벤트 목적 선택 (신규고객 유치, 재방문 유도, 브랜드 인지도, 고객 데이터 수집)
|
||||
- **08-AI이벤트추천.html**: AI 트렌드 분석 및 예산별 이벤트 추천 (저/중/고비용, 온라인/오프라인)
|
||||
- **09-콘텐츠미리보기.html**: SNS 이미지 스타일 선택 (카드 선택 UI)
|
||||
- **10-콘텐츠편집.html**: 텍스트 편집 및 색상 조정
|
||||
- **11-배포채널선택.html**: 배포 채널 선택 (우리동네TV, 링고비즈, SNS, QR코드)
|
||||
- **12-최종승인.html**: 이벤트 최종 검토 및 승인
|
||||
|
||||
### 4. 이벤트 관리 및 모니터링 (13~17)
|
||||
- **13-이벤트상세.html**: 이벤트 상세 정보 및 실시간 KPI
|
||||
- **14-참여자목록.html**: 참여자 관리 및 필터링
|
||||
- **15-이벤트참여.html**: 사용자 이벤트 참여 화면 (고객용)
|
||||
- **16-당첨자추첨.html**: 당첨자 추첨 및 관리
|
||||
- **17-성과분석.html**: 실시간 대시보드 및 성과 분석
|
||||
|
||||
---
|
||||
|
||||
## 🔄 화면 흐름도
|
||||
|
||||
```
|
||||
[로그인/회원가입]
|
||||
↓
|
||||
[대시보드] ←→ [프로필]
|
||||
↓
|
||||
├─ [이벤트목록] → [이벤트상세] → [참여자목록] → [당첨자추첨]
|
||||
│ ↓
|
||||
│ [성과분석]
|
||||
│
|
||||
└─ [새 이벤트 생성 플로우]
|
||||
↓
|
||||
[목적선택]
|
||||
↓
|
||||
[AI추천]
|
||||
↓
|
||||
[콘텐츠미리보기] (스타일 선택)
|
||||
↓
|
||||
[콘텐츠편집]
|
||||
↓
|
||||
[배포채널선택]
|
||||
↓
|
||||
[최종승인]
|
||||
↓
|
||||
[이벤트상세]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 주요 UI/UX 패턴
|
||||
|
||||
### Bottom Navigation
|
||||
- **구성**: 홈, 이벤트, 분석, 프로필
|
||||
- **특징**: 모든 메인 화면에서 일관되게 표시
|
||||
- **네비게이션**:
|
||||
- 홈 → 05-대시보드.html
|
||||
- 이벤트 → 06-이벤트목록.html
|
||||
- 분석 → 17-성과분석.html
|
||||
- 프로필 → 03-프로필.html
|
||||
|
||||
### Header
|
||||
- **구성**: 타이틀, 뒤로가기 버튼, 프로필 버튼
|
||||
- **특징**:
|
||||
- 대시보드에서는 뒤로가기 없음
|
||||
- 이벤트 생성 플로우에서는 프로필 버튼 숨김
|
||||
|
||||
### FAB (Floating Action Button)
|
||||
- **위치**: 우측 하단 고정
|
||||
- **기능**: 새 이벤트 생성 (07-이벤트목적선택.html로 이동)
|
||||
|
||||
### 카드 기반 UI
|
||||
- 옵션 선택, 이벤트 목록, KPI 표시 등 대부분의 콘텐츠가 카드 형식
|
||||
- 일관된 그림자, 라운드 모서리, 패딩 적용
|
||||
|
||||
---
|
||||
|
||||
## 🔧 최근 변경사항 (세션 기록)
|
||||
|
||||
### 1. 09-콘텐츠미리보기.html
|
||||
**변경**: Radio 버튼 → 카드 선택 UI
|
||||
- **Before**: 전통적인 Radio 버튼 리스트
|
||||
- **After**:
|
||||
- 각 스타일을 카드로 표시
|
||||
- 선택 시 카드 테두리 강조 (빨간색)
|
||||
- 우측 상단에 체크 배지 표시
|
||||
- 이미지 클릭 시 전체화면 모달로 확대
|
||||
|
||||
**영향**: 사용자 경험 개선, 시각적 명확성 향상
|
||||
|
||||
### 2. 10-콘텐츠편집.html
|
||||
**변경**: 로고 위치 섹션 삭제 + 색상 조정
|
||||
- **삭제**: 로고 위치 선택 (상단, 하단, 중앙) 섹션 제거
|
||||
- **유지**: 텍스트 편집, 색상 조정 기능
|
||||
- **이유**: 간소화 및 사용자 복잡도 감소
|
||||
|
||||
### 3. 05-대시보드.html
|
||||
**변경**: AI추천, 템플릿 버튼 삭제
|
||||
- **삭제 항목**:
|
||||
- "AI 추천" 버튼
|
||||
- "템플릿" 버튼
|
||||
- **유지**: 새 이벤트, 분석 버튼만 유지
|
||||
- **이유**: 기능 중복 제거 (AI 추천은 이벤트 생성 플로우에 통합)
|
||||
|
||||
### 4. 햄버거 메뉴 제거
|
||||
**변경**: 전체 화면에서 햄버거 메뉴 제거
|
||||
- **대체**: Bottom Navigation으로 모든 주요 기능 접근
|
||||
- **영향**: 모바일 친화적인 네비게이션 구조
|
||||
|
||||
### 5. Bottom Navigation 연결 수정
|
||||
**변경**: 분석 탭 연결
|
||||
- **Before**: 미정의 또는 다른 경로
|
||||
- **After**: 17-성과분석.html
|
||||
- **영향**: 네비게이션 일관성 확보
|
||||
|
||||
### 6. 13-이벤트상세.html
|
||||
**변경**: Bottom Nav focus
|
||||
- **수정**: Bottom Navigation의 포커스를 'events'로 설정
|
||||
- **이유**: 이벤트 상세는 이벤트 섹션의 하위 화면
|
||||
|
||||
---
|
||||
|
||||
## 🎯 핵심 기능별 화면 매핑
|
||||
|
||||
### AI 기반 이벤트 생성
|
||||
1. **목적 인식** (07-이벤트목적선택.html)
|
||||
- 신규고객 유치, 재방문 유도, 브랜드 인지도, 데이터 수집
|
||||
2. **AI 추천** (08-AI이벤트추천.html)
|
||||
- 업종/지역/시즌 트렌드 분석
|
||||
- 예산별 온라인/오프라인 이벤트 추천
|
||||
3. **콘텐츠 생성** (09-콘텐츠미리보기.html, 10-콘텐츠편집.html)
|
||||
- 이미지 스타일 선택
|
||||
- 텍스트 및 색상 편집
|
||||
4. **배포 설정** (11-배포채널선택.html)
|
||||
- 다중 채널 선택 및 옵션 설정
|
||||
5. **최종 검토** (12-최종승인.html)
|
||||
- 전체 설정 확인 및 이벤트 생성
|
||||
|
||||
### 이벤트 관리
|
||||
- **목록 조회** (06-이벤트목록.html): 검색, 필터링, 정렬
|
||||
- **상세 모니터링** (13-이벤트상세.html): 실시간 KPI, 참여 추이
|
||||
- **참여자 관리** (14-참여자목록.html): 검색, 필터링, 엑셀 다운로드
|
||||
- **당첨 관리** (16-당첨자추첨.html): 자동 추첨 및 결과 관리
|
||||
|
||||
### 성과 분석
|
||||
- **대시보드** (17-성과분석.html)
|
||||
- 전체 이벤트 요약 통계
|
||||
- 이벤트별 성과 비교
|
||||
- ROI 상세 분석
|
||||
- 채널별 효율성
|
||||
|
||||
---
|
||||
|
||||
## 📱 반응형 디자인
|
||||
|
||||
### 브레이크포인트
|
||||
- **Mobile**: 기본
|
||||
- **Tablet**: 768px 이상 (grid-cols 조정)
|
||||
- **Desktop**: 1024px 이상 (side-by-side 레이아웃)
|
||||
|
||||
### 주요 반응형 패턴
|
||||
1. **그리드 시스템**: 모바일(1열) → 태블릿(2열) → 데스크탑(3-4열)
|
||||
2. **카드 레이아웃**: 세로 스택 → 가로 배치
|
||||
3. **네비게이션**: Bottom Nav 고정 (모든 화면)
|
||||
4. **콘텐츠 편집** (10번): 모바일(세로 스택) → 데스크탑(좌우 분할)
|
||||
|
||||
---
|
||||
|
||||
## 🔍 삭제된 기능
|
||||
|
||||
### 1. 햄버거 메뉴
|
||||
- **삭제 이유**: Bottom Navigation으로 대체
|
||||
- **영향**: 네비게이션 복잡도 감소
|
||||
|
||||
### 2. 대시보드 AI추천/템플릿 버튼
|
||||
- **삭제 이유**: 기능 중복 (이벤트 생성 플로우에 통합)
|
||||
- **영향**: 화면 간소화
|
||||
|
||||
### 3. 로고 위치 선택
|
||||
- **삭제 이유**: 편집 기능 단순화
|
||||
- **영향**: 사용자 설정 옵션 감소
|
||||
|
||||
---
|
||||
|
||||
## ➕ 추가/변경된 기능
|
||||
|
||||
### 1. 카드 선택 UI (09-콘텐츠미리보기.html)
|
||||
- **추가**: 비주얼 중심의 스타일 선택
|
||||
- **기능**:
|
||||
- 카드 클릭 선택
|
||||
- 이미지 전체화면 보기
|
||||
- 선택 상태 시각적 피드백
|
||||
|
||||
### 2. 성과 분석 탭 연결
|
||||
- **추가**: Bottom Nav에서 17-성과분석.html 직접 접근
|
||||
- **영향**: 주요 기능으로 성과 분석 격상
|
||||
|
||||
### 3. 실시간 KPI 업데이트
|
||||
- **추가**: 이벤트 상세(13번)에서 실시간 데이터 표시
|
||||
- **기능**: 참여자, 조회수, ROI, 전환율 실시간 갱신
|
||||
|
||||
---
|
||||
|
||||
## 🎨 UI/UX 개선 포인트
|
||||
|
||||
### 강점
|
||||
1. ✅ **일관된 디자인 시스템**: 색상, 타이포그래피, 컴포넌트
|
||||
2. ✅ **모바일 우선 설계**: Bottom Navigation, FAB
|
||||
3. ✅ **명확한 정보 계층**: 카드, 헤더, 섹션 구분
|
||||
4. ✅ **시각적 피드백**: 버튼 상태, 카드 선택, 로딩 상태
|
||||
5. ✅ **접근성 고려**: Material Icons, 명확한 레이블
|
||||
|
||||
### 개선 권장사항
|
||||
1. ⚠️ **키보드 네비게이션**: 접근성 개선 필요
|
||||
2. ⚠️ **에러 상태 처리**: 일부 화면에서 에러 메시지 미비
|
||||
3. ⚠️ **로딩 상태**: 비동기 작업 시 로딩 인디케이터 추가 권장
|
||||
4. ⚠️ **빈 상태**: 데이터 없을 때 처리 개선
|
||||
|
||||
---
|
||||
|
||||
## 📋 유저스토리 업데이트 핵심 사항
|
||||
|
||||
### 1. 화면 구성 변경
|
||||
- **대시보드**: AI추천/템플릿 버튼 삭제 → "새 이벤트", "분석" 2개만
|
||||
- **네비게이션**: 햄버거 메뉴 제거 → Bottom Navigation 4개 탭
|
||||
|
||||
### 2. 이벤트 생성 플로우 수정
|
||||
- **09번 화면**: Radio 버튼 → 카드 선택 UI
|
||||
- **10번 화면**: 로고 위치 섹션 삭제
|
||||
- **흐름**: 07 → 08 → 09 → 10 → 11 → 12
|
||||
|
||||
### 3. 분석 기능 강화
|
||||
- **Bottom Nav**: "분석" 탭 추가 (17-성과분석.html)
|
||||
- **실시간 모니터링**: 이벤트 상세에서 실시간 KPI
|
||||
|
||||
### 4. 주요 화면 번호
|
||||
```
|
||||
01: 로그인
|
||||
02: 회원가입
|
||||
03: 프로필
|
||||
04: 로그아웃 확인
|
||||
05: 대시보드
|
||||
06: 이벤트목록
|
||||
07: 이벤트목적선택
|
||||
08: AI이벤트추천
|
||||
09: 콘텐츠미리보기 (스타일 선택)
|
||||
10: 콘텐츠편집
|
||||
11: 배포채널선택
|
||||
12: 최종승인
|
||||
13: 이벤트상세
|
||||
14: 참여자목록
|
||||
15: 이벤트참여 (고객용)
|
||||
16: 당첨자추첨
|
||||
17: 성과분석
|
||||
```
|
||||
|
||||
### 5. 네비게이션 구조
|
||||
```
|
||||
Bottom Navigation (모든 주요 화면):
|
||||
- 홈: 05-대시보드.html
|
||||
- 이벤트: 06-이벤트목록.html
|
||||
- 분석: 17-성과분석.html
|
||||
- 프로필: 03-프로필.html
|
||||
|
||||
FAB (새 이벤트):
|
||||
- 07-이벤트목적선택.html
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 화면 간 의존성
|
||||
|
||||
### 순차적 플로우
|
||||
1. **이벤트 생성**: 07 → 08 → 09 → 10 → 11 → 12 → 13
|
||||
2. **참여자 관리**: 13 → 14 → 16
|
||||
3. **성과 분석**: 13 → 17
|
||||
|
||||
### 독립적 화면
|
||||
- 05 (대시보드)
|
||||
- 06 (이벤트목록)
|
||||
- 17 (성과분석)
|
||||
|
||||
---
|
||||
|
||||
## 📊 완성도 평가
|
||||
|
||||
| 영역 | 완성도 | 비고 |
|
||||
|------|--------|------|
|
||||
| 화면 디자인 | ✅ 95% | 일관된 디자인 시스템 |
|
||||
| 화면 흐름 | ✅ 90% | 명확한 사용자 여정 |
|
||||
| 반응형 | ✅ 85% | 주요 브레이크포인트 지원 |
|
||||
| 접근성 | ⚠️ 70% | 키보드 네비게이션 개선 필요 |
|
||||
| 에러 처리 | ⚠️ 60% | 에러 상태 보완 필요 |
|
||||
| 로딩 상태 | ⚠️ 65% | 비동기 작업 피드백 추가 권장 |
|
||||
|
||||
---
|
||||
|
||||
## ✅ 결론
|
||||
|
||||
프로토타입은 **17개 화면**으로 구성되어 있으며, AI 기반 이벤트 자동 생성의 전체 사용자 여정을 잘 표현하고 있습니다.
|
||||
|
||||
**주요 강점**:
|
||||
- 명확한 정보 구조와 화면 흐름
|
||||
- 일관된 디자인 시스템
|
||||
- 모바일 친화적 네비게이션
|
||||
|
||||
**개선 영역**:
|
||||
- 접근성 (키보드 네비게이션, 스크린 리더)
|
||||
- 에러 및 로딩 상태 처리
|
||||
- 빈 상태 디자인
|
||||
|
||||
**유저스토리 업데이트 필요 사항**:
|
||||
1. 화면 번호 및 명칭 정리 (01~17번)
|
||||
2. 네비게이션 구조 변경 (햄버거 메뉴 제거, Bottom Nav 4개 탭)
|
||||
3. 이벤트 생성 플로우 상세화 (7단계: 목적→AI추천→스타일→편집→배포→승인→상세)
|
||||
4. 분석 기능 강화 (Bottom Nav 추가, 실시간 KPI)
|
||||
@ -1,104 +0,0 @@
|
||||
=====================================
|
||||
PlantUML 파일 검증 시작 (UTF-8)
|
||||
=====================================
|
||||
|
||||
[1] 검증 중: design/backend/sequence/inner/ai-트렌드분석및추천.puml
|
||||
❌ 실패: ai-트렌드분석및추천.puml (HTTP 400)
|
||||
|
||||
[2] 검증 중: design/backend/sequence/inner/analytics-대시보드조회-캐시미스.puml
|
||||
✅ 성공: analytics-대시보드조회-캐시미스.puml
|
||||
|
||||
[3] 검증 중: design/backend/sequence/inner/analytics-대시보드조회-캐시히트.puml
|
||||
✅ 성공: analytics-대시보드조회-캐시히트.puml
|
||||
|
||||
[4] 검증 중: design/backend/sequence/inner/analytics-배포완료구독.puml
|
||||
✅ 성공: analytics-배포완료구독.puml
|
||||
|
||||
[5] 검증 중: design/backend/sequence/inner/analytics-이벤트생성구독.puml
|
||||
✅ 성공: analytics-이벤트생성구독.puml
|
||||
|
||||
[6] 검증 중: design/backend/sequence/inner/analytics-참여자등록구독.puml
|
||||
✅ 성공: analytics-참여자등록구독.puml
|
||||
|
||||
[7] 검증 중: design/backend/sequence/inner/content-이미지생성.puml
|
||||
❌ 실패: content-이미지생성.puml (HTTP 400)
|
||||
|
||||
[8] 검증 중: design/backend/sequence/inner/distribution-다중채널배포.puml
|
||||
❌ 실패: distribution-다중채널배포.puml (HTTP 400)
|
||||
|
||||
[9] 검증 중: design/backend/sequence/inner/distribution-배포상태조회.puml
|
||||
✅ 성공: distribution-배포상태조회.puml
|
||||
|
||||
[10] 검증 중: design/backend/sequence/inner/event-AI추천요청.puml
|
||||
✅ 성공: event-AI추천요청.puml
|
||||
|
||||
[11] 검증 중: design/backend/sequence/inner/event-목록조회.puml
|
||||
✅ 성공: event-목록조회.puml
|
||||
|
||||
[12] 검증 중: design/backend/sequence/inner/event-목적선택.puml
|
||||
✅ 성공: event-목적선택.puml
|
||||
|
||||
[13] 검증 중: design/backend/sequence/inner/event-상세조회.puml
|
||||
✅ 성공: event-상세조회.puml
|
||||
|
||||
[14] 검증 중: design/backend/sequence/inner/event-콘텐츠선택.puml
|
||||
✅ 성공: event-콘텐츠선택.puml
|
||||
|
||||
[15] 검증 중: design/backend/sequence/inner/event-대시보드조회.puml
|
||||
❌ 실패: event-대시보드조회.puml (HTTP 400)
|
||||
|
||||
[16] 검증 중: design/backend/sequence/inner/event-추천결과조회.puml
|
||||
✅ 성공: event-추천결과조회.puml
|
||||
|
||||
[17] 검증 중: design/backend/sequence/inner/event-이미지결과조회.puml
|
||||
✅ 성공: event-이미지결과조회.puml
|
||||
|
||||
[18] 검증 중: design/backend/sequence/inner/event-이미지생성요청.puml
|
||||
✅ 성공: event-이미지생성요청.puml
|
||||
|
||||
[19] 검증 중: design/backend/sequence/inner/event-최종승인및배포.puml
|
||||
✅ 성공: event-최종승인및배포.puml
|
||||
|
||||
[20] 검증 중: design/backend/sequence/inner/participation-당첨자추첨.puml
|
||||
✅ 성공: participation-당첨자추첨.puml
|
||||
|
||||
[21] 검증 중: design/backend/sequence/inner/participation-이벤트참여.puml
|
||||
✅ 성공: participation-이벤트참여.puml
|
||||
|
||||
[22] 검증 중: design/backend/sequence/inner/participation-참여자목록조회.puml
|
||||
✅ 성공: participation-참여자목록조회.puml
|
||||
|
||||
[23] 검증 중: design/backend/sequence/inner/user-로그인.puml
|
||||
✅ 성공: user-로그인.puml
|
||||
|
||||
[24] 검증 중: design/backend/sequence/inner/user-로그아웃.puml
|
||||
✅ 성공: user-로그아웃.puml
|
||||
|
||||
[25] 검증 중: design/backend/sequence/inner/user-회원가입.puml
|
||||
✅ 성공: user-회원가입.puml
|
||||
|
||||
[26] 검증 중: design/backend/sequence/inner/user-프로필수정.puml
|
||||
✅ 성공: user-프로필수정.puml
|
||||
|
||||
[27] 검증 중: design/backend/sequence/outer/고객참여플로우.puml
|
||||
✅ 성공: 고객참여플로우.puml
|
||||
|
||||
[28] 검증 중: design/backend/sequence/outer/성과분석플로우.puml
|
||||
✅ 성공: 성과분석플로우.puml
|
||||
|
||||
[29] 검증 중: design/backend/sequence/outer/사용자인증플로우.puml
|
||||
✅ 성공: 사용자인증플로우.puml
|
||||
|
||||
[30] 검증 중: design/backend/sequence/outer/이벤트생성플로우.puml
|
||||
❌ 실패: 이벤트생성플로우.puml (HTTP 400)
|
||||
|
||||
=====================================
|
||||
검증 완료
|
||||
=====================================
|
||||
총 파일 수: 30
|
||||
성공: 25
|
||||
실패: 5
|
||||
|
||||
상세 결과: debug/puml-validation/validation-result.txt
|
||||
오류 상세: debug/puml-validation/validation-errors.txt
|
||||
=====================================
|
||||
@ -1,32 +0,0 @@
|
||||
[From string (line 208) ]
|
||||
|
||||
@startuml ai-트렌드분석및추천
|
||||
|
||||
|
||||
|
||||
|
||||
...
|
||||
... ( skipping 354 lines )
|
||||
...
|
||||
트렌드: {트렌드}
|
||||
매장: {매장정보}
|
||||
|
||||
출력 형식:
|
||||
- 이벤트 제목
|
||||
- 추천 경품 (예산: 저)
|
||||
- 참여 방법 (난이도: 낮음)
|
||||
- 예상 참여자 수
|
||||
- 예상 비용
|
||||
- 예상 ROI"
|
||||
end note
|
||||
|
||||
AIClient -> ExternalAPI: POST /api/v1/recommend\n(저비용 옵션)
|
||||
ExternalAPI --> AIClient: 200 OK\n{추천안 1}
|
||||
AIClient --> CB: 추천안 1
|
||||
deactivate AIClient
|
||||
CB --> RecommendEngine: 옵션 1 완료
|
||||
deactivate CB
|
||||
|
||||
and
|
||||
^^^^^
|
||||
Syntax Error? (Assumed diagram type: sequence)
|
||||
|
Before Width: | Height: | Size: 223 KiB |
|
Before Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 80 KiB |