@zerocost/sdk 0.6.0 → 0.8.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 +130 -178
- package/dist/index.js +130 -178
- package/dist/modules/widget.d.ts +10 -25
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -146,18 +146,24 @@ 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", "tooltip-ad", "sponsored-card", "sidebar-display", "inline-text"];
|
|
150
|
+
var AUTO_SLOT_ID = "zerocost-auto-slot";
|
|
149
151
|
var WidgetModule = class {
|
|
150
152
|
constructor(client) {
|
|
151
153
|
this.client = client;
|
|
152
154
|
}
|
|
153
155
|
mounted = /* @__PURE__ */ new Map();
|
|
154
|
-
async autoInjectWithConfig(display) {
|
|
156
|
+
async autoInjectWithConfig(display, widget) {
|
|
155
157
|
try {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
const selected = this.resolveSelectedWidget(display, widget);
|
|
159
|
+
this.clearAutoInjectedSlots();
|
|
160
|
+
if (!selected || !selected.enabled) {
|
|
161
|
+
this.client.log("No enabled widget format found. Skipping injection.");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
this.client.log(`Auto-inject: rendering configured format "${selected.format}" only.`);
|
|
165
|
+
await this.mountSingleFormat(selected);
|
|
166
|
+
this.client.log("\u2713 Single ad slot injected.");
|
|
161
167
|
} catch (err) {
|
|
162
168
|
this.client.log(`Widget autoInject error: ${err}`);
|
|
163
169
|
}
|
|
@@ -165,47 +171,98 @@ var WidgetModule = class {
|
|
|
165
171
|
async autoInject() {
|
|
166
172
|
try {
|
|
167
173
|
this.client.log("Fetching ad placements from server...");
|
|
168
|
-
const { display } = await this.client.request("/get-placements");
|
|
169
|
-
|
|
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
|
+
const { display, widget } = await this.client.request("/get-placements");
|
|
175
|
+
await this.autoInjectWithConfig(display, widget);
|
|
174
176
|
} catch (err) {
|
|
175
177
|
this.client.log(`Widget autoInject error: ${err}`);
|
|
176
178
|
}
|
|
177
179
|
}
|
|
180
|
+
resolveSelectedWidget(display, widget) {
|
|
181
|
+
if (widget?.enabled && widget?.format) {
|
|
182
|
+
return {
|
|
183
|
+
format: widget.format,
|
|
184
|
+
position: widget.position || "bottom-right",
|
|
185
|
+
theme: widget.theme || "dark",
|
|
186
|
+
autoplay: widget.autoplay ?? widget.format === "video-widget",
|
|
187
|
+
enabled: true
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
const configs = this.normalizeConfigs(display);
|
|
191
|
+
for (const fmt of FORMAT_PRIORITY) {
|
|
192
|
+
const cfg = configs[fmt];
|
|
193
|
+
if (cfg?.enabled) {
|
|
194
|
+
return {
|
|
195
|
+
format: fmt,
|
|
196
|
+
position: cfg.position,
|
|
197
|
+
theme: cfg.theme,
|
|
198
|
+
autoplay: cfg.autoplay,
|
|
199
|
+
enabled: true
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
178
205
|
normalizeConfigs(display) {
|
|
179
|
-
if (display && "video-widget" in display) {
|
|
206
|
+
if (display && typeof display === "object" && "video-widget" in display) {
|
|
180
207
|
return display;
|
|
181
208
|
}
|
|
182
209
|
const pos = display?.position || "bottom-right";
|
|
183
210
|
const theme = display?.theme || "dark";
|
|
184
211
|
return {
|
|
185
|
-
"video-widget": { position: pos, theme, autoplay:
|
|
186
|
-
"tooltip-ad": { position: pos, theme, autoplay: false, enabled:
|
|
187
|
-
"sponsored-card": { position: pos, theme, autoplay: false, enabled:
|
|
188
|
-
"
|
|
212
|
+
"video-widget": { position: pos, theme, autoplay: true, enabled: true },
|
|
213
|
+
"tooltip-ad": { position: pos, theme, autoplay: false, enabled: false },
|
|
214
|
+
"sponsored-card": { position: pos, theme, autoplay: false, enabled: false },
|
|
215
|
+
"sidebar-display": { position: pos, theme, autoplay: false, enabled: false },
|
|
216
|
+
"inline-text": { position: "after-paragraph-1", theme, autoplay: false, enabled: false }
|
|
189
217
|
};
|
|
190
218
|
}
|
|
191
|
-
async
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
el.
|
|
200
|
-
if (
|
|
201
|
-
|
|
219
|
+
async mountSingleFormat(config) {
|
|
220
|
+
const isInline = config.format === "inline-text";
|
|
221
|
+
const targetElementId = isInline ? this.ensureInlineTarget(config.position) : AUTO_SLOT_ID;
|
|
222
|
+
if (!targetElementId) {
|
|
223
|
+
this.client.log("Inline target not found. Skipping inline ad render.");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (!isInline) {
|
|
227
|
+
let el = document.getElementById(AUTO_SLOT_ID);
|
|
228
|
+
if (!el) {
|
|
229
|
+
el = document.createElement("div");
|
|
230
|
+
el.id = AUTO_SLOT_ID;
|
|
231
|
+
document.body.appendChild(el);
|
|
202
232
|
}
|
|
203
|
-
const posStyle = POSITION_STYLES[
|
|
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
|
|
206
|
-
|
|
207
|
-
|
|
233
|
+
const posStyle = POSITION_STYLES[config.position] || POSITION_STYLES["bottom-right"];
|
|
234
|
+
const maxW = config.format === "video-widget" ? "max-width:200px;" : config.format === "sponsored-card" || config.format === "sidebar-display" ? "max-width:176px;" : "max-width:320px;";
|
|
235
|
+
el.setAttribute("style", `${posStyle}${maxW}`);
|
|
236
|
+
el.setAttribute("data-zerocost", "");
|
|
237
|
+
el.setAttribute("data-format", config.format);
|
|
208
238
|
}
|
|
239
|
+
await this.mount(targetElementId, {
|
|
240
|
+
format: config.format,
|
|
241
|
+
refreshInterval: 30,
|
|
242
|
+
theme: config.theme,
|
|
243
|
+
autoplay: config.autoplay,
|
|
244
|
+
position: config.position
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
ensureInlineTarget(position) {
|
|
248
|
+
const paragraphMatch = /after-paragraph-(\d+)/.exec(position || "");
|
|
249
|
+
const index = paragraphMatch ? Number(paragraphMatch[1]) : 1;
|
|
250
|
+
const paragraphs = Array.from(document.querySelectorAll("p"));
|
|
251
|
+
const anchor = paragraphs[Math.max(0, Math.min(paragraphs.length - 1, index - 1))];
|
|
252
|
+
if (!anchor) return null;
|
|
253
|
+
const existing = document.getElementById(AUTO_SLOT_ID);
|
|
254
|
+
if (existing) existing.remove();
|
|
255
|
+
const inlineId = `${AUTO_SLOT_ID}-inline`;
|
|
256
|
+
let target = document.getElementById(inlineId);
|
|
257
|
+
if (!target) {
|
|
258
|
+
target = document.createElement("div");
|
|
259
|
+
target.id = inlineId;
|
|
260
|
+
target.setAttribute("data-zerocost", "");
|
|
261
|
+
target.setAttribute("data-format", "inline-text");
|
|
262
|
+
target.style.margin = "12px 0";
|
|
263
|
+
anchor.insertAdjacentElement("afterend", target);
|
|
264
|
+
}
|
|
265
|
+
return inlineId;
|
|
209
266
|
}
|
|
210
267
|
async mount(targetElementId, options = {}) {
|
|
211
268
|
const el = document.getElementById(targetElementId);
|
|
@@ -214,30 +271,25 @@ var WidgetModule = class {
|
|
|
214
271
|
const refreshMs = (options.refreshInterval ?? 30) * 1e3;
|
|
215
272
|
const theme = options.theme || "dark";
|
|
216
273
|
const format = options.format || "video-widget";
|
|
217
|
-
const autoplay = options.autoplay ??
|
|
274
|
+
const autoplay = options.autoplay ?? format === "video-widget";
|
|
218
275
|
const render = async () => {
|
|
219
276
|
try {
|
|
220
|
-
const formatMap = {
|
|
221
|
-
"video-widget": "video",
|
|
222
|
-
"sponsored-card": "display",
|
|
223
|
-
"sidebar-display": "display",
|
|
224
|
-
"tooltip-ad": "native",
|
|
225
|
-
"inline-text": "native"
|
|
226
|
-
};
|
|
227
277
|
const body = {
|
|
228
|
-
|
|
278
|
+
widget_style: format,
|
|
229
279
|
theme,
|
|
230
|
-
autoplay
|
|
280
|
+
autoplay,
|
|
281
|
+
position: options.position
|
|
231
282
|
};
|
|
232
283
|
const data = await this.client.request("/serve-widget", body);
|
|
233
284
|
const ad = data.ad;
|
|
234
285
|
if (!ad || !data.html) {
|
|
235
|
-
this.client.log(`No ad inventory available for format "${format}"
|
|
286
|
+
this.client.log(`No ad inventory available for configured format "${format}".`);
|
|
236
287
|
el.innerHTML = "";
|
|
237
288
|
return;
|
|
238
289
|
}
|
|
239
290
|
el.innerHTML = data.html;
|
|
240
291
|
el.setAttribute("data-zerocost-ad-id", ad.id);
|
|
292
|
+
this.ensureVideoPlayback(el);
|
|
241
293
|
const ctas = el.querySelectorAll("[data-zc-cta]");
|
|
242
294
|
ctas.forEach((cta) => {
|
|
243
295
|
cta.addEventListener("click", () => {
|
|
@@ -253,10 +305,7 @@ var WidgetModule = class {
|
|
|
253
305
|
closeBtn.addEventListener("click", (e) => {
|
|
254
306
|
e.preventDefault();
|
|
255
307
|
e.stopPropagation();
|
|
256
|
-
|
|
257
|
-
if (mountInfo?.interval) clearInterval(mountInfo.interval);
|
|
258
|
-
this.mounted.delete(targetElementId);
|
|
259
|
-
el.remove();
|
|
308
|
+
this.unmount(targetElementId);
|
|
260
309
|
});
|
|
261
310
|
}
|
|
262
311
|
} catch (err) {
|
|
@@ -267,138 +316,37 @@ var WidgetModule = class {
|
|
|
267
316
|
const interval = refreshMs > 0 ? setInterval(render, refreshMs) : null;
|
|
268
317
|
this.mounted.set(targetElementId, { elementId: targetElementId, interval });
|
|
269
318
|
}
|
|
270
|
-
|
|
271
|
-
const
|
|
272
|
-
if (
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
buildFormatHtml(format, ad, theme, autoplay) {
|
|
284
|
-
switch (format) {
|
|
285
|
-
case "video-widget":
|
|
286
|
-
return this.buildVideoWidget(ad, theme, autoplay);
|
|
287
|
-
case "tooltip-ad":
|
|
288
|
-
return this.buildTooltipAd(ad, theme);
|
|
289
|
-
case "sponsored-card":
|
|
290
|
-
return this.buildSponsoredCard(ad, theme);
|
|
291
|
-
case "inline-text":
|
|
292
|
-
return this.buildInlineText(ad, theme);
|
|
293
|
-
default:
|
|
294
|
-
return this.buildSponsoredCard(ad, theme);
|
|
319
|
+
ensureVideoPlayback(root) {
|
|
320
|
+
const video = root.querySelector("video");
|
|
321
|
+
if (!video) return;
|
|
322
|
+
video.muted = true;
|
|
323
|
+
video.autoplay = true;
|
|
324
|
+
video.loop = true;
|
|
325
|
+
video.playsInline = true;
|
|
326
|
+
video.preload = "auto";
|
|
327
|
+
const tryPlay = () => video.play().catch(() => {
|
|
328
|
+
});
|
|
329
|
+
if (video.readyState >= 2) {
|
|
330
|
+
tryPlay();
|
|
331
|
+
return;
|
|
295
332
|
}
|
|
333
|
+
video.addEventListener("loadeddata", tryPlay, { once: true });
|
|
334
|
+
video.addEventListener("canplay", tryPlay, { once: true });
|
|
296
335
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
buildVideoWidget(ad, theme, autoplay) {
|
|
302
|
-
const isDark = theme === "dark";
|
|
303
|
-
const border = isDark ? "#333" : "#e0e0e0";
|
|
304
|
-
const videoSrc = ad.video_url || ad.image_url || "";
|
|
305
|
-
const hasVideo = !!ad.video_url;
|
|
306
|
-
const sponsor = ad.title || "Sponsor";
|
|
307
|
-
const cta = ad.cta_text || "Learn More";
|
|
308
|
-
return `
|
|
309
|
-
<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
|
-
<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;" />`}
|
|
312
|
-
<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
|
-
<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
|
-
<div style="color:#fff;font-weight:600;font-size:12px;line-height:1.3;">${this.esc(ad.description || "")}</div>
|
|
315
|
-
<a href="${this.esc(ad.landing_url)}" target="_blank" rel="noopener" data-zc-cta style="display:block;margin-top:8px;width:100%;padding:6px 0;background:#fff;color:#000;font-size:10px;font-weight:700;text-align:center;border-radius:6px;text-decoration:none;cursor:pointer;">${this.esc(cta)}</a>
|
|
316
|
-
</div>
|
|
317
|
-
</div>`;
|
|
336
|
+
clearAutoInjectedSlots() {
|
|
337
|
+
this.unmountAll();
|
|
338
|
+
const existing = document.querySelectorAll("[data-zerocost], #zerocost-auto-slot, #zerocost-auto-slot-inline");
|
|
339
|
+
existing.forEach((node) => node.remove());
|
|
318
340
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const bg = isDark ? "#111" : "#ffffff";
|
|
326
|
-
const fg = isDark ? "#fff" : "#111";
|
|
327
|
-
const fgFaint = isDark ? "#888" : "#999";
|
|
328
|
-
const border = isDark ? "#333" : "#e0e0e0";
|
|
329
|
-
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;">
|
|
331
|
-
<div style="font-size:10px;font-family:monospace;margin-bottom:6px;display:flex;align-items:center;color:${fgFaint};">
|
|
332
|
-
<span style="width:6px;height:6px;border-radius:50%;background:#22c55e;margin-right:6px;display:inline-block;"></span>
|
|
333
|
-
Sponsored
|
|
334
|
-
</div>
|
|
335
|
-
<p style="font-size:12px;line-height:1.5;color:${fg};margin:0;">
|
|
336
|
-
${this.esc(ad.description || ad.title)}
|
|
337
|
-
<a href="${this.esc(ad.landing_url)}" target="_blank" rel="noopener" data-zc-cta style="color:#60a5fa;text-decoration:underline;text-underline-offset:2px;cursor:pointer;margin-left:4px;">${this.esc(ad.cta_text || "Learn More")}</a>
|
|
338
|
-
</p>
|
|
339
|
-
</div>`;
|
|
340
|
-
}
|
|
341
|
-
/**
|
|
342
|
-
* Sponsored Card — matches playground SponsoredCardPreview
|
|
343
|
-
* Card with gradient header, icon, headline, description, CTA button
|
|
344
|
-
*/
|
|
345
|
-
buildSponsoredCard(ad, theme) {
|
|
346
|
-
const isDark = theme === "dark";
|
|
347
|
-
const bg = isDark ? "#111" : "#ffffff";
|
|
348
|
-
const fg = isDark ? "#fff" : "#111";
|
|
349
|
-
const fgFaint = isDark ? "#888" : "#999";
|
|
350
|
-
const border = isDark ? "#333" : "#e0e0e0";
|
|
351
|
-
const iconBg = isDark ? "#0a0a0a" : "#f5f5f5";
|
|
352
|
-
const initial = (ad.title || "A").charAt(0).toUpperCase();
|
|
353
|
-
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;">
|
|
355
|
-
${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
|
-
<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
|
-
<div style="font-weight:700;font-size:12px;color:${fg};margin-bottom:2px;letter-spacing:-0.01em;">${this.esc(ad.title)}</div>
|
|
359
|
-
<div style="font-size:10px;color:${fgFaint};line-height:1.4;margin-bottom:12px;">${this.esc(ad.description || "")}</div>
|
|
360
|
-
<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>
|
|
361
|
-
</div>
|
|
362
|
-
</div>`;
|
|
341
|
+
unmount(targetElementId) {
|
|
342
|
+
const slot = this.mounted.get(targetElementId);
|
|
343
|
+
if (slot?.interval) clearInterval(slot.interval);
|
|
344
|
+
const el = document.getElementById(targetElementId);
|
|
345
|
+
if (el) el.remove();
|
|
346
|
+
this.mounted.delete(targetElementId);
|
|
363
347
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
* Card with sponsored header, icon, headline, body, CTA button
|
|
367
|
-
* Designed to be injected inside content/chat streams
|
|
368
|
-
*/
|
|
369
|
-
buildInlineText(ad, theme) {
|
|
370
|
-
const isDark = theme === "dark";
|
|
371
|
-
const bg = isDark ? "#141414" : "#fafafa";
|
|
372
|
-
const fg = isDark ? "#fff" : "#111";
|
|
373
|
-
const fgMuted = isDark ? "#aaa" : "#666";
|
|
374
|
-
const fgFaint = isDark ? "#888" : "#999";
|
|
375
|
-
const border = isDark ? "#2a2a2a" : "#d0d0d0";
|
|
376
|
-
const headerBorder = isDark ? "#222" : "#e8e8e8";
|
|
377
|
-
const btnBg = isDark ? "#fff" : "#111";
|
|
378
|
-
const btnFg = isDark ? "#111" : "#fff";
|
|
379
|
-
const initial = "\u25B2";
|
|
380
|
-
const sponsor = ad.title?.split("\u2014")[0]?.trim() || ad.title || "Sponsor";
|
|
381
|
-
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%;">
|
|
383
|
-
<div style="padding:8px 12px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid ${headerBorder};">
|
|
384
|
-
<span style="font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:0.1em;color:${fgFaint};">Sponsored</span>
|
|
385
|
-
<span style="font-size:9px;color:${fgFaint};">Ad \xB7 ${this.esc(sponsor)}</span>
|
|
386
|
-
</div>
|
|
387
|
-
<div style="padding:12px;">
|
|
388
|
-
<div style="display:flex;gap:10px;">
|
|
389
|
-
<div style="width:32px;height:32px;border-radius:6px;background:${btnBg};color:${btnFg};display:flex;align-items:center;justify-content:center;font-weight:700;font-size:11px;flex-shrink:0;">${initial}</div>
|
|
390
|
-
<div style="flex:1;min-width:0;">
|
|
391
|
-
<div style="font-weight:600;font-size:12px;color:${fg};margin-bottom:2px;">${this.esc(ad.title)}</div>
|
|
392
|
-
<div style="font-size:11px;line-height:1.4;color:${fgMuted};">${this.esc(ad.description || "")}</div>
|
|
393
|
-
<a href="${this.esc(ad.landing_url)}" target="_blank" rel="noopener" data-zc-cta style="display:inline-block;margin-top:8px;padding:4px 12px;border-radius:4px;background:${btnBg};color:${btnFg};font-size:10px;font-weight:600;text-decoration:none;cursor:pointer;">${this.esc(ad.cta_text || "Learn More")} \u2192</a>
|
|
394
|
-
</div>
|
|
395
|
-
</div>
|
|
396
|
-
</div>
|
|
397
|
-
</div>`;
|
|
398
|
-
}
|
|
399
|
-
esc(str) {
|
|
400
|
-
if (!str) return "";
|
|
401
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
348
|
+
unmountAll() {
|
|
349
|
+
for (const id of Array.from(this.mounted.keys())) this.unmount(id);
|
|
402
350
|
}
|
|
403
351
|
};
|
|
404
352
|
|
|
@@ -768,10 +716,14 @@ var ZerocostSDK = class {
|
|
|
768
716
|
this.core.log("Running in non-browser environment \u2014 skipping DOM injection.");
|
|
769
717
|
return;
|
|
770
718
|
}
|
|
719
|
+
const isIframe = window !== window.top;
|
|
720
|
+
if (isIframe) {
|
|
721
|
+
this.core.log("Running inside an iframe. Ads will still render if permissions allow.");
|
|
722
|
+
}
|
|
771
723
|
this.core.log("Initializing... Ads will be auto-injected into the DOM. No custom component required.");
|
|
772
724
|
try {
|
|
773
|
-
const { display, dataCollection } = await this.core.request("/get-placements");
|
|
774
|
-
this.widget.autoInjectWithConfig(display);
|
|
725
|
+
const { display, widget, dataCollection } = await this.core.request("/get-placements");
|
|
726
|
+
this.widget.autoInjectWithConfig(display, widget);
|
|
775
727
|
if (dataCollection?.llm) {
|
|
776
728
|
this.data.start(dataCollection.llm);
|
|
777
729
|
}
|
package/dist/index.js
CHANGED
|
@@ -117,18 +117,24 @@ 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", "tooltip-ad", "sponsored-card", "sidebar-display", "inline-text"];
|
|
121
|
+
var AUTO_SLOT_ID = "zerocost-auto-slot";
|
|
120
122
|
var WidgetModule = class {
|
|
121
123
|
constructor(client) {
|
|
122
124
|
this.client = client;
|
|
123
125
|
}
|
|
124
126
|
mounted = /* @__PURE__ */ new Map();
|
|
125
|
-
async autoInjectWithConfig(display) {
|
|
127
|
+
async autoInjectWithConfig(display, widget) {
|
|
126
128
|
try {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
const selected = this.resolveSelectedWidget(display, widget);
|
|
130
|
+
this.clearAutoInjectedSlots();
|
|
131
|
+
if (!selected || !selected.enabled) {
|
|
132
|
+
this.client.log("No enabled widget format found. Skipping injection.");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.client.log(`Auto-inject: rendering configured format "${selected.format}" only.`);
|
|
136
|
+
await this.mountSingleFormat(selected);
|
|
137
|
+
this.client.log("\u2713 Single ad slot injected.");
|
|
132
138
|
} catch (err) {
|
|
133
139
|
this.client.log(`Widget autoInject error: ${err}`);
|
|
134
140
|
}
|
|
@@ -136,47 +142,98 @@ var WidgetModule = class {
|
|
|
136
142
|
async autoInject() {
|
|
137
143
|
try {
|
|
138
144
|
this.client.log("Fetching ad placements from server...");
|
|
139
|
-
const { display } = await this.client.request("/get-placements");
|
|
140
|
-
|
|
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
|
+
const { display, widget } = await this.client.request("/get-placements");
|
|
146
|
+
await this.autoInjectWithConfig(display, widget);
|
|
145
147
|
} catch (err) {
|
|
146
148
|
this.client.log(`Widget autoInject error: ${err}`);
|
|
147
149
|
}
|
|
148
150
|
}
|
|
151
|
+
resolveSelectedWidget(display, widget) {
|
|
152
|
+
if (widget?.enabled && widget?.format) {
|
|
153
|
+
return {
|
|
154
|
+
format: widget.format,
|
|
155
|
+
position: widget.position || "bottom-right",
|
|
156
|
+
theme: widget.theme || "dark",
|
|
157
|
+
autoplay: widget.autoplay ?? widget.format === "video-widget",
|
|
158
|
+
enabled: true
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const configs = this.normalizeConfigs(display);
|
|
162
|
+
for (const fmt of FORMAT_PRIORITY) {
|
|
163
|
+
const cfg = configs[fmt];
|
|
164
|
+
if (cfg?.enabled) {
|
|
165
|
+
return {
|
|
166
|
+
format: fmt,
|
|
167
|
+
position: cfg.position,
|
|
168
|
+
theme: cfg.theme,
|
|
169
|
+
autoplay: cfg.autoplay,
|
|
170
|
+
enabled: true
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
149
176
|
normalizeConfigs(display) {
|
|
150
|
-
if (display && "video-widget" in display) {
|
|
177
|
+
if (display && typeof display === "object" && "video-widget" in display) {
|
|
151
178
|
return display;
|
|
152
179
|
}
|
|
153
180
|
const pos = display?.position || "bottom-right";
|
|
154
181
|
const theme = display?.theme || "dark";
|
|
155
182
|
return {
|
|
156
|
-
"video-widget": { position: pos, theme, autoplay:
|
|
157
|
-
"tooltip-ad": { position: pos, theme, autoplay: false, enabled:
|
|
158
|
-
"sponsored-card": { position: pos, theme, autoplay: false, enabled:
|
|
159
|
-
"
|
|
183
|
+
"video-widget": { position: pos, theme, autoplay: true, enabled: true },
|
|
184
|
+
"tooltip-ad": { position: pos, theme, autoplay: false, enabled: false },
|
|
185
|
+
"sponsored-card": { position: pos, theme, autoplay: false, enabled: false },
|
|
186
|
+
"sidebar-display": { position: pos, theme, autoplay: false, enabled: false },
|
|
187
|
+
"inline-text": { position: "after-paragraph-1", theme, autoplay: false, enabled: false }
|
|
160
188
|
};
|
|
161
189
|
}
|
|
162
|
-
async
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
el.
|
|
171
|
-
if (
|
|
172
|
-
|
|
190
|
+
async mountSingleFormat(config) {
|
|
191
|
+
const isInline = config.format === "inline-text";
|
|
192
|
+
const targetElementId = isInline ? this.ensureInlineTarget(config.position) : AUTO_SLOT_ID;
|
|
193
|
+
if (!targetElementId) {
|
|
194
|
+
this.client.log("Inline target not found. Skipping inline ad render.");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (!isInline) {
|
|
198
|
+
let el = document.getElementById(AUTO_SLOT_ID);
|
|
199
|
+
if (!el) {
|
|
200
|
+
el = document.createElement("div");
|
|
201
|
+
el.id = AUTO_SLOT_ID;
|
|
202
|
+
document.body.appendChild(el);
|
|
173
203
|
}
|
|
174
|
-
const posStyle = POSITION_STYLES[
|
|
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
|
|
177
|
-
|
|
178
|
-
|
|
204
|
+
const posStyle = POSITION_STYLES[config.position] || POSITION_STYLES["bottom-right"];
|
|
205
|
+
const maxW = config.format === "video-widget" ? "max-width:200px;" : config.format === "sponsored-card" || config.format === "sidebar-display" ? "max-width:176px;" : "max-width:320px;";
|
|
206
|
+
el.setAttribute("style", `${posStyle}${maxW}`);
|
|
207
|
+
el.setAttribute("data-zerocost", "");
|
|
208
|
+
el.setAttribute("data-format", config.format);
|
|
179
209
|
}
|
|
210
|
+
await this.mount(targetElementId, {
|
|
211
|
+
format: config.format,
|
|
212
|
+
refreshInterval: 30,
|
|
213
|
+
theme: config.theme,
|
|
214
|
+
autoplay: config.autoplay,
|
|
215
|
+
position: config.position
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
ensureInlineTarget(position) {
|
|
219
|
+
const paragraphMatch = /after-paragraph-(\d+)/.exec(position || "");
|
|
220
|
+
const index = paragraphMatch ? Number(paragraphMatch[1]) : 1;
|
|
221
|
+
const paragraphs = Array.from(document.querySelectorAll("p"));
|
|
222
|
+
const anchor = paragraphs[Math.max(0, Math.min(paragraphs.length - 1, index - 1))];
|
|
223
|
+
if (!anchor) return null;
|
|
224
|
+
const existing = document.getElementById(AUTO_SLOT_ID);
|
|
225
|
+
if (existing) existing.remove();
|
|
226
|
+
const inlineId = `${AUTO_SLOT_ID}-inline`;
|
|
227
|
+
let target = document.getElementById(inlineId);
|
|
228
|
+
if (!target) {
|
|
229
|
+
target = document.createElement("div");
|
|
230
|
+
target.id = inlineId;
|
|
231
|
+
target.setAttribute("data-zerocost", "");
|
|
232
|
+
target.setAttribute("data-format", "inline-text");
|
|
233
|
+
target.style.margin = "12px 0";
|
|
234
|
+
anchor.insertAdjacentElement("afterend", target);
|
|
235
|
+
}
|
|
236
|
+
return inlineId;
|
|
180
237
|
}
|
|
181
238
|
async mount(targetElementId, options = {}) {
|
|
182
239
|
const el = document.getElementById(targetElementId);
|
|
@@ -185,30 +242,25 @@ var WidgetModule = class {
|
|
|
185
242
|
const refreshMs = (options.refreshInterval ?? 30) * 1e3;
|
|
186
243
|
const theme = options.theme || "dark";
|
|
187
244
|
const format = options.format || "video-widget";
|
|
188
|
-
const autoplay = options.autoplay ??
|
|
245
|
+
const autoplay = options.autoplay ?? format === "video-widget";
|
|
189
246
|
const render = async () => {
|
|
190
247
|
try {
|
|
191
|
-
const formatMap = {
|
|
192
|
-
"video-widget": "video",
|
|
193
|
-
"sponsored-card": "display",
|
|
194
|
-
"sidebar-display": "display",
|
|
195
|
-
"tooltip-ad": "native",
|
|
196
|
-
"inline-text": "native"
|
|
197
|
-
};
|
|
198
248
|
const body = {
|
|
199
|
-
|
|
249
|
+
widget_style: format,
|
|
200
250
|
theme,
|
|
201
|
-
autoplay
|
|
251
|
+
autoplay,
|
|
252
|
+
position: options.position
|
|
202
253
|
};
|
|
203
254
|
const data = await this.client.request("/serve-widget", body);
|
|
204
255
|
const ad = data.ad;
|
|
205
256
|
if (!ad || !data.html) {
|
|
206
|
-
this.client.log(`No ad inventory available for format "${format}"
|
|
257
|
+
this.client.log(`No ad inventory available for configured format "${format}".`);
|
|
207
258
|
el.innerHTML = "";
|
|
208
259
|
return;
|
|
209
260
|
}
|
|
210
261
|
el.innerHTML = data.html;
|
|
211
262
|
el.setAttribute("data-zerocost-ad-id", ad.id);
|
|
263
|
+
this.ensureVideoPlayback(el);
|
|
212
264
|
const ctas = el.querySelectorAll("[data-zc-cta]");
|
|
213
265
|
ctas.forEach((cta) => {
|
|
214
266
|
cta.addEventListener("click", () => {
|
|
@@ -224,10 +276,7 @@ var WidgetModule = class {
|
|
|
224
276
|
closeBtn.addEventListener("click", (e) => {
|
|
225
277
|
e.preventDefault();
|
|
226
278
|
e.stopPropagation();
|
|
227
|
-
|
|
228
|
-
if (mountInfo?.interval) clearInterval(mountInfo.interval);
|
|
229
|
-
this.mounted.delete(targetElementId);
|
|
230
|
-
el.remove();
|
|
279
|
+
this.unmount(targetElementId);
|
|
231
280
|
});
|
|
232
281
|
}
|
|
233
282
|
} catch (err) {
|
|
@@ -238,138 +287,37 @@ var WidgetModule = class {
|
|
|
238
287
|
const interval = refreshMs > 0 ? setInterval(render, refreshMs) : null;
|
|
239
288
|
this.mounted.set(targetElementId, { elementId: targetElementId, interval });
|
|
240
289
|
}
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
if (
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
buildFormatHtml(format, ad, theme, autoplay) {
|
|
255
|
-
switch (format) {
|
|
256
|
-
case "video-widget":
|
|
257
|
-
return this.buildVideoWidget(ad, theme, autoplay);
|
|
258
|
-
case "tooltip-ad":
|
|
259
|
-
return this.buildTooltipAd(ad, theme);
|
|
260
|
-
case "sponsored-card":
|
|
261
|
-
return this.buildSponsoredCard(ad, theme);
|
|
262
|
-
case "inline-text":
|
|
263
|
-
return this.buildInlineText(ad, theme);
|
|
264
|
-
default:
|
|
265
|
-
return this.buildSponsoredCard(ad, theme);
|
|
290
|
+
ensureVideoPlayback(root) {
|
|
291
|
+
const video = root.querySelector("video");
|
|
292
|
+
if (!video) return;
|
|
293
|
+
video.muted = true;
|
|
294
|
+
video.autoplay = true;
|
|
295
|
+
video.loop = true;
|
|
296
|
+
video.playsInline = true;
|
|
297
|
+
video.preload = "auto";
|
|
298
|
+
const tryPlay = () => video.play().catch(() => {
|
|
299
|
+
});
|
|
300
|
+
if (video.readyState >= 2) {
|
|
301
|
+
tryPlay();
|
|
302
|
+
return;
|
|
266
303
|
}
|
|
304
|
+
video.addEventListener("loadeddata", tryPlay, { once: true });
|
|
305
|
+
video.addEventListener("canplay", tryPlay, { once: true });
|
|
267
306
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
buildVideoWidget(ad, theme, autoplay) {
|
|
273
|
-
const isDark = theme === "dark";
|
|
274
|
-
const border = isDark ? "#333" : "#e0e0e0";
|
|
275
|
-
const videoSrc = ad.video_url || ad.image_url || "";
|
|
276
|
-
const hasVideo = !!ad.video_url;
|
|
277
|
-
const sponsor = ad.title || "Sponsor";
|
|
278
|
-
const cta = ad.cta_text || "Learn More";
|
|
279
|
-
return `
|
|
280
|
-
<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
|
-
<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;" />`}
|
|
283
|
-
<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
|
-
<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
|
-
<div style="color:#fff;font-weight:600;font-size:12px;line-height:1.3;">${this.esc(ad.description || "")}</div>
|
|
286
|
-
<a href="${this.esc(ad.landing_url)}" target="_blank" rel="noopener" data-zc-cta style="display:block;margin-top:8px;width:100%;padding:6px 0;background:#fff;color:#000;font-size:10px;font-weight:700;text-align:center;border-radius:6px;text-decoration:none;cursor:pointer;">${this.esc(cta)}</a>
|
|
287
|
-
</div>
|
|
288
|
-
</div>`;
|
|
307
|
+
clearAutoInjectedSlots() {
|
|
308
|
+
this.unmountAll();
|
|
309
|
+
const existing = document.querySelectorAll("[data-zerocost], #zerocost-auto-slot, #zerocost-auto-slot-inline");
|
|
310
|
+
existing.forEach((node) => node.remove());
|
|
289
311
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const bg = isDark ? "#111" : "#ffffff";
|
|
297
|
-
const fg = isDark ? "#fff" : "#111";
|
|
298
|
-
const fgFaint = isDark ? "#888" : "#999";
|
|
299
|
-
const border = isDark ? "#333" : "#e0e0e0";
|
|
300
|
-
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;">
|
|
302
|
-
<div style="font-size:10px;font-family:monospace;margin-bottom:6px;display:flex;align-items:center;color:${fgFaint};">
|
|
303
|
-
<span style="width:6px;height:6px;border-radius:50%;background:#22c55e;margin-right:6px;display:inline-block;"></span>
|
|
304
|
-
Sponsored
|
|
305
|
-
</div>
|
|
306
|
-
<p style="font-size:12px;line-height:1.5;color:${fg};margin:0;">
|
|
307
|
-
${this.esc(ad.description || ad.title)}
|
|
308
|
-
<a href="${this.esc(ad.landing_url)}" target="_blank" rel="noopener" data-zc-cta style="color:#60a5fa;text-decoration:underline;text-underline-offset:2px;cursor:pointer;margin-left:4px;">${this.esc(ad.cta_text || "Learn More")}</a>
|
|
309
|
-
</p>
|
|
310
|
-
</div>`;
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Sponsored Card — matches playground SponsoredCardPreview
|
|
314
|
-
* Card with gradient header, icon, headline, description, CTA button
|
|
315
|
-
*/
|
|
316
|
-
buildSponsoredCard(ad, theme) {
|
|
317
|
-
const isDark = theme === "dark";
|
|
318
|
-
const bg = isDark ? "#111" : "#ffffff";
|
|
319
|
-
const fg = isDark ? "#fff" : "#111";
|
|
320
|
-
const fgFaint = isDark ? "#888" : "#999";
|
|
321
|
-
const border = isDark ? "#333" : "#e0e0e0";
|
|
322
|
-
const iconBg = isDark ? "#0a0a0a" : "#f5f5f5";
|
|
323
|
-
const initial = (ad.title || "A").charAt(0).toUpperCase();
|
|
324
|
-
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;">
|
|
326
|
-
${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
|
-
<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
|
-
<div style="font-weight:700;font-size:12px;color:${fg};margin-bottom:2px;letter-spacing:-0.01em;">${this.esc(ad.title)}</div>
|
|
330
|
-
<div style="font-size:10px;color:${fgFaint};line-height:1.4;margin-bottom:12px;">${this.esc(ad.description || "")}</div>
|
|
331
|
-
<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>
|
|
332
|
-
</div>
|
|
333
|
-
</div>`;
|
|
312
|
+
unmount(targetElementId) {
|
|
313
|
+
const slot = this.mounted.get(targetElementId);
|
|
314
|
+
if (slot?.interval) clearInterval(slot.interval);
|
|
315
|
+
const el = document.getElementById(targetElementId);
|
|
316
|
+
if (el) el.remove();
|
|
317
|
+
this.mounted.delete(targetElementId);
|
|
334
318
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
* Card with sponsored header, icon, headline, body, CTA button
|
|
338
|
-
* Designed to be injected inside content/chat streams
|
|
339
|
-
*/
|
|
340
|
-
buildInlineText(ad, theme) {
|
|
341
|
-
const isDark = theme === "dark";
|
|
342
|
-
const bg = isDark ? "#141414" : "#fafafa";
|
|
343
|
-
const fg = isDark ? "#fff" : "#111";
|
|
344
|
-
const fgMuted = isDark ? "#aaa" : "#666";
|
|
345
|
-
const fgFaint = isDark ? "#888" : "#999";
|
|
346
|
-
const border = isDark ? "#2a2a2a" : "#d0d0d0";
|
|
347
|
-
const headerBorder = isDark ? "#222" : "#e8e8e8";
|
|
348
|
-
const btnBg = isDark ? "#fff" : "#111";
|
|
349
|
-
const btnFg = isDark ? "#111" : "#fff";
|
|
350
|
-
const initial = "\u25B2";
|
|
351
|
-
const sponsor = ad.title?.split("\u2014")[0]?.trim() || ad.title || "Sponsor";
|
|
352
|
-
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%;">
|
|
354
|
-
<div style="padding:8px 12px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid ${headerBorder};">
|
|
355
|
-
<span style="font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:0.1em;color:${fgFaint};">Sponsored</span>
|
|
356
|
-
<span style="font-size:9px;color:${fgFaint};">Ad \xB7 ${this.esc(sponsor)}</span>
|
|
357
|
-
</div>
|
|
358
|
-
<div style="padding:12px;">
|
|
359
|
-
<div style="display:flex;gap:10px;">
|
|
360
|
-
<div style="width:32px;height:32px;border-radius:6px;background:${btnBg};color:${btnFg};display:flex;align-items:center;justify-content:center;font-weight:700;font-size:11px;flex-shrink:0;">${initial}</div>
|
|
361
|
-
<div style="flex:1;min-width:0;">
|
|
362
|
-
<div style="font-weight:600;font-size:12px;color:${fg};margin-bottom:2px;">${this.esc(ad.title)}</div>
|
|
363
|
-
<div style="font-size:11px;line-height:1.4;color:${fgMuted};">${this.esc(ad.description || "")}</div>
|
|
364
|
-
<a href="${this.esc(ad.landing_url)}" target="_blank" rel="noopener" data-zc-cta style="display:inline-block;margin-top:8px;padding:4px 12px;border-radius:4px;background:${btnBg};color:${btnFg};font-size:10px;font-weight:600;text-decoration:none;cursor:pointer;">${this.esc(ad.cta_text || "Learn More")} \u2192</a>
|
|
365
|
-
</div>
|
|
366
|
-
</div>
|
|
367
|
-
</div>
|
|
368
|
-
</div>`;
|
|
369
|
-
}
|
|
370
|
-
esc(str) {
|
|
371
|
-
if (!str) return "";
|
|
372
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
319
|
+
unmountAll() {
|
|
320
|
+
for (const id of Array.from(this.mounted.keys())) this.unmount(id);
|
|
373
321
|
}
|
|
374
322
|
};
|
|
375
323
|
|
|
@@ -739,10 +687,14 @@ var ZerocostSDK = class {
|
|
|
739
687
|
this.core.log("Running in non-browser environment \u2014 skipping DOM injection.");
|
|
740
688
|
return;
|
|
741
689
|
}
|
|
690
|
+
const isIframe = window !== window.top;
|
|
691
|
+
if (isIframe) {
|
|
692
|
+
this.core.log("Running inside an iframe. Ads will still render if permissions allow.");
|
|
693
|
+
}
|
|
742
694
|
this.core.log("Initializing... Ads will be auto-injected into the DOM. No custom component required.");
|
|
743
695
|
try {
|
|
744
|
-
const { display, dataCollection } = await this.core.request("/get-placements");
|
|
745
|
-
this.widget.autoInjectWithConfig(display);
|
|
696
|
+
const { display, widget, dataCollection } = await this.core.request("/get-placements");
|
|
697
|
+
this.widget.autoInjectWithConfig(display, widget);
|
|
746
698
|
if (dataCollection?.llm) {
|
|
747
699
|
this.data.start(dataCollection.llm);
|
|
748
700
|
}
|
package/dist/modules/widget.d.ts
CHANGED
|
@@ -6,6 +6,9 @@ interface FormatConfig {
|
|
|
6
6
|
enabled: boolean;
|
|
7
7
|
}
|
|
8
8
|
type DisplayConfigs = Record<string, FormatConfig>;
|
|
9
|
+
interface SelectedWidgetConfig extends FormatConfig {
|
|
10
|
+
format: string;
|
|
11
|
+
}
|
|
9
12
|
export declare class WidgetModule {
|
|
10
13
|
private client;
|
|
11
14
|
private mounted;
|
|
@@ -13,40 +16,22 @@ export declare class WidgetModule {
|
|
|
13
16
|
autoInjectWithConfig(display: DisplayConfigs | {
|
|
14
17
|
position: string;
|
|
15
18
|
theme: string;
|
|
16
|
-
}): Promise<void>;
|
|
19
|
+
}, widget?: Partial<SelectedWidgetConfig> | null): Promise<void>;
|
|
17
20
|
autoInject(): Promise<void>;
|
|
21
|
+
private resolveSelectedWidget;
|
|
18
22
|
private normalizeConfigs;
|
|
19
|
-
private
|
|
23
|
+
private mountSingleFormat;
|
|
24
|
+
private ensureInlineTarget;
|
|
20
25
|
mount(targetElementId: string, options?: {
|
|
21
26
|
format?: string;
|
|
22
27
|
refreshInterval?: number;
|
|
23
28
|
theme?: string;
|
|
24
29
|
autoplay?: boolean;
|
|
30
|
+
position?: string;
|
|
25
31
|
}): Promise<void>;
|
|
32
|
+
private ensureVideoPlayback;
|
|
33
|
+
private clearAutoInjectedSlots;
|
|
26
34
|
unmount(targetElementId: string): void;
|
|
27
35
|
unmountAll(): void;
|
|
28
|
-
private buildFormatHtml;
|
|
29
|
-
/**
|
|
30
|
-
* Floating Video Widget — matches playground VideoWidgetPreview
|
|
31
|
-
* 9:16 aspect ratio, video with gradient overlay, sponsor label, CTA button
|
|
32
|
-
*/
|
|
33
|
-
private buildVideoWidget;
|
|
34
|
-
/**
|
|
35
|
-
* Tooltip Ad — matches playground ContextualTextPreview
|
|
36
|
-
* Small floating card with sponsored label, inline text, and link
|
|
37
|
-
*/
|
|
38
|
-
private buildTooltipAd;
|
|
39
|
-
/**
|
|
40
|
-
* Sponsored Card — matches playground SponsoredCardPreview
|
|
41
|
-
* Card with gradient header, icon, headline, description, CTA button
|
|
42
|
-
*/
|
|
43
|
-
private buildSponsoredCard;
|
|
44
|
-
/**
|
|
45
|
-
* Inline Text Ad — matches playground InlineTextPreview
|
|
46
|
-
* Card with sponsored header, icon, headline, body, CTA button
|
|
47
|
-
* Designed to be injected inside content/chat streams
|
|
48
|
-
*/
|
|
49
|
-
private buildInlineText;
|
|
50
|
-
private esc;
|
|
51
36
|
}
|
|
52
37
|
export {};
|