@adstage/web-sdk 2.5.3 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +59 -0
  2. package/dist/index.cjs.js +2073 -1045
  3. package/dist/index.d.ts +55 -12
  4. package/dist/index.esm.js +2073 -1045
  5. package/dist/index.standalone.js +2073 -1045
  6. package/package.json +1 -1
  7. package/src/constants/endpoints.ts +0 -1
  8. package/src/core/{AdStage.ts → adstage.ts} +36 -8
  9. package/src/index.ts +9 -3
  10. package/src/managers/ads/advertisement-event-tracker.ts +15 -11
  11. package/src/managers/ads/carousel-slider-manager.ts +90 -12
  12. package/src/managers/ads/slider-event-tracker.ts +57 -0
  13. package/src/managers/ads/text-transition-manager.ts +91 -26
  14. package/src/modules/ads/ad-renderer.ts +259 -0
  15. package/src/modules/ads/{AdsModule.ts → ads-module.ts} +202 -21
  16. package/src/modules/ads/interfaces/i-ad-renderer.ts +77 -0
  17. package/src/modules/ads/renderers/banner-ad-renderer.ts +414 -0
  18. package/src/modules/ads/renderers/base-ad-renderer.ts +340 -0
  19. package/src/modules/ads/renderers/interstitial-ad-renderer.ts +256 -0
  20. package/src/modules/ads/renderers/native-ad-renderer.ts +154 -0
  21. package/src/modules/ads/renderers/text-ad-renderer.ts +120 -0
  22. package/src/modules/ads/renderers/video-ad-renderer.ts +433 -0
  23. package/src/modules/config/{ConfigModule.ts → config-module.ts} +1 -5
  24. package/src/react/{AdStageProvider.tsx → ad-stage-provider.tsx} +1 -1
  25. package/src/react/index.ts +2 -2
  26. package/src/types/config.ts +2 -184
  27. package/src/utils/ad-click-handler.ts +155 -0
  28. package/src/utils/text-ad-utils.ts +37 -0
  29. package/src/dummy/ads_dummy.json +0 -84
  30. package/src/modules/ads/AdRenderer.ts +0 -735
  31. package/src/renderers/banner-renderer.ts +0 -35
  32. package/src/renderers/base-renderer.ts +0 -209
  33. package/src/renderers/index.ts +0 -71
  34. package/src/renderers/interstitial-renderer.ts +0 -70
  35. package/src/renderers/native-renderer.ts +0 -35
  36. package/src/renderers/text-renderer.ts +0 -94
  37. package/src/renderers/video-renderer.ts +0 -63
  38. /package/src/modules/events/{EventsModule.ts → events-module.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adstage/web-sdk",
3
- "version": "2.5.3",
3
+ "version": "2.6.1",
4
4
  "description": "AdStage Web SDK - Production-ready marketing platform SDK with React Provider support for seamless integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs.js",
@@ -53,7 +53,6 @@ export class EndpointBuilder {
53
53
  */
54
54
  setBaseUrl(url: string): void {
55
55
  this.baseUrl = url;
56
- console.log('🔄 API endpoint changed:', url);
57
56
  }
58
57
 
59
58
  /**
@@ -4,9 +4,10 @@
4
4
  */
5
5
 
6
6
  import { AdStageConfig, ModuleName } from '../types/config';
7
- import { AdsModule } from '../modules/ads/AdsModule';
8
- import { ConfigModule } from '../modules/config/ConfigModule';
9
- import { EventsModule } from '../modules/events/EventsModule';
7
+ import { AdsModule } from '../modules/ads/ads-module';
8
+ import { ConfigModule } from '../modules/config/config-module';
9
+ import { EventsModule } from '../modules/events/events-module';
10
+ import { ViewableEventTracker } from '../managers/ads/viewable-event-tracker';
10
11
  import { endpoints } from '../constants/endpoints';
11
12
 
12
13
  export class AdStage {
@@ -46,10 +47,6 @@ export class AdStage {
46
47
  timeout: 30000,
47
48
  debug: false,
48
49
  modules: ['ads', 'events', 'config'],
49
- validateOnInit: false,
50
- fallbackMode: true,
51
- offlineMode: false,
52
- productionMode: false,
53
50
  ...config
54
51
  };
55
52
 
@@ -78,7 +75,7 @@ export class AdStage {
78
75
  version: '2.0.0',
79
76
  modules: enabledModules,
80
77
  apiKey: config.apiKey.substring(0, 8) + '...',
81
- mode: config.productionMode ? 'production' : 'development'
78
+ mode: 'development'
82
79
  });
83
80
  }
84
81
  }
