@adstage/web-sdk 2.1.0 → 2.1.2

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.
package/dist/index.cjs.js CHANGED
@@ -2217,6 +2217,10 @@ class AdsModule {
2217
2217
  const adElement = document.createElement('div');
2218
2218
  adElement.id = slotId;
2219
2219
  adElement.className = `adstage-slot adstage-${type.toLowerCase()}`;
2220
+ // 확실한 컨테이너 식별을 위한 데이터 속성 추가
2221
+ adElement.setAttribute('data-adstage-container', 'true');
2222
+ adElement.setAttribute('data-adstage-type', type);
2223
+ adElement.setAttribute('data-adstage-slot', slotId);
2220
2224
  adElement.style.width = typeof options.width === 'number' ? `${options.width}px` : (options.width || '100%');
2221
2225
  adElement.style.height = typeof options.height === 'number' ? `${options.height}px` : (options.height || '250px');
2222
2226
  adElement.style.border = '1px dashed #ccc';
@@ -2314,22 +2318,110 @@ class AdsModule {
2314
2318
  }
2315
2319
  }
2316
2320
  /**
2317
- * Fallback 광고 렌더링 - DOM에서 완전 제거
2321
+ * Fallback 광고 렌더링 - AdStage 확실한 컨테이너 우선 탐지
2318
2322
  */
