@zerocost/sdk 0.6.0 → 0.7.0

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 CHANGED
@@ -146,6 +146,7 @@ var POSITION_STYLES = {
146
146
  "sidebar-left": "position:fixed;top:50%;left:24px;transform:translateY(-50%);z-index:9999;",
147
147
  "sidebar-right": "position:fixed;top:50%;right:24px;transform:translateY(-50%);z-index:9999;"
148
148
  };
149
+ var FORMAT_PRIORITY = ["video-widget", "sponsored-card", "sidebar-display", "tooltip-ad", "inline-text"];
149
150
  var WidgetModule = class {
150
151
  constructor(client) {
151
152
  this.client = client;
@@ -154,10 +155,14 @@ var WidgetModule = class {
154
155
  async autoInjectWithConfig(display) {
155
156
  try {
156
157
  const configs = this.normalizeConfigs(display);
157
- const enabledCount = Object.values(configs).filter((c) => c.enabled).length;
158
- this.client.log(`Auto-inject: ${enabledCount} ad format(s) enabled. No custom component needed \u2014 ads render automatically.`);
159
- await this.mountFormats(configs);
160
- this.client.log("\u2713 Ad slots injected successfully. Ads will appear once inventory is available.");
158
+ const chosen = this.pickOneFormat(configs);
159
+ if (!chosen) {
160
+ this.client.log("No ad formats enabled. Skipping injection.");
161
+ return;
162
+ }
163
+ this.client.log(`Auto-inject: rendering "${chosen.format}" only.`);
164
+ await this.mountSingleFormat(chosen.format, chosen.config);
165
+ this.client.log("\u2713 Ad slot injected.");
161
166
  } catch (err) {
162
167
  this.client.log(`Widget autoInject error: ${err}`);
163
168
  }
@@ -166,15 +171,18 @@ var WidgetModule = class {
166
171
  try {
167
172
  this.client.log("Fetching ad placements from server...");
168
173
  const { display } = await this.client.request("/get-placements");
169
- const configs = this.normalizeConfigs(display);
170
- const enabledCount = Object.values(configs).filter((c) => c.enabled).length;
171
- this.client.log(`Auto-inject: ${enabledCount} ad format(s) enabled. No custom component needed \u2014 ads render automatically.`);
172
- await this.mountFormats(configs);
173
- this.client.log("\u2713 Ad slots injected successfully. Ads will appear once inventory is available.");
174
+ await this.autoInjectWithConfig(display);
174
175
  } catch (err) {
175
176
  this.client.log(`Widget autoInject error: ${err}`);
176
177
  }
177
178
  }
179
+ pickOneFormat(configs) {
180
+ for (const fmt of FORMAT_PRIORITY) {
181
+ const cfg = configs[fmt];
182
+ if (cfg?.enabled) return { format: fmt, config: cfg };
183
+ }
184
+ return null;
185
+ }
178
186
  normalizeConfigs(display) {
179
187
  if (display && "video-widget" in display) {
180
188
  return display;
@@ -182,30 +190,25 @@ var WidgetModule = class {
182
190
  const pos = display?.position || "bottom-right";
183
191
  const theme = display?.theme || "dark";
184
192
  return {
185
- "video-widget": { position: pos, theme, autoplay: false, enabled: true },
186
- "tooltip-ad": { position: pos, theme, autoplay: false, enabled: true },
187
- "sponsored-card": { position: pos, theme, autoplay: false, enabled: true },
188
- "inline-text": { position: "after-paragraph-1", theme, autoplay: false, enabled: true }
193
+ "video-widget": { position: pos, theme, autoplay: true, enabled: true },
194
+ "tooltip-ad": { position: pos, theme, autoplay: false, enabled: false },
195
+ "sponsored-card": { position: pos, theme, autoplay: false, enabled: false },
196
+ "inline-text": { position: "after-paragraph-1", theme, autoplay: false, enabled: false }
189
197
  };
190
198
  }
191
- async mountFormats(configs) {
192
- for (const [format, cfg] of Object.entries(configs)) {
193
- if (!cfg.enabled) continue;
194
- const elId = `zerocost-${format}`;
195
- if (document.getElementById(elId)) continue;
196
- const el = document.createElement("div");
197
- el.id = elId;
198
- el.setAttribute("data-zerocost", "");
199
- el.setAttribute("data-format", format);
200
- if (format === "inline-text") {
201
- continue;
202
- }
203
- const posStyle = POSITION_STYLES[cfg.position] || POSITION_STYLES["bottom-right"];
204
- const maxW = format === "video-widget" ? "max-width:200px;" : format === "sponsored-card" || format === "sidebar-display" ? "max-width:176px;" : "max-width:320px;";
205
- el.setAttribute("style", posStyle + maxW);
206
- document.body.appendChild(el);
207
- await this.mount(elId, { format, refreshInterval: 30, theme: cfg.theme, autoplay: cfg.autoplay });
208
- }
199
+ async mountSingleFormat(format, cfg) {
200
+ const elId = `zerocost-${format}`;
201
+ if (document.getElementById(elId)) return;
202
+ if (format === "inline-text") return;
203
+ const el = document.createElement("div");
204
+ el.id = elId;
205
+ el.setAttribute("data-zerocost", "");
206
+ el.setAttribute("data-format", format);
207
+ const posStyle = POSITION_STYLES[cfg.position] || POSITION_STYLES["bottom-right"];
208
+ const maxW = format === "video-widget" ? "max-width:200px;" : format === "sponsored-card" || format === "sidebar-display" ? "max-width:176px;" : "max-width:320px;";
209
+ el.setAttribute("style", posStyle + maxW);
210
+ document.body.appendChild(el);
211
+ await this.mount(elId, { format, refreshInterval: 30, theme: cfg.theme, autoplay: cfg.autoplay ?? true });
209
212
  }
210
213
  async mount(targetElementId, options = {}) {
211
214
  const el = document.getElementById(targetElementId);
@@ -238,6 +241,12 @@ var WidgetModule = class {
238
241
  }
239
242
  el.innerHTML = data.html;
240
243
  el.setAttribute("data-zerocost-ad-id", ad.id);
244
+ const video = el.querySelector("video");
245
+ if (video) {
246
+ video.muted = true;
247
+ video.play().catch(() => {
248
+ });
249
+ }
241
250
  const ctas = el.querySelectorAll("[data-zc-cta]");
242
251
  ctas.forEach((cta) => {
243
252
  cta.addEventListener("click", () => {
@@ -308,7 +317,7 @@ var WidgetModule = class {
308
317
  return `
309
318
  <div style="width:200px;aspect-ratio:9/16;border-radius:12px;border:1px solid ${border};overflow:hidden;position:relative;font-family:system-ui,-apple-system,sans-serif;box-shadow:0 8px 32px rgba(0,0,0,${isDark ? "0.5" : "0.15"});">
310
319
  <button data-zc-close style="position:absolute;top:8px;right:8px;z-index:20;width:24px;height:24px;border-radius:50%;background:rgba(0,0,0,0.5);backdrop-filter:blur(4px);border:none;color:rgba(255,255,255,0.7);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;line-height:1;">\u2715</button>
311
- ${hasVideo ? `<video src="${this.esc(videoSrc)}" ${autoplay ? "autoplay" : ""} muted loop playsinline style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;"></video>` : `<img src="${this.esc(videoSrc)}" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;" />`}
320
+ ${hasVideo ? `<video src="${this.esc(videoSrc)}" autoplay muted loop playsinline preload="auto" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;"></video>` : `<img src="${this.esc(videoSrc)}" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;" />`}
312
321
  <div style="position:absolute;inset-inline:0;bottom:0;padding:12px;background:linear-gradient(to top,rgba(0,0,0,0.9),rgba(0,0,0,0.5),transparent);z-index:10;">
313
322
  <div style="font-size:8px;font-weight:700;text-transform:uppercase;letter-spacing:0.1em;color:rgba(255,255,255,0.5);margin-bottom:2px;">Sponsored by ${this.esc(sponsor)}</div>
314
323
  <div style="color:#fff;font-weight:600;font-size:12px;line-height:1.3;">${this.esc(ad.description || "")}</div>
@@ -327,7 +336,8 @@ var WidgetModule = class {
327
336
  const fgFaint = isDark ? "#888" : "#999";
328
337
  const border = isDark ? "#333" : "#e0e0e0";
329
338
  return `
330
- <div style="max-width:320px;border-radius:8px;padding:12px;box-shadow:0 8px 32px rgba(0,0,0,${isDark ? "0.4" : "0.12"});background:${bg};border:1px solid ${border};font-family:system-ui,-apple-system,sans-serif;">
339
+ <div style="max-width:320px;border-radius:8px;padding:12px;box-shadow:0 8px 32px rgba(0,0,0,${isDark ? "0.4" : "0.12"});background:${bg};border:1px solid ${border};font-family:system-ui,-apple-system,sans-serif;position:relative;">
340
+ <button data-zc-close style="position:absolute;top:6px;right:6px;z-index:20;width:20px;height:20px;border-radius:50%;background:${isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.08)"};border:none;color:${fgFaint};cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px;line-height:1;">\u2715</button>
331
341
  <div style="font-size:10px;font-family:monospace;margin-bottom:6px;display:flex;align-items:center;color:${fgFaint};">
332
342
  <span style="width:6px;height:6px;border-radius:50%;background:#22c55e;margin-right:6px;display:inline-block;"></span>
333
343
  Sponsored
@@ -348,13 +358,11 @@ var WidgetModule = class {
348
358
  const fg = isDark ? "#fff" : "#111";
349
359
  const fgFaint = isDark ? "#888" : "#999";
350
360
  const border = isDark ? "#333" : "#e0e0e0";
351
- const iconBg = isDark ? "#0a0a0a" : "#f5f5f5";
352
- const initial = (ad.title || "A").charAt(0).toUpperCase();
353
361
  return `
354
- <div style="width:176px;border-radius:12px;overflow:hidden;box-shadow:0 8px 32px rgba(0,0,0,${isDark ? "0.4" : "0.12"});background:${bg};border:1px solid ${border};font-family:system-ui,-apple-system,sans-serif;">
362
+ <div style="width:176px;border-radius:12px;overflow:hidden;box-shadow:0 8px 32px rgba(0,0,0,${isDark ? "0.4" : "0.12"});background:${bg};border:1px solid ${border};font-family:system-ui,-apple-system,sans-serif;position:relative;">
363
+ <button data-zc-close style="position:absolute;top:6px;right:6px;z-index:20;width:20px;height:20px;border-radius:50%;background:rgba(0,0,0,0.5);backdrop-filter:blur(4px);border:none;color:rgba(255,255,255,0.7);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px;line-height:1;">\u2715</button>
355
364
  ${ad.image_url ? `<img src="${this.esc(ad.image_url)}" style="width:100%;height:80px;object-fit:cover;display:block;" />` : `<div style="height:80px;background:linear-gradient(135deg,rgba(249,115,22,0.2),rgba(236,72,153,0.2));"></div>`}
356
365
  <div style="padding:12px;">
357
- <div style="width:24px;height:24px;border-radius:4px;background:${iconBg};border:1px solid ${border};display:flex;align-items:center;justify-content:center;font-weight:700;font-size:9px;color:${fg};margin-bottom:8px;">${initial}</div>
358
366
  <div style="font-weight:700;font-size:12px;color:${fg};margin-bottom:2px;letter-spacing:-0.01em;">${this.esc(ad.title)}</div>
359
367
  <div style="font-size:10px;color:${fgFaint};line-height:1.4;margin-bottom:12px;">${this.esc(ad.description || "")}</div>
360
368
  <a href="${this.esc(ad.landing_url)}" target="_blank" rel="noopener" data-zc-cta style="display:block;width:100%;padding:6px 0;border:1px solid ${border};border-radius:4px;font-size:10px;font-weight:500;color:${fg};text-align:center;text-decoration:none;cursor:pointer;">${this.esc(ad.cta_text || "Learn More")}</a>
@@ -379,7 +387,8 @@ var WidgetModule = class {
379
387
  const initial = "\u25B2";
380
388
  const sponsor = ad.title?.split("\u2014")[0]?.trim() || ad.title || "Sponsor";
381
389
  return `
382
- <div style="border-radius:8px;overflow:hidden;border:1px solid ${border};background:${bg};font-family:system-ui,-apple-system,sans-serif;max-width:100%;">
390
+ <div style="border-radius:8px;overflow:hidden;border:1px solid ${border};background:${bg};font-family:system-ui,-apple-system,sans-serif;max-width:100%;position:relative;">
391
+ <button data-zc-close style="position:absolute;top:6px;right:6px;z-index:20;width:20px;height:20px;border-radius:50%;background:${isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.08)"};border:none;color:${fgFaint};cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px;line-height:1;">\u2715</button>
383
392
  <div style="padding:8px 12px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid ${headerBorder};">
384
393
  <span style="font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:0.1em;color:${fgFaint};">Sponsored</span>
385
394
  <span style="font-size:9px;color:${fgFaint};">Ad \xB7 ${this.esc(sponsor)}</span>
@@ -768,6 +777,10 @@ var ZerocostSDK = class {
768
777
  this.core.log("Running in non-browser environment \u2014 skipping DOM injection.");
769
778
  return;
770
779
  }
780
+ const isIframe = window !== window.top;
781
+ if (isIframe) {
782
+ this.core.log("Running inside an iframe. Ads will still render if permissions allow.");
783
+ }
771
784
  this.core.log("Initializing... Ads will be auto-injected into the DOM. No custom component required.");
772
785
  try {
773
786
  const { display, dataCollection } = await this.core.request("/get-placements");
package/dist/index.js CHANGED
@@ -117,6 +117,7 @@ var POSITION_STYLES = {
117
117
  "sidebar-left": "position:fixed;top:50%;left:24px;transform:translateY(-50%);z-index:9999;",
118
118
  "sidebar-right": "position:fixed;top:50%;right:24px;transform:translateY(-50%);z-index:9999;"
119
119
  };
120
+ var FORMAT_PRIORITY = ["video-widget", "sponsored-card", "sidebar-display", "tooltip-ad", "inline-text"];
120
121
  var WidgetModule = class {
121
122
  constructor(client) {
122
123
  this.client = client;
@@ -125,10 +126,14 @@ var WidgetModule = class {
125
126
  async autoInjectWithConfig(display) {
126
127
  try {
127
128
  const configs = this.normalizeConfigs(display);
128
- const enabledCount = Object.values(configs).filter((c) => c.enabled).length;
129
- this.client.log(`Auto-inject: ${enabledCount} ad format(s) enabled. No custom component needed \u2014 ads render automatically.`);
130
- await this.mountFormats(configs);
131
- this.client.log("\u2713 Ad slots injected successfully. Ads will appear once inventory is available.");
129
+ const chosen = this.pickOneFormat(configs);
130
+ if (!chosen) {
131
+ this.client.log("No ad formats enabled. Skipping injection.");
132
+ return;
133
+ }
134
+ this.client.log(`Auto-inject: rendering "${chosen.format}" only.`);
135
+ await this.mountSingleFormat(chosen.format, chosen.config);
136
+ this.client.log("\u2713 Ad slot injected.");
132
137
  } catch (err) {
133
138
  this.client.log(`Widget autoInject error: ${err}`);
134
139
  }
@@ -137,15 +142,18 @@ var WidgetModule = class {
137
142
  try {
138
143
  this.client.log("Fetching ad placements from server...");
139
144
  const { display } = await this.client.request("/get-placements");
140
- const configs = this.normalizeConfigs(display);
141
- const enabledCount = Object.values(configs).filter((c) => c.enabled).length;
142
- this.client.log(`Auto-inject: ${enabledCount} ad format(s) enabled. No custom component needed \u2014 ads render automatically.`);
143
- await this.mountFormats(configs);
144
- this.client.log("\u2713 Ad slots injected successfully. Ads will appear once inventory is available.");
145
+ await this.autoInjectWithConfig(display);
145
146
  } catch (err) {
146
147
  this.client.log(`Widget autoInject error: ${err}`);
147
148
  }
148
149
  }
150
+ pickOneFormat(configs) {
151
+ for (const fmt of FORMAT_PRIORITY) {
152
+ const cfg = configs[fmt];
153
+ if (cfg?.enabled) return { format: fmt, config: cfg };
154
+ }
155
+ return null;
156
+ }
149
157
  normalizeConfigs(display) {
150
158
  if (display && "video-widget" in display) {
151
159
  return display;
@@ -153,30 +161,25 @@ var WidgetModule = class {
153
161
  const pos = display?.position || "bottom-right";
154
162
  const theme = display?.theme || "dark";
155
163
  return {
156
- "video-widget": { position: pos, theme, autoplay: false, enabled: true },
157
- "tooltip-ad": { position: pos, theme, autoplay: false, enabled: true },
158
- "sponsored-card": { position: pos, theme, autoplay: false, enabled: true },
159
- "inline-text": { position: "after-paragraph-1", theme, autoplay: false, enabled: true }
164
+ "video-widget": { position: pos, theme, autoplay: true, enabled: true },
165
+ "tooltip-ad": { position: pos, theme, autoplay: false, enabled: false },
166
+ "sponsored-card": { position: pos, theme, autoplay: false, enabled: false },
167
+ "inline-text": { position: "after-paragraph-1", theme, autoplay: false, enabled: false }
160
168
  };
161
169
  }
162
- async mountFormats(configs) {
163
- for (const [format, cfg] of Object.entries(configs)) {
164
- if (!cfg.enabled) continue;
165
- const elId = `zerocost-${format}`;
166
- if (document.getElementById(elId)) continue;
167
- const el = document.createElement("div");
168
- el.id = elId;
169
- el.setAttribute("data-zerocost", "");
170
- el.setAttribute("data-format", format);
171
- if (format === "inline-text") {
172
- continue;
173
- }
174
- const posStyle = POSITION_STYLES[cfg.position] || POSITION_STYLES["bottom-right"];
175
- const maxW = format === "video-widget" ? "max-width:200px;" : format === "sponsored-card" || format === "sidebar-display" ? "max-width:176px;" : "max-width:320px;";
176
- el.setAttribute("style", posStyle + maxW);
177
- document.body.appendChild(el);
178
- await this.mount(elId, { format, refreshInterval: 30, theme: cfg.theme, autoplay: cfg.autoplay });
179
- }
170
+ async mountSingleFormat(format, cfg) {
171
+ const elId = `zerocost-${format}`;
172
+ if (document.getElementById(elId)) return;
173
+ if (format === "inline-text") return;
174
+ const el = document.createElement("div");
175
+ el.id = elId;
176
+ el.setAttribute("data-zerocost", "");
177
+ el.setAttribute("data-format", format);
178
+ const posStyle = POSITION_STYLES[cfg.position] || POSITION_STYLES["bottom-right"];
179
+ const maxW = format === "video-widget" ? "max-width:200px;" : format === "sponsored-card" || format === "sidebar-display" ? "max-width:176px;" : "max-width:320px;";
180
+ el.setAttribute("style", posStyle + maxW);
181
+ document.body.appendChild(el);
182
+ await this.mount(elId, { format, refreshInterval: 30, theme: cfg.theme, autoplay: cfg.autoplay ?? true });
180
183
  }
181
184
  async mount(targetElementId, options = {}) {
182
185
  const el = document.getElementById(targetElementId);
@@ -209,6 +212,12 @@ var WidgetModule = class {
209
212
  }
210
213
  el.innerHTML = data.html;
211
214
  el.setAttribute("data-zerocost-ad-id", ad.id);
215
+ const video = el.querySelector("video");
216
+ if (video) {
217
+ video.muted = true;
218
+ video.play().catch(() => {
219
+ });
220
+ }
212
221
  const ctas = el.querySelectorAll("[data-zc-cta]");
213
222
  ctas.forEach((cta) => {
214
223
  cta.addEventListener("click", () => {
@@ -279,7 +288,7 @@ var WidgetModule = class {
279
288
  return `
280
289
  <div style="width:200px;aspect-ratio:9/16;border-radius:12px;border:1px solid ${border};overflow:hidden;position:relative;font-family:system-ui,-apple-system,sans-serif;box-shadow:0 8px 32px rgba(0,0,0,${isDark ? "0.5" : "0.15"});">
281
290
  <button data-zc-close style="position:absolute;top:8px;right:8px;z-index:20;width:24px;height:24px;border-radius:50%;background:rgba(0,0,0,0.5);backdrop-filter:blur(4px);border:none;color:rgba(255,255,255,0.7);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;line-height:1;">\u2715</button>
282
- ${hasVideo ? `<video src="${this.esc(videoSrc)}" ${autoplay ? "autoplay" : ""} muted loop playsinline style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;"></video>` : `<img src="${this.esc(videoSrc)}" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;" />`}
291
+ ${hasVideo ? `<video src="${this.esc(videoSrc)}" autoplay muted loop playsinline preload="auto" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;"></video>` : `<img src="${this.esc(videoSrc)}" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;" />`}
283
292
  <div style="position:absolute;inset-inline:0;bottom:0;padding:12px;background:linear-gradient(to top,rgba(0,0,0,0.9),rgba(0,0,0,0.5),transparent);z-index:10;">
284
293
  <div style="font-size:8px;font-weight:700;text-transform:uppercase;letter-spacing:0.1em;color:rgba(255,255,255,0.5);margin-bottom:2px;">Sponsored by ${this.esc(sponsor)}</div>
285
294
  <div style="color:#fff;font-weight:600;font-size:12px;line-height:1.3;">${this.esc(ad.description || "")}</div>
@@ -298,7 +307,8 @@ var WidgetModule = class {
298
307
  const fgFaint = isDark ? "#888" : "#999";
299
308
  const border = isDark ? "#333" : "#e0e0e0";
300
309
  return `
301
- <div style="max-width:320px;border-radius:8px;padding:12px;box-shadow:0 8px 32px rgba(0,0,0,${isDark ? "0.4" : "0.12"});background:${bg};border:1px solid ${border};font-family:system-ui,-apple-system,sans-serif;">
310
+ <div style="max-width:320px;border-radius:8px;padding:12px;box-shadow:0 8px 32px rgba(0,0,0,${isDark ? "0.4" : "0.12"});background:${bg};border:1px solid ${border};font-family:system-ui,-apple-system,sans-serif;position:relative;">
311
+ <button data-zc-close style="position:absolute;top:6px;right:6px;z-index:20;width:20px;height:20px;border-radius:50%;background:${isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.08)"};border:none;color:${fgFaint};cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px;line-height:1;">\u2715</button>
302
312
  <div style="font-size:10px;font-family:monospace;margin-bottom:6px;display:flex;align-items:center;color:${fgFaint};">
303
313
  <span style="width:6px;height:6px;border-radius:50%;background:#22c55e;margin-right:6px;display:inline-block;"></span>
304
314
  Sponsored
@@ -319,13 +329,11 @@ var WidgetModule = class {
319
329
  const fg = isDark ? "#fff" : "#111";
320
330
  const fgFaint = isDark ? "#888" : "#999";
321
331
  const border = isDark ? "#333" : "#e0e0e0";
322
- const iconBg = isDark ? "#0a0a0a" : "#f5f5f5";
323
- const initial = (ad.title || "A").charAt(0).toUpperCase();
324
332
  return `
325
- <div style="width:176px;border-radius:12px;overflow:hidden;box-shadow:0 8px 32px rgba(0,0,0,${isDark ? "0.4" : "0.12"});background:${bg};border:1px solid ${border};font-family:system-ui,-apple-system,sans-serif;">
333
+ <div style="width:176px;border-radius:12px;overflow:hidden;box-shadow:0 8px 32px rgba(0,0,0,${isDark ? "0.4" : "0.12"});background:${bg};border:1px solid ${border};font-family:system-ui,-apple-system,sans-serif;position:relative;">
334
+ <button data-zc-close style="position:absolute;top:6px;right:6px;z-index:20;width:20px;height:20px;border-radius:50%;background:rgba(0,0,0,0.5);backdrop-filter:blur(4px);border:none;color:rgba(255,255,255,0.7);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px;line-height:1;">\u2715</button>
326
335
  ${ad.image_url ? `<img src="${this.esc(ad.image_url)}" style="width:100%;height:80px;object-fit:cover;display:block;" />` : `<div style="height:80px;background:linear-gradient(135deg,rgba(249,115,22,0.2),rgba(236,72,153,0.2));"></div>`}
327
336
  <div style="padding:12px;">
328
- <div style="width:24px;height:24px;border-radius:4px;background:${iconBg};border:1px solid ${border};display:flex;align-items:center;justify-content:center;font-weight:700;font-size:9px;color:${fg};margin-bottom:8px;">${initial}</div>
329
337
  <div style="font-weight:700;font-size:12px;color:${fg};margin-bottom:2px;letter-spacing:-0.01em;">${this.esc(ad.title)}</div>
330
338
  <div style="font-size:10px;color:${fgFaint};line-height:1.4;margin-bottom:12px;">${this.esc(ad.description || "")}</div>
331
339
  <a href="${this.esc(ad.landing_url)}" target="_blank" rel="noopener" data-zc-cta style="display:block;width:100%;padding:6px 0;border:1px solid ${border};border-radius:4px;font-size:10px;font-weight:500;color:${fg};text-align:center;text-decoration:none;cursor:pointer;">${this.esc(ad.cta_text || "Learn More")}</a>
@@ -350,7 +358,8 @@ var WidgetModule = class {
350
358
  const initial = "\u25B2";
351
359
  const sponsor = ad.title?.split("\u2014")[0]?.trim() || ad.title || "Sponsor";
352
360
  return `
353
- <div style="border-radius:8px;overflow:hidden;border:1px solid ${border};background:${bg};font-family:system-ui,-apple-system,sans-serif;max-width:100%;">
361
+ <div style="border-radius:8px;overflow:hidden;border:1px solid ${border};background:${bg};font-family:system-ui,-apple-system,sans-serif;max-width:100%;position:relative;">
362
+ <button data-zc-close style="position:absolute;top:6px;right:6px;z-index:20;width:20px;height:20px;border-radius:50%;background:${isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.08)"};border:none;color:${fgFaint};cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px;line-height:1;">\u2715</button>
354
363
  <div style="padding:8px 12px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid ${headerBorder};">
355
364
  <span style="font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:0.1em;color:${fgFaint};">Sponsored</span>
356
365
  <span style="font-size:9px;color:${fgFaint};">Ad \xB7 ${this.esc(sponsor)}</span>
@@ -739,6 +748,10 @@ var ZerocostSDK = class {
739
748
  this.core.log("Running in non-browser environment \u2014 skipping DOM injection.");
740
749
  return;
741
750
  }
751
+ const isIframe = window !== window.top;
752
+ if (isIframe) {
753
+ this.core.log("Running inside an iframe. Ads will still render if permissions allow.");
754
+ }
742
755
  this.core.log("Initializing... Ads will be auto-injected into the DOM. No custom component required.");
743
756
  try {
744
757
  const { display, dataCollection } = await this.core.request("/get-placements");
@@ -15,8 +15,9 @@ export declare class WidgetModule {
15
15
  theme: string;
16
16
  }): Promise<void>;
17
17
  autoInject(): Promise<void>;
18
+ private pickOneFormat;
18
19
  private normalizeConfigs;
19
- private mountFormats;
20
+ private mountSingleFormat;
20
21
  mount(targetElementId: string, options?: {
21
22
  format?: string;
22
23
  refreshInterval?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerocost/sdk",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",