@@ -131,6 +128,37 @@ export class AdStage {
131
128
  AdStage.instance._config = null;
132
129
  }
133
130
  }
131
+
132
+ /**
133
+ * 디버그용 메서드들
134
+ */
135
+ public static debug = {
136
+ /**
137
+ * 모든 viewable 추적 데이터 초기화
138
+ */
139
+ clearAllViewable: (): void => {
140
+ ViewableEventTracker.clear();
141
+ console.log('✅ AdStage Debug: 모든 viewable 추적 데이터 초기화됨');
142
+ },
143
+
144
+ /**
145
+ * 특정 광고의 viewable 추적 초기화
146
+ */
147
+ clearAdViewable: (adId: string, slotId: string): void => {
148
+ ViewableEventTracker.clearAdViewable(adId, slotId);
149
+ console.log(`✅ AdStage Debug: 광고 ${adId}(${slotId})의 viewable 추적 초기화됨`);
150
+ },
151
+
152
+ /**
153
+ * 현재 추적 중인 viewable 상태 확인
154
+ */
155
+ getViewableStatus: (): void => {
156
+ console.log('📊 AdStage Debug: 현재 viewable 추적 상태', {
157
+ trackedCount: (ViewableEventTracker as any).viewableTracker.size,
158
+ trackedItems: Array.from((ViewableEventTracker as any).viewableTracker)
159
+ });
160
+ }
161
+ };
134
162
  }
135
163
 
136
164
  // 전역 네임스페이스로 내보내기
package/src/index.ts CHANGED
@@ -4,7 +4,8 @@
4
4
  */
5
5
 
6
6
  // 메인 네임스페이스 클래스
7
- export { default as AdStage } from './core/AdStage';
7
+ export { default as AdStage } from './core/adstage';
8
+ import AdStageCore from './core/adstage';
8
9
 
9
10
  // React 통합
10
11
  export { AdStageProvider, useAdStageContext, useAdStageInstance } from './react';
@@ -13,8 +14,8 @@ export { AdStageProvider, useAdStageContext, useAdStageInstance } from './react'
13
14
  export type { AdStageConfig, ModuleName, BaseModule, ApiResponse, OrganizationInfo } from './types/config';
14
15
 
15
16
  // 모듈별 타입
16
- export type { AdOptions } from './modules/ads/AdsModule';
17
- export type { EventProperties, PageData } from './modules/events/EventsModule';
17
+ export type { AdOptions } from './modules/ads/ads-module';
18
+ export type { EventProperties, PageData } from './modules/events/events-module';
18
19
 
19
20
  // 광고 관련 타입
20
21
  export type { AdType, AdEventType, Advertisement, AdSlot } from './types/advertisement';
@@ -22,3 +23,8 @@ export type { AdType, AdEventType, Advertisement, AdSlot } from './types/adverti
22
23
  // 버전 정보
23
24
  export const SDK_VERSION = '2.0.0';
24
25
  export const SUPPORTED_MODULES = ['ads', 'events', 'config'] as const;
26
+
27
+ // 브라우저 환경에서 전역 객체로 노출 (디버깅용)
28
+ if (typeof window !== 'undefined') {
29
+ (window as any).AdStage = AdStageCore;
30
+ }
@@ -38,16 +38,6 @@ export class AdvertisementEventTracker {
38
38
  console.log(`🚀 AdvertisementEventTracker: Processing ${eventType} event for ad ${adId} in slot ${slotId}`);
39
39
  }
40
40
 
41
- // VIEWABLE 이벤트 중복 확인
42
- if (eventType === AdEventType.VIEWABLE) {
43
- if (ViewableEventTracker.isDuplicateViewable(adId, slotId, this.debug)) {
44
- if (this.debug) {
45
- console.log(`⏭️ Skipping duplicate viewable event for ad ${adId} in slot ${slotId}`);
46
- }
47
- return;
48
- }
49
- }
50
-
51
41
  // 현재 슬롯 정보 가져오기