2319
2323
  renderFallback(slot) {
2320
2324
  const element = document.getElementById(slot.id);
2321
2325
  if (element) {
2322
- // 부모 컨테이너에서 광고 슬롯을 완전히 제거
2323
- const parentContainer = element.parentNode;
2324
- if (parentContainer) {
2325
- parentContainer.removeChild(element);
2326
+ // 1순위: AdStage가 생성한 확실한 컨테이너들 (데이터 속성 기반)
2327
+ const adstageContainers = [
2328
+ element.querySelector('[data-adstage-container="true"]'), // 내부 AdStage 컨테이너
2329
+ element.closest('[data-adstage-container="true"]'), // 상위 AdStage 컨테이너
2330
+ element, // 자기 자신이 AdStage 컨테이너인 경우
2331
+ ].filter(el => el && el.hasAttribute('data-adstage-container'));
2332
+ // 2순위: AdStage 클래스 기반 컨테이너들
2333
+ const classBasedContainers = [
2334
+ element.closest('.adstage-slot'),
2335
+ element.closest('.adstage-banner'),
2336
+ element.closest('.adstage-text'),
2337
+ element.closest('.adstage-video'),
2338
+ element.closest('.adstage-native'),
2339
+ element.closest('.adstage-interstitial'),
2340
+ ].filter(Boolean);
2341
+ // 3순위: 일반적인 광고 컨테이너 패턴들 (fallback)
2342
+ const generalContainers = [
2343
+ element.closest('[class*="ad"]'),
2344
+ element.closest('[class*="banner"]'),
2345
+ element.closest('[class*="container"]'),
2346
+ element.closest('div[style*="height"]'),
2347
+ element.closest('div[style*="min-height"]'),
2348
+ element.parentElement
2349
+ ].filter(Boolean);
2350
+ // 우선순위에 따라 컨테이너 선택
2351
+ const possibleContainers = [
2352
+ ...adstageContainers,
2353
+ ...classBasedContainers,
2354
+ ...generalContainers
2355
+ ];
2356
+ // 가장 적절한 컨테이너 선택
2357
+ const targetContainer = possibleContainers[0];
2358
+ if (targetContainer) {
2359
+ // 컨테이너 타입 로깅
2360
+ let containerType = 'unknown';
2361
+ if (targetContainer.hasAttribute('data-adstage-container')) {
2362
+ containerType = 'adstage-official';
2363
+ }
2364
+ else if (targetContainer.classList.contains('adstage-slot')) {
2365
+ containerType = 'adstage-class';
2366
+ }
2367
+ else {
2368
+ containerType = 'generic';
2369
+ }
2370
+ targetContainer.style.cssText += `
2371
+ height: 0px !important;
2372
+ min-height: 0px !important;
2373
+ padding: 0px !important;
2374
+ margin: 0px !important;
2375
+ border: none !important;
2376
+ overflow: hidden !important;
2377
+ display: block !important;
2378
+ `;
2379
+ // 내부 모든 요소 제거
2380
+ targetContainer.innerHTML = '';
2381
+ // 빈 상태임을 표시하는 속성 추가
2382
+ targetContainer.setAttribute('data-adstage-empty', 'true');
2326
2383
  if (this._config?.debug) {
2327
- console.warn(`⚠️ Ad slot completely removed from DOM: ${slot.id}`);
2384
+ console.warn(`⚠️ Ad container collapsed (${containerType}): ${slot.id}`, targetContainer);
2328
2385
  }
2329
2386
  }
2387
+ else {
2388
+ // 컨테이너를 찾지 못한 경우 새로운 빈 컨테이너 생성
2389
+ this.createEmptyContainer(slot);
2390
+ }
2391
+ }
2392
+ // 슬롯 상태 업데이트 (제거하지 않고 빈 상태로 마킹)
2393
+ slot.advertisement = undefined;
2394
+ slot.isEmpty = true;
2395
+ }
2396
+ /**
2397
+ * 빈 컨테이너 생성 (컨테이너를 찾지 못한 경우)
2398
+ */
2399
+ createEmptyContainer(slot) {
2400
+ const originalContainer = document.getElementById(slot.containerId);
2401
+ if (originalContainer) {
2402
+ // 기존 내용 제거
2403
+ originalContainer.innerHTML = '';
2404
+ // 빈 AdStage 컨테이너 생성
2405
+ const emptyElement = document.createElement('div');
2406
+ emptyElement.id = slot.id;
2407
+ emptyElement.className = 'adstage-slot adstage-empty';
2408
+ emptyElement.setAttribute('data-adstage-container', 'true');
2409
+ emptyElement.setAttribute('data-adstage-empty', 'true');
2410
+ emptyElement.setAttribute('data-adstage-slot', slot.id);
2411
+ emptyElement.style.cssText = `
2412
+ height: 0px !important;
2413
+ min-height: 0px !important;
2414
+ padding: 0px !important;
2415
+ margin: 0px !important;
2416
+ border: none !important;
2417
+ overflow: hidden !important;
2418
+ display: block !important;
2419
+ `;
2420
+ originalContainer.appendChild(emptyElement);
2421
+ if (this._config?.debug) {
2422
+ console.warn(`⚠️ Created empty AdStage container: ${slot.id}`);
2423
+ }
2330
2424
  }
2331
- // 슬롯 맵에서도 제거
2332
- this.slots.delete(slot.id);
2333
2425
  }
2334
2426
  /**
2335
2427
  * 광고 데이터 가져오기
package/dist/index.d.ts CHANGED
@@ -295,9 +295,13 @@ declare class AdsModule implements BaseModule {
295
295
  */
296
296
  private handleViewableEvent;
297
297
  /**
298
- * Fallback 광고 렌더링 - DOM에서 완전 제거
298
+ * Fallback 광고 렌더링 - AdStage 확실한 컨테이너 우선 탐지
299
299
  */
300
300
  private renderFallback;
301
+ /**
302
+ * 빈 컨테이너 생성 (컨테이너를 찾지 못한 경우)
303
+ */
304
+ private createEmptyContainer;
301
305
  /**
302
306
  * 광고 데이터 가져오기
303
307
  */
package/dist/index.esm.js CHANGED
@@ -2215,6 +2215,10 @@ class AdsModule {
2215
2215
  const adElement = document.createElement('div');
2216
2216
  adElement.id = slotId;
2217
2217
  adElement.className = `adstage-slot adstage-${type.toLowerCase()}`;
2218
+ // 확실한 컨테이너 식별을 위한 데이터 속성 추가
2219
+ adElement.setAttribute('data-adstage-container', 'true');
2220
+ adElement.setAttribute('data-adstage-type', type);
2221
+ adElement.setAttribute('data-adstage-slot', slotId);
2218
2222
  adElement.style.width = typeof options.width === 'number' ? `${options.width}px` : (options.width || '100%');
2219
2223
  adElement.style.height = typeof options.height === 'number' ? `${options.height}px` : (options.height || '250px');
2220
2224
  adElement.style.border = '1px dashed #ccc';
@@ -2312,22 +2316,110 @@ class AdsModule {
2312
2316
  }
2313
2317
  }
2314
2318
  /**
2315
- * Fallback 광고 렌더링 - DOM에서 완전 제거
2319
+ * Fallback 광고 렌더링 - AdStage 확실한 컨테이너 우선 탐지
2316
2320
  */
2317
2321
  renderFallback(slot) {
2318
2322
  const element = document.getElementById(slot.id);
2319
2323
  if (element) {
2320
- // 부모 컨테이너에서 광고 슬롯을 완전히 제거
2321
- const parentContainer = element.parentNode;
2322
- if (parentContainer) {
2323
- parentContainer.removeChild(element);
2324
+ // 1순위: AdStage가 생성한 확실한 컨테이너들 (데이터 속성 기반)
2325
+ const adstageContainers = [
2326
+ element.querySelector('[data-adstage-container="true"]'), // 내부 AdStage 컨테이너
2327
+ element.closest('[data-adstage-container="true"]'), // 상위 AdStage 컨테이너
2328
+ element, // 자기 자신이 AdStage 컨테이너인 경우
2329
+ ].filter(el => el && el.hasAttribute('data-adstage-container'));
2330
+ // 2순위: AdStage 클래스 기반 컨테이너들
2331
+ const classBasedContainers = [
2332
+ element.closest('.adstage-slot'),
2333
+ element.closest('.adstage-banner'),
2334
+ element.closest('.adstage-text'),
2335
+ element.closest('.adstage-video'),
2336
+ element.closest('.adstage-native'),
2337
+ element.closest('.adstage-interstitial'),
2338
+ ].filter(Boolean);
2339
+ // 3순위: 일반적인 광고 컨테이너 패턴들 (fallback)
2340
+ const generalContainers = [
2341
+ element.closest('[class*="ad"]'),
2342
+ element.closest('[class*="banner"]'),
2343
+ element.closest('[class*="container"]'),
2344
+ element.closest('div[style*="height"]'),
2345
+ element.closest('div[style*="min-height"]'),
2346
+ element.parentElement
2347
+ ].filter(Boolean);
2348
+ // 우선순위에 따라 컨테이너 선택
2349
+ const possibleContainers = [
2350
+ ...adstageContainers,
2351
+ ...classBasedContainers,
2352
+ ...generalContainers
2353
+ ];
2354
+ // 가장 적절한 컨테이너 선택
2355
+ const targetContainer = possibleContainers[0];
2356
+ if (targetContainer) {
2357
+ // 컨테이너 타입 로깅
2358
+ let containerType = 'unknown';
2359
+ if (targetContainer.hasAttribute('data-adstage-container')) {
2360
+ containerType = 'adstage-official';
2361
+ }
2362
+ else if (targetContainer.classList.contains('adstage-slot')) {
2363
+ containerType = 'adstage-class';
2364
+ }
2365
+ else {
2366
+ containerType = 'generic';
2367
+ }
2368
+ targetContainer.style.cssText += `
2369
+ height: 0px !important;
2370
+ min-height: 0px !important;
2371
+ padding: 0px !important;
2372
+ margin: 0px !important;
2373
+ border: none !important;
2374
+ overflow: hidden !important;
2375
+ display: block !important;
2376
+ `;
2377
+ // 내부 모든 요소 제거
2378
+ targetContainer.innerHTML = '';
2379
+ // 빈 상태임을 표시하는 속성 추가
2380
+ targetContainer.setAttribute('data-adstage-empty', 'true');
2324
2381
  if (this._config?.debug) {
2325
- console.warn(`⚠️ Ad slot completely removed from DOM: ${slot.id}`);
2382
+ console.warn(`⚠️ Ad container collapsed (${containerType}): ${slot.id}`, targetContainer);
2326
2383
  }
2327
2384
  }
2385
+ else {
2386
+ // 컨테이너를 찾지 못한 경우 새로운 빈 컨테이너 생성
2387
+ this.createEmptyContainer(slot);
2388
+ }
2389
+ }
2390
+ // 슬롯 상태 업데이트 (제거하지 않고 빈 상태로 마킹)
2391
+ slot.advertisement = undefined;
2392
+ slot.isEmpty = true;
2393
+ }
2394
+ /**
2395
+ * 빈 컨테이너 생성 (컨테이너를 찾지 못한 경우)
2396
+ */
2397
+ createEmptyContainer(slot) {
2398
+ const originalContainer = document.getElementById(slot.containerId);
2399
+ if (originalContainer) {
2400
+ // 기존 내용 제거
2401
+ originalContainer.innerHTML = '';
2402
+ // 빈 AdStage 컨테이너 생성
2403
+ const emptyElement = document.createElement('div');
2404
+ emptyElement.id = slot.id;
2405
+ emptyElement.className = 'adstage-slot adstage-empty';
2406
+ emptyElement.setAttribute('data-adstage-container', 'true');
2407
+ emptyElement.setAttribute('data-adstage-empty', 'true');
2408
+ emptyElement.setAttribute('data-adstage-slot', slot.id);
2409
+ emptyElement.style.cssText = `
2410
+ height: 0px !important;
2411
+ min-height: 0px !important;
2412
+ padding: 0px !important;
2413
+ margin: 0px !important;
2414
+ border: none !important;
2415
+ overflow: hidden !important;
2416
+ display: block !important;
2417
+ `;
2418
+ originalContainer.appendChild(emptyElement);
2419
+ if (this._config?.debug) {
2420
+ console.warn(`⚠️ Created empty AdStage container: ${slot.id}`);
2421
+ }
2328
2422
  }
2329
- // 슬롯 맵에서도 제거
2330
- this.slots.delete(slot.id);
2331
2423
  }
2332
2424
  /**
2333
2425
  * 광고 데이터 가져오기
@@ -2215,6 +2215,10 @@ class AdsModule {
2215
2215
  const adElement = document.createElement('div');
2216
2216
  adElement.id = slotId;
2217
2217
  adElement.className = `adstage-slot adstage-${type.toLowerCase()}`;
2218
+ // 확실한 컨테이너 식별을 위한 데이터 속성 추가
2219
+ adElement.setAttribute('data-adstage-container', 'true');
2220
+ adElement.setAttribute('data-adstage-type', type);
2221
+ adElement.setAttribute('data-adstage-slot', slotId);
2218
2222
  adElement.style.width = typeof options.width === 'number' ? `${options.width}px` : (options.width || '100%');
2219
2223
  adElement.style.height = typeof options.height === 'number' ? `${options.height}px` : (options.height || '250px');
2220
2224
  adElement.style.border = '1px dashed #ccc';
@@ -2312,22 +2316,110 @@ class AdsModule {
2312
2316
  }
2313
2317
  }
2314
2318
  /**
2315
- * Fallback 광고 렌더링 - DOM에서 완전 제거
2319
+ * Fallback 광고 렌더링 - AdStage 확실한 컨테이너 우선 탐지
2316
2320
  */
2317
2321
  renderFallback(slot) {
2318
2322
  const element = document.getElementById(slot.id);
2319
2323
  if (element) {
2320
- // 부모 컨테이너에서 광고 슬롯을 완전히 제거
2321
- const parentContainer = element.parentNode;
2322
- if (parentContainer) {
2323
- parentContainer.removeChild(element);
2324
+ // 1순위: AdStage가 생성한 확실한 컨테이너들 (데이터 속성 기반)
2325
+ const adstageContainers = [
2326
+ element.querySelector('[data-adstage-container="true"]'), // 내부 AdStage 컨테이너
2327
+ element.closest('[data-adstage-container="true"]'), // 상위 AdStage 컨테이너
2328
+ element, // 자기 자신이 AdStage 컨테이너인 경우
2329
+ ].filter(el => el && el.hasAttribute('data-adstage-container'));
2330
+ // 2순위: AdStage 클래스 기반 컨테이너들
2331
+ const classBasedContainers = [
2332
+ element.closest('.adstage-slot'),
2333
+ element.closest('.adstage-banner'),
2334
+ element.closest('.adstage-text'),
2335
+ element.closest('.adstage-video'),
2336
+ element.closest('.adstage-native'),
2337
+ element.closest('.adstage-interstitial'),
2338
+ ].filter(Boolean);
2339
+ // 3순위: 일반적인 광고 컨테이너 패턴들 (fallback)
2340
+ const generalContainers = [
2341
+ element.closest('[class*="ad"]'),
2342
+ element.closest('[class*="banner"]'),
2343
+ element.closest('[class*="container"]'),
2344
+ element.closest('div[style*="height"]'),
2345
+ element.closest('div[style*="min-height"]'),
2346
+ element.parentElement
2347
+ ].filter(Boolean);
2348
+ // 우선순위에 따라 컨테이너 선택
2349
+ const possibleContainers = [
2350
+ ...adstageContainers,
2351
+ ...classBasedContainers,
2352
+ ...generalContainers
2353
+ ];
2354
+ // 가장 적절한 컨테이너 선택
2355
+ const targetContainer = possibleContainers[0];
2356
+ if (targetContainer) {
2357
+ // 컨테이너 타입 로깅
2358
+ let containerType = 'unknown';
2359
+ if (targetContainer.hasAttribute('data-adstage-container')) {
2360
+ containerType = 'adstage-official';
2361
+ }
2362
+ else if (targetContainer.classList.contains('adstage-slot')) {
2363
+ containerType = 'adstage-class';
2364
+ }
2365
+ else {
2366
+ containerType = 'generic';
2367
+ }
2368
+ targetContainer.style.cssText += `
2369
+ height: 0px !important;
2370
+ min-height: 0px !important;
2371
+ padding: 0px !important;
2372
+ margin: 0px !important;
2373
+ border: none !important;
2374
+ overflow: hidden !important;
2375
+ display: block !important;
2376
+ `;
2377
+ // 내부 모든 요소 제거
2378
+ targetContainer.innerHTML = '';
2379
+ // 빈 상태임을 표시하는 속성 추가
2380
+ targetContainer.setAttribute('data-adstage-empty', 'true');
2324
2381
  if (this._config?.debug) {
2325
- console.warn(`⚠️ Ad slot completely removed from DOM: ${slot.id}`);
2382
+ console.warn(`⚠️ Ad container collapsed (${containerType}): ${slot.id}`, targetContainer);
2326
2383
  }
2327
2384
  }
2385
+ else {
2386
+ // 컨테이너를 찾지 못한 경우 새로운 빈 컨테이너 생성
2387
+ this.createEmptyContainer(slot);
2388
+ }
2389
+ }
2390
+ // 슬롯 상태 업데이트 (제거하지 않고 빈 상태로 마킹)
2391
+ slot.advertisement = undefined;
2392
+ slot.isEmpty = true;
2393
+ }
2394
+ /**
2395
+ * 빈 컨테이너 생성 (컨테이너를 찾지 못한 경우)
2396
+ */
2397
+ createEmptyContainer(slot) {
2398
+ const originalContainer = document.getElementById(slot.containerId);
2399
+ if (originalContainer) {
2400
+ // 기존 내용 제거
2401
+ originalContainer.innerHTML = '';
2402
+ // 빈 AdStage 컨테이너 생성
2403
+ const emptyElement = document.createElement('div');
2404
+ emptyElement.id = slot.id;
2405
+ emptyElement.className = 'adstage-slot adstage-empty';
2406
+ emptyElement.setAttribute('data-adstage-container', 'true');
2407
+ emptyElement.setAttribute('data-adstage-empty', 'true');
2408
+ emptyElement.setAttribute('data-adstage-slot', slot.id);
2409
+ emptyElement.style.cssText = `
2410
+ height: 0px !important;
2411
+ min-height: 0px !important;
2412
+ padding: 0px !important;
2413
+ margin: 0px !important;
2414
+ border: none !important;
2415
+ overflow: hidden !important;
2416
+ display: block !important;
2417
+ `;
2418
+ originalContainer.appendChild(emptyElement);
2419
+ if (this._config?.debug) {
2420
+ console.warn(`⚠️ Created empty AdStage container: ${slot.id}`);
2421
+ }
2328
2422
  }
2329
- // 슬롯 맵에서도 제거
2330
- this.slots.delete(slot.id);
2331
2423
  }
2332
2424
  /**
2333
2425
  * 광고 데이터 가져오기
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adstage/web-sdk",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "AdStage Web SDK - Production-ready marketing platform SDK with namespace architecture for ads, events, and config management",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs.js",
@@ -279,6 +279,11 @@ export class AdsModule implements BaseModule {
279
279
  const adElement = document.createElement('div');
280
280
  adElement.id = slotId;
281
281
  adElement.className = `adstage-slot adstage-${type.toLowerCase()}`;
282
+ // 확실한 컨테이너 식별을 위한 데이터 속성 추가
283
+ adElement.setAttribute('data-adstage-container', 'true');
284
+ adElement.setAttribute('data-adstage-type', type);
285
+ adElement.setAttribute('data-adstage-slot', slotId);
286
+
282
287
  adElement.style.width = typeof options.width === 'number' ? `${options.width}px` : (options.width || '100%');
283
288
  adElement.style.height = typeof options.height === 'number' ? `${options.height}px` : (options.height || '250px');
284
289
  adElement.style.border = '1px dashed #ccc';
@@ -401,24 +406,122 @@ export class AdsModule implements BaseModule {
401
406
  }
402
407
 
403
408
  /**
404
- * Fallback 광고 렌더링 - DOM에서 완전 제거
409
+ * Fallback 광고 렌더링 - AdStage 확실한 컨테이너 우선 탐지
405
410
  */
406
411
  private renderFallback(slot: AdSlot): void {
407
412
  const element = document.getElementById(slot.id);
408
413
  if (element) {
409
- // 부모 컨테이너에서 광고 슬롯을 완전히 제거
410
- const parentContainer = element.parentNode;
411
- if (parentContainer) {
412
- parentContainer.removeChild(element);
414
+ // 1순위: AdStage가 생성한 확실한 컨테이너들 (데이터 속성 기반)
415
+ const adstageContainers = [
416
+ element.querySelector('[data-adstage-container="true"]'), // 내부 AdStage 컨테이너
417
+ element.closest('[data-adstage-container="true"]'), // 상위 AdStage 컨테이너
418
+ element, // 자기 자신이 AdStage 컨테이너인 경우
419
+ ].filter(el => el && el.hasAttribute('data-adstage-container'));
420
+
421
+ // 2순위: AdStage 클래스 기반 컨테이너들
422
+ const classBasedContainers = [
423
+ element.closest('.adstage-slot'),
424
+ element.closest('.adstage-banner'),
425
+ element.closest('.adstage-text'),
426
+ element.closest('.adstage-video'),
427
+ element.closest('.adstage-native'),
428
+ element.closest('.adstage-interstitial'),
429
+ ].filter(Boolean);
430
+
431
+ // 3순위: 일반적인 광고 컨테이너 패턴들 (fallback)
432
+ const generalContainers = [
433
+ element.closest('[class*="ad"]'),
434
+ element.closest('[class*="banner"]'),
435
+ element.closest('[class*="container"]'),
436
+ element.closest('div[style*="height"]'),
437
+ element.closest('div[style*="min-height"]'),
438
+ element.parentElement
439
+ ].filter(Boolean);
440
+
441
+ // 우선순위에 따라 컨테이너 선택
442
+ const possibleContainers = [
443
+ ...adstageContainers,
444
+ ...classBasedContainers,
445
+ ...generalContainers
446
+ ];
447
+
448
+ // 가장 적절한 컨테이너 선택
449
+ const targetContainer = possibleContainers[0] as HTMLElement;
450
+
451
+ if (targetContainer) {
452
+ // 컨테이너 타입 로깅
453
+ let containerType = 'unknown';
454
+ if (targetContainer.hasAttribute('data-adstage-container')) {
455
+ containerType = 'adstage-official';
456
+ } else if (targetContainer.classList.contains('adstage-slot')) {
457
+ containerType = 'adstage-class';
458
+ } else {
459
+ containerType = 'generic';
460
+ }
461
+
462
+ targetContainer.style.cssText += `
463
+ height: 0px !important;
464
+ min-height: 0px !important;
465
+ padding: 0px !important;
466
+ margin: 0px !important;
467
+ border: none !important;
468
+ overflow: hidden !important;
469
+ display: block !important;
470
+ `;
471
+
472
+ // 내부 모든 요소 제거
473
+ targetContainer.innerHTML = '';
474
+
475
+ // 빈 상태임을 표시하는 속성 추가
476
+ targetContainer.setAttribute('data-adstage-empty', 'true');
413
477
 
414
478
  if (this._config?.debug) {
415
- console.warn(`⚠️ Ad slot completely removed from DOM: ${slot.id}`);
479
+ console.warn(`⚠️ Ad container collapsed (${containerType}): ${slot.id}`, targetContainer);
416
480
  }
481
+ } else {
482
+ // 컨테이너를 찾지 못한 경우 새로운 빈 컨테이너 생성
483
+ this.createEmptyContainer(slot);
417
484
  }
418
485
  }
419
486
 
420
- // 슬롯 맵에서도 제거
421
- this.slots.delete(slot.id);
487
+ // 슬롯 상태 업데이트 (제거하지 않고 빈 상태로 마킹)
488
+ slot.advertisement = undefined;
489
+ (slot as any).isEmpty = true;
490
+ }
491
+
492
+ /**
493
+ * 빈 컨테이너 생성 (컨테이너를 찾지 못한 경우)
494
+ */
495
+ private createEmptyContainer(slot: AdSlot): void {
496
+ const originalContainer = document.getElementById(slot.containerId);
497
+ if (originalContainer) {
498
+ // 기존 내용 제거
499
+ originalContainer.innerHTML = '';
500
+
501
+ // 빈 AdStage 컨테이너 생성
502
+ const emptyElement = document.createElement('div');
503
+ emptyElement.id = slot.id;
504
+ emptyElement.className = 'adstage-slot adstage-empty';
505
+ emptyElement.setAttribute('data-adstage-container', 'true');
506
+ emptyElement.setAttribute('data-adstage-empty', 'true');
507
+ emptyElement.setAttribute('data-adstage-slot', slot.id);
508
+
509
+ emptyElement.style.cssText = `
510
+ height: 0px !important;
511
+ min-height: 0px !important;
512
+ padding: 0px !important;
513
+ margin: 0px !important;
514
+ border: none !important;
515
+ overflow: hidden !important;
516
+ display: block !important;
517
+ `;
518
+
519
+ originalContainer.appendChild(emptyElement);
520
+
521
+ if (this._config?.debug) {
522
+ console.warn(`⚠️ Created empty AdStage container: ${slot.id}`);
523
+ }
524
+ }
422
525
  }
423
526
 
424
527
  /**