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