52
42
  const slot = this.slots.get(slotId);
53
43
 
@@ -100,6 +90,12 @@ export class AdvertisementEventTracker {
100
90
  headers,
101
91
  eventData
102
92
  });
93
+ console.log(`🌐 Full API call details:`, {
94
+ method: 'POST',
95
+ url,
96
+ hasApiKey: !!this.apiKey,
97
+ bodySize: JSON.stringify(eventData).length
98
+ });
103
99
  }
104
100
 
105
101
  const response = await fetch(url, {
@@ -124,7 +120,15 @@ export class AdvertisementEventTracker {
124
120
  console.log(`✅ Successfully tracked advertisement event: ${eventType} for ad ${adId}`);
125
121
  }
126
122
  } catch (error) {
127
- console.error('Failed to track advertisement event:', error);
123
+ console.error('Failed to track advertisement event:', error);
124
+ console.error('🔍 Debug info:', {
125
+ baseUrl: this.baseUrl,
126
+ apiKey: this.apiKey ? `${this.apiKey.substring(0, 8)}...` : 'NOT_SET',
127
+ url: `${this.baseUrl}/advertisements/events/${adId}/${eventType}`,
128
+ eventType,
129
+ adId,
130
+ slotId
131
+ });
128
132
  }
129
133
  }
130
134
 
@@ -1,6 +1,7 @@
1
1
  import { AdType, AdEventType } from '../../types/advertisement';
2
2
  import type { AdSlot, Advertisement } from '../../types/advertisement';
3
- import { AdRendererFactory } from '../../renderers';
3
+ import { SliderEventTracker } from './slider-event-tracker';
4
+ import { AdClickHandler } from '../../utils/ad-click-handler';
4
5
 
5
6
  /**
6
7
  * 캐러셀 슬라이더 관리 클래스
@@ -10,6 +11,75 @@ import { AdRendererFactory } from '../../renderers';
10
11
  * - 도트 인디케이터 포함
11
12
  */
12
13
  export class CarouselSliderManager {
14
+ /**
15
+ * 간단한 광고 요소 생성 (크기 측정용)
16
+ */
17
+ private static createSimpleAdElement(slot: AdSlot, advertisement: Advertisement): HTMLElement {
18
+ const adElement = document.createElement('div');
19
+ adElement.className = `adstage-ad adstage-${String(slot.adType).toLowerCase()}`;
20
+ adElement.setAttribute('data-adstage-ad-id', advertisement._id);
21
+ adElement.setAttribute('data-adstage-slot-id', slot.id);
22
+
23
+ // 기본 스타일 설정
24
+ adElement.style.display = 'block';
25
+ adElement.style.width = '100%';
26
+ adElement.style.height = 'auto';
27
+
28
+ // 광고 타입별 기본 컨테이너 설정
29
+ switch (slot.adType) {
30
+ case AdType.BANNER:
31
+ if (advertisement.imageUrl) {
32
+ const img = document.createElement('img');
33
+ img.src = advertisement.imageUrl;
34
+ img.style.width = '100%';
35
+ img.style.height = 'auto';
36
+ img.style.objectFit = 'cover';
37
+ adElement.appendChild(img);
38
+ } else {
39
+ adElement.style.height = '100px';
40
+ adElement.style.backgroundColor = '#f0f0f0';
41
+ adElement.style.border = '1px dashed #ccc';
42
+ adElement.textContent = 'Banner Ad';
43
+ }
44
+ break;
45
+ case AdType.VIDEO:
46
+ if (advertisement.videoUrl) {
47
+ const video = document.createElement('video');
48
+ video.src = advertisement.videoUrl;
49
+ video.style.width = '100%';
50
+ video.style.height = 'auto';
51
+ adElement.appendChild(video);
52
+ } else {
53
+ adElement.style.height = '200px';
54
+ adElement.style.backgroundColor = '#000';
55
+ adElement.style.border = '1px solid #666';
56
+ adElement.textContent = 'Video Ad';
57
+ adElement.style.color = 'white';
58
+ }
59
+ break;
60
+ case AdType.TEXT:
61
+ if (advertisement.textContent) {
62
+ const textDiv = document.createElement('div');
63
+ textDiv.textContent = advertisement.textContent || '';
64
+ textDiv.style.padding = '8px';
65
+ textDiv.style.fontSize = '14px';
66
+ adElement.appendChild(textDiv);
67
+ } else {
68
+ adElement.style.height = '50px';
69
+ adElement.style.padding = '8px';
70
+ adElement.textContent = 'Text Ad';
71
+ }
72
+ break;
73
+ default:
74
+ adElement.style.height = '100px';
75
+ adElement.style.border = '1px dashed #ccc';
76
+ adElement.style.backgroundColor = '#f9f9f9';
77
+ adElement.textContent = `${slot.adType} Ad`;
78
+ }
79
+
80
+ return adElement;
81
+ }
82
+
13
83
  /**
14
84
  * Create carousel slider container with dot indicators and navigation
15
85
  */
@@ -17,7 +87,8 @@ export class CarouselSliderManager {
17
87
  slot: AdSlot,
18
88
  advertisements: any[],
19
89
  options: any,
20
- trackEventCallback: (adId: string, slotId: string, eventType: AdEventType) => void
90
+ trackEventCallback: (adId: string, slotId: string, eventType: AdEventType) => void,
91
+ debug: boolean = false
21
92
  ): HTMLElement {
22
93
  const sliderWrapper = document.createElement('div');
23
94
  sliderWrapper.className = 'adstage-slider-wrapper';
@@ -88,11 +159,7 @@ export class CarouselSliderManager {
88
159
 
89
160
  // 모든 광고의 크기를 측정하여 최대 크기 찾기
90
161
  advertisements.forEach(ad => {
91
- const measureAdElement = AdRendererFactory.render(
92
- ad,
93
- slot,
94
- trackEventCallback
95
- );
162
+ const measureAdElement = this.createSimpleAdElement(slot, ad);
96
163
  measureContainer.appendChild(measureAdElement);
97
164
 
98
165
  const rect = measureAdElement.getBoundingClientRect();
@@ -162,10 +229,16 @@ export class CarouselSliderManager {
162
229
  });
163
230
 
164
231
  // 광고 렌더링
165
- const adElement = AdRendererFactory.render(
232
+ const adElement = this.createSimpleAdElement(slot, ad);
233
+
234
+ // 클릭 이벤트 추가 (공통 컴포넌트 사용)
235
+ AdClickHandler.addClickEventForSlider(
236
+ adElement,
166
237
  ad,
167
238
  slot,
168
- trackEventCallback
239
+ trackEventCallback,
240
+ debug,
241
+ String(slot.adType).toLowerCase()
169
242
  );
170
243
 
171
244
  slideElement.appendChild(adElement);
@@ -217,9 +290,14 @@ export class CarouselSliderManager {
217
290
  });
218
291
  }
219
292
 
220
- // 현재 슬라이드의 광고에 대해 노출 이벤트 추적 (모든 슬라이드 포함)
221
- console.log(`🎯 Triggering VIEWABLE event for slide change: ad ${advertisements[actualIndex]._id} (index: ${actualIndex}) in slot: ${slot.id}`);
222
- trackEventCallback(advertisements[actualIndex]._id, slot.id, AdEventType.VIEWABLE);
293
+ // 🎯 공통 슬라이더 이벤트 추적 적용 (모든 슬라이드 포함)
294
+ SliderEventTracker.trackSlideViewable(
295
+ advertisements[actualIndex],
296
+ slot,
297
+ actualIndex,
298
+ trackEventCallback,
299
+ debug // debug 모드
300
+ );
223
301
  };
224
302
 
225
303
  // 무한 루프 처리 함수
@@ -0,0 +1,57 @@
1
+ import { AdEventType } from '../../types/advertisement';
2
+ import type { Advertisement, AdSlot } from '../../types/advertisement';
3
+
4
+ /**
5
+ * 슬라이더 이벤트 추적 공통 유틸리티
6
+ * - 모든 슬라이더 타입에서 일관된 VIEWABLE 이벤트 추적
7
+ * - 중복 방지는 상위 레벨에서 처리
8
+ */
9
+ export class SliderEventTracker {
10
+ /**
11
+ * 슬라이드 변경 시 VIEWABLE 이벤트 추적
12
+ * @param advertisement 현재 슬라이드의 광고
13
+ * @param slot 광고 슬롯
14
+ * @param slideIndex 현재 슬라이드 인덱스
15
+ * @param trackEventCallback 이벤트 추적 콜백
16
+ * @param debug 디버그 모드
17
+ */
18
+ static trackSlideViewable(
19
+ advertisement: Advertisement,
20
+ slot: AdSlot,
21
+ slideIndex: number,
22
+ trackEventCallback: (adId: string, slotId: string, eventType: AdEventType) => void,
23
+ debug: boolean = false
24
+ ): void {
25
+ if (debug) {
26
+ console.log(
27
+ `🎯 Triggering VIEWABLE event for slide change: ad ${advertisement._id} (index: ${slideIndex}) in slot: ${slot.id}`
28
+ );
29
+ }
30
+
31
+ // 모든 슬라이드에 대해 VIEWABLE 이벤트 추적 (첫 번째 포함)
32
+ trackEventCallback(advertisement._id, slot.id, AdEventType.VIEWABLE);
33
+ }
34
+
35
+ /**
36
+ * 초기 슬라이드 로딩 시 VIEWABLE 이벤트 추적
37
+ * @param advertisement 첫 번째 슬라이드의 광고
38
+ * @param slot 광고 슬롯
39
+ * @param trackEventCallback 이벤트 추적 콜백
40
+ * @param debug 디버그 모드
41
+ */
42
+ static trackInitialSlideViewable(
43
+ advertisement: Advertisement,
44
+ slot: AdSlot,
45
+ trackEventCallback: (adId: string, slotId: string, eventType: AdEventType) => void,
46
+ debug: boolean = false
47
+ ): void {
48
+ if (debug) {
49
+ console.log(
50
+ `🎯 Triggering initial VIEWABLE event: ad ${advertisement._id} (index: 0) in slot: ${slot.id}`
51
+ );
52
+ }
53
+
54
+ // 첫 번째 슬라이드도 동일하게 추적
55
+ trackEventCallback(advertisement._id, slot.id, AdEventType.VIEWABLE);
56
+ }
57
+ }
@@ -1,6 +1,8 @@
1
1
  import { AdType, AdEventType } from '../../types/advertisement';
2
2
  import type { AdSlot, Advertisement } from '../../types/advertisement';
3
- import { AdRendererFactory } from '../../renderers';
3
+ import { SliderEventTracker } from './slider-event-tracker';
4
+ import { TextAdUtils } from '../../utils/text-ad-utils';
5
+ import { AdClickHandler } from '../../utils/ad-click-handler';
4
6
 
5
7
  /**
6
8
  * 텍스트 전환 효과 관리 클래스
@@ -10,14 +12,34 @@ import { AdRendererFactory } from '../../renderers';
10
12
  */
11
13
  export class TextTransitionManager {
12
14
  /**
13
- * 텍스트 전환 슬라이더 컨테이너 생성
15
+ * 간단한 광고 요소 생성 (크기 측정용)
16
+ */
17
+ private static createSimpleAdElement(slot: AdSlot, advertisement: Advertisement): HTMLElement {
18
+ const adElement = document.createElement('div');
19
+ adElement.className = `adstage-ad adstage-${String(slot.adType).toLowerCase()}`;
20
+ adElement.setAttribute('data-adstage-ad-id', advertisement._id);
21
+ adElement.setAttribute('data-adstage-slot-id', slot.id);
22
+
23
+ // 🎯 공통 스타일 및 콘텐츠 적용 (중복 제거)
24
+ adElement.style.cssText = TextAdUtils.createTextAdStyles(true);
25
+ TextAdUtils.setTextAdContent(adElement, advertisement);
26
+
27
+ return adElement;
28
+ }
29
+
30
+ /**
31
+ * 텍스트 전환 컨테이너 생성
14
32
  */
15
33
  static createTextTransitionContainer(
16
34
  slot: AdSlot,
17
35
  advertisements: Advertisement[],
18
36
  options: any,
19
- trackEventCallback: (adId: string, slotId: string, eventType: AdEventType) => void
37
+ trackEventCallback: (adId: string, slotId: string, eventType: AdEventType) => void,
38
+ debug: boolean = false
20
39
  ): HTMLElement {
40
+ // 사용자가 높이를 지정했는지 미리 확인 ('auto'나 undefined면 자동 높이)
41
+ const hasUserDefinedHeight = slot.height && slot.height !== 0 && slot.height !== 'auto';
42
+
21
43
  const sliderWrapper = document.createElement('div');
22
44
  sliderWrapper.className = 'adstage-fade-slider-wrapper';
23
45
 
@@ -25,9 +47,15 @@ export class TextTransitionManager {
25
47
  const containerStyles: Record<string, string> = {
26
48
  position: 'relative',
27
49
  overflow: 'hidden',
28
- display: 'inline-block',
50
+ display: 'block',
29
51
  };
30
52
 
53
+ // 높이가 지정되지 않은 경우 자동 높이 사용
54
+ if (!hasUserDefinedHeight) {
55
+ containerStyles.height = 'auto';
56
+ containerStyles.minHeight = 'fit-content';
57
+ }
58
+
31
59
  // 사용자가 크기를 지정한 경우
32
60
  if (slot.width && slot.width !== 0) {
33
61
  let width: string;
@@ -39,7 +67,8 @@ export class TextTransitionManager {
39
67
  containerStyles.width = width;
40
68
  }
41
69
 
42
- if (slot.height && slot.height !== 0) {
70
+ // 사용자가 높이를 명시적으로 지정한 경우에만 적용 ('auto'는 제외)
71
+ if (slot.height && slot.height !== 0 && slot.height !== 'auto' && hasUserDefinedHeight) {
43
72
  let height: string;
44
73
  if (typeof slot.height === 'string') {
45
74
  height = slot.height.includes('px') || slot.height.includes('%') ? slot.height : `${slot.height}px`;
@@ -57,18 +86,20 @@ export class TextTransitionManager {
57
86
  // 슬라이드 컨테이너
58
87
  const slideContainer = document.createElement('div');
59
88
  slideContainer.className = 'adstage-fade-slide-container';
89
+
60
90
  slideContainer.style.cssText = `
61
91
  position: relative;
62
92
  width: 100%;
63
- height: 100%;
93
+ ${hasUserDefinedHeight ? 'height: 100%;' : 'height: auto; min-height: fit-content;'}
64
94
  `;
65
95
 
66
96
  // 크기 측정을 위한 임시 컨테이너 (자동 크기 계산이 필요한 경우)
67
97
  let measureContainer: HTMLElement | null = null;
68
98
  const needsWidthMeasurement = !slot.width || slot.width === 0;
69
- const needsHeightMeasurement = !slot.height || slot.height === 0;
99
+ const needsHeightMeasurement = !slot.height || slot.height === 0 || slot.height === undefined || slot.height === 'auto';
70
100
 
71
- if (needsWidthMeasurement || needsHeightMeasurement) {
101
+ // 너비 측정만 필요한 경우 (높이는 자동으로 유지)
102
+ if (needsWidthMeasurement || (needsHeightMeasurement && hasUserDefinedHeight)) {
72
103
  measureContainer = document.createElement('div');
73
104
  measureContainer.style.cssText = `
74
105
  position: absolute;
@@ -97,11 +128,7 @@ export class TextTransitionManager {
97
128
 
98
129
  // 모든 광고의 크기를 측정하여 최대 크기 찾기
99
130
  advertisements.forEach(ad => {
100
- const measureAdElement = AdRendererFactory.render(
101
- ad,
102
- slot,
103
- trackEventCallback
104
- );
131
+ const measureAdElement = this.createSimpleAdElement(slot, ad);
105
132
  measureContainer!.appendChild(measureAdElement);
106
133
 
107
134
  const rect = measureAdElement.getBoundingClientRect();
@@ -117,7 +144,8 @@ export class TextTransitionManager {
117
144
  sliderWrapper.style.width = `${maxWidth}px`;
118
145
  }
119
146
 
120
- if (needsHeightMeasurement && maxHeight > 0) {
147
+ // 사용자가 높이를 지정하지 않은 경우 auto 높이 유지 (측정된 높이 적용하지 않음)
148
+ if (needsHeightMeasurement && maxHeight > 0 && hasUserDefinedHeight) {
121
149
  sliderWrapper.style.height = `${maxHeight}px`;
122
150
  }
123
151
 
@@ -130,26 +158,37 @@ export class TextTransitionManager {
130
158
  advertisements.forEach((ad, index) => {
131
159
  const slideElement = document.createElement('div');
132
160
  slideElement.className = 'adstage-fade-slide';
161
+
162
+ // 자동 높이일 때는 첫 번째 슬라이드만 relative로 높이 확보
163
+ const isFirstSlide = index === 0;
164
+ const positionStyle = hasUserDefinedHeight ? 'absolute' : (isFirstSlide ? 'relative' : 'absolute');
165
+
133
166
  slideElement.style.cssText = `
134
- position: absolute;
135
- top: 0;
136
- left: 0;
167
+ position: ${positionStyle};
168
+ top: ${positionStyle === 'absolute' ? '0' : 'auto'};
169
+ left: ${positionStyle === 'absolute' ? '0' : 'auto'};
137
170
  width: 100%;
138
- height: 100%;
171
+ ${hasUserDefinedHeight ? 'height: 100%;' : 'height: auto;'}
139
172
  display: flex;
140
173
  align-items: center;
141
- justify-content: center;
174
+ justify-content: flex-start;
142
175
  opacity: ${index === 0 ? '1' : '0'};
143
176
  transform: translateY(${index === 0 ? '0' : '20px'});
144
177
  transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
145
178
  z-index: ${index === 0 ? '2' : '1'};
146
179
  `;
147
180
 
148
- // 광고 렌더링
149
- const adElement = AdRendererFactory.render(
181
+ // 광고 렌더링 - 공통 함수 사용으로 일관성 유지
182
+ const adElement = this.createSimpleAdElement(slot, ad);
183
+
184
+ // 클릭 이벤트 추가 (공통 컴포넌트 사용)
185
+ AdClickHandler.addClickEventForSlider(
186
+ adElement,
150
187
  ad,
151
188
  slot,
152
- trackEventCallback
189
+ trackEventCallback,
190
+ debug,
191
+ 'Text'
153
192
  );
154
193
 
155
194
  slideElement.appendChild(adElement);
@@ -173,6 +212,21 @@ export class TextTransitionManager {
173
212
  const previousSlide = slideElements[currentSlide];
174
213
  const nextSlide = slideElements[index];
175
214
 
215
+ // 자동 높이 모드일 때는 위치 변경
216
+ if (!hasUserDefinedHeight) {
217
+ // 이전 슬라이드를 absolute로 변경
218
+ if (previousSlide.style.position === 'relative') {
219
+ previousSlide.style.position = 'absolute';
220
+ previousSlide.style.top = '0';
221
+ previousSlide.style.left = '0';
222
+ }
223
+
224
+ // 다음 슬라이드를 relative로 변경하여 높이 확보
225
+ nextSlide.style.position = 'relative';
226
+ nextSlide.style.top = 'auto';
227
+ nextSlide.style.left = 'auto';
228
+ }
229
+
176
230
  // 이전 슬라이드 페이드 아웃 (아래로)
177
231
  previousSlide.style.opacity = '0';
178
232
  previousSlide.style.transform = 'translateY(-20px)';
@@ -189,15 +243,26 @@ export class TextTransitionManager {
189
243
  slide.style.opacity = '0';
190
244
  slide.style.transform = 'translateY(20px)';
191
245
  slide.style.zIndex = '1';
246
+
247
+ // 자동 높이 모드일 때는 다른 슬라이드들을 absolute로
248
+ if (!hasUserDefinedHeight && slide.style.position === 'relative') {
249
+ slide.style.position = 'absolute';
250
+ slide.style.top = '0';
251
+ slide.style.left = '0';
252
+ }
192
253
  }
193
254
  });
194
255
 
195
256
  currentSlide = index;
196
257
 
197
- // 현재 슬라이드의 광고에 대해 노출 이벤트 추적
198
- if (currentSlide > 0) { // 첫 번째는 이미 loadSlot에서 추적됨
199
- trackEventCallback(advertisements[currentSlide]._id, slot.id, AdEventType.VIEWABLE);
200
- }
258
+ // 🎯 공통 슬라이더 이벤트 추적 적용 (모든 슬라이드 포함)
259
+ SliderEventTracker.trackSlideViewable(
260
+ advertisements[currentSlide],
261
+ slot,
262
+ currentSlide,
263
+ trackEventCallback,
264
+ debug // debug 모드 상속
265
+ );
201
266
  };
202
267
 
203
268
  // 자동 슬라이드