PageSourceSearch

https://www.scientificamerican.com/sciads/sciads.js

js scientificamerican.com collected 2026-10-01 07:48:04 UTC 24,616 bytes, 821 lines download raw bytes

vendor: 19 bytes, lines 1-2
1(() => {
2  // src/s
2ciads/utils/log.js
3  var noop = () => {
4  };
5  var log = window.location.search.match(/debug=ads/) ? console.debug.bind(window.console, "[sciads]") : noop;
6
7  
7// src/sciads/perf.js
8  var PERF_KEYS = {
9    libLoaded: "ads:library_loaded",
10    consent: "ads:consent_determined",
11    measureConsent: "ads:time_to_consent",
12    page: "ads:page_requested",
13    gptLoaded: "ads:gpt_loaded",
14    gptQueue: "ads:gpt_queue_init",
15    firstCall: "ads:first_call",
16    firstLoaded: "ads:first_loaded",
17    measureFirstCall: "ads:time_to_first_call",
18    measureFirstLoad: "ads:time_to_first_ad"
19  };
20  function logMeasurement(key, message) {
21    let t = Math.round(performance.getEntriesByName(key)[0].duration);
22    log(`${message}: ${t}ms`);
23  }
24  function trackPerformanceFirstCall(gptAd) {
25    if (!performance.getEntriesByName(PERF_KEYS.firstCall).length) {
26      performance.mark(PERF_KEYS.firstCall);
27      performance.measure(PERF_KEYS.measureFirstCall, PERF_KEYS.page, PERF_KEYS.firstCall);
28    }
29  }
30  function trackPerformanceFirstLoad(gptAd) {
31    if (!performance.getEntriesByName(PERF_KEYS.firstLoaded).length) {
32      performance.mark(PERF_KEYS.firstLoaded);
33      performance.measure(PERF_KEYS.measureFirstLoad, PERF_KEYS.page, PERF_KEYS.firstLoaded);
34      logMeasurement(PERF_KEYS.measureFirstLoad, "Time to first ad");
35      let t = Math.round(performance.getEntriesByName(PERF_KEYS.measureFirstLoad)[0].duration);
36    }
37  }
38
39  
39// src/sciads/consent.js
40  var optOuts = {
41    ads: false
42  };
43  function isOptedOut() {
44    return optOuts.ads;
45  }
46  window.consentQueue = window.consentQueue || [];
47  function getConsent() {
48    return new Promise((resolve) => {
49      if (navigator.globalPrivacyControl) {
50        optOuts.ads = true;
51        resolve();
52        return;
53      }
54      window.consentQueue.push(() => {
55        const consent = window.Osano?.cm?.getConsent() || {};
56        const hasMarketingConsent = consent.MARKETING === "ACCEPT";
57        const optedOut = consent.OPT_OUT === "ACCEPT";
58        optOuts.ads = !hasMarketingConsent || optedOut;
59        resolve();
60      });
61    }).then(() => {
62      performance.mark(PERF_KEYS.consent);
63      performance.measure(PERF_KEYS.measureConsent, PERF_KEYS.libLoaded, PERF_KEYS.consent);
64      logMeasurement(PERF_KEYS.measureConsent, "Time to determine consent status");
65      if (isOptedOut()) {
66        log(`Ads: user opted out. Showing nonpersonalized ads only.`);
67      }
68    });
69  }
70
71  
71// src/sciads/constants.js
72  var IAB_SIZES = ["320x50", "300x250", "300x600", "728x90", "970x250"];
73  var PREMIUM_SIZES = ["320x450", "970x350"];
74
75  
75// src/sciads/utils/cookies.js
76  function readCookie(name) {
77    var nameEQ = name + "=";
78    var ca = document.cookie.split(";");
79    for (var i = 0; i < ca.length; i++) {
80      var c = ca[i];
81      while (c.charAt(0) == " ") c = c.substring(1, c.length);
82      if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
83    }
84    return null;
85  }
86
87  
vendor: 1,685 bytes, lines 87-143
87// node_modules/jwt-decode/build/esm/index.js
88  var InvalidTokenError = class extends Error {
89  };
90  InvalidTokenError.prototype.name = "InvalidTokenError";
91  function b64DecodeUnicode(str) {
92    return decodeURIComponent(atob(str).replace(/(.)/g, (m, p) => {
93      let code = p.charCodeAt(0).toString(16).toUpperCase();
94      if (code.length < 2) {
95        code = "0" + code;
96      }
97      return "%" + code;
98    }));
99  }
100  function base64UrlDecode(str) {
101    let output = str.replace(/-/g, "+").replace(/_/g, "/");
102    switch (output.length % 4) {
103      case 0:
104        break;
105      case 2:
106        output += "==";
107        break;
108      case 3:
109        output += "=";
110        break;
111      default:
112        throw new Error("base64 string is not of the correct length");
113    }
114    try {
115      return b64DecodeUnicode(output);
116    } catch (err) {
117      return atob(output);
118    }
119  }
120  function jwtDecode(token, options) {
121    if (typeof token !== "string") {
122      throw new InvalidTokenError("Invalid token specified: must be a string");
123    }
124    options || (options = {});
125    const pos = options.header === true ? 0 : 1;
126    const part = token.split(".")[pos];
127    if (typeof part !== "string") {
128      throw new InvalidTokenError(`Invalid token specified: missing part #${pos + 1}`);
129    }
130    let decoded;
131    try {
132      decoded = base64UrlDecode(part);
133    } catch (e) {
134      throw new InvalidTokenError(`Invalid token specified: invalid base64 for part #${pos + 1} (${e.message})`);
135    }
136    try {
137      return JSON.parse(decoded);
138    } catch (e) {
139      throw new InvalidTokenError(`Invalid token specified: invalid json for part #${pos + 1} (${e.message})`);
140    }
141  }
142
143  
143// src/sciads/utils/user.js
144  function getUserData() {
145    const auth0Cookie = document.cookie.split(";").map((c) => c.trim()).filter((c) => c.startsWith("sa_user."))[0];
146    if (!auth0Cookie) {
147      return null;
148    }
149    const cookieValue = auth0Cookie.split("=")[1];
150    const data = jwtDecode(cookieValue);
151    return data;
152  }
153
154  
154// src/sciads/utils/geo.js
155  var geoData;
156  async function getGeo() {
157    if (geoData) return geoData;
158    try {
159      geoData = await fetch("https://www.scientificamerican.com/api/geo/", {
160        headers: { Accept: "application/json" },
161        credentials: "omit"
162      }).then((res) => res.json());
163    } catch (e) {
164      console.warn("[sciads] [geo] Failed to fetch", e);
165    }
166    return geoData;
167  }
168  var gdprCountries = [
169    // GDPR/EU
170    "AUT",
171    "BEL",
172    "BGR",
173    "HRV",
174    "CYP",
175    "CZE",
176    "DNK",
177    "EST",
178    "FIN",
179    "FRA",
180    "DEU",
181    "GRC",
182    "HUN",
183    "IRL",
184    "ITA",
185    "LVA",
186    "LTU",
187    "LUX",
188    "MLT",
189    "NLD",
190    "POL",
191    "PRT",
192    "ROU",
193    "SVK",
194    "SVN",
195    "ESP",
196    "SWE",
197    "CHE",
198    // Switzerland
199    "UKR",
200    // Ukraine
201    "GBR"
202    // United Kingdom
203  ];
204
205  
205// src/sciads/plugins/ppid.js
206  var ppid;
207  function getPublisherProvidedId() {
208    return ppid;
209  }
210  async function hash(message) {
211    if (!message) return;
212    if (window.location.protocol !== "https:") {
213      log("[ads] insecure context, will not pass PPID because crypto hashing is unavailable");
214      return;
215    }
216    const encoder = new TextEncoder();
217    const data = encoder.encode(message);
218    const hashBuffer = await window.crypto.subtle.digest("SHA-256", data);
219    const hashArray = Array.from(new Uint8Array(hashBuffer));
220    const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
221    return hashHex;
222  }
223  async function publisherProvidedId() {
224    const geo = await getGeo();
225    if (geo?.country !== "USA") {
226      return;
227    }
228    if (isOptedOut()) {
229      return;
230    }
231    const user = getUserData();
232    const id = user?.["https://sciam.com/user_id"];
233    if (!id) return null;
234    ppid = await hash(id);
235    return ppid;
236  }
237
238  
238// src/sciads/gpt.js
239  window.googletag = window.googletag || {};
240  window.googletag.cmd = window.googletag.cmd || [];
241  var gptQueue = [];
242  gptQueue.push = (fn) => {
243    window.googletag.cmd.push(() => {
244      try {
245        fn();
246      } catch (e) {
247        console.log(e);
248      }
249    });
250  };
251  function parseSize(sizeStr) {
252    if (sizeStr === "fluid") {
253      return sizeStr;
254    }
255    let size = sizeStr.split("x").map((num) => parseInt(num, 10));
256    return size;
257  }
258  function defineSlot(gptAd) {
259    const slot = googletag.defineSlot(gptAd.unitpath, gptAd.getSizesForBreakpoint(), gptAd.id);
260    const responsiveSizeMapping = googletag.sizeMapping();
261    gptAd.breakpoints.forEach((breakpoint) => {
262      responsiveSizeMapping.addSize([breakpoint.width, 0], breakpoint.sizes.map(parseSize));
263    });
264    slot.defineSizeMapping(responsiveSizeMapping.build());
265    slot.setConfig({ targeting: omitFalsy(gptAd.targeting) });
266    slot.addService(googletag.pubads());
267    googletag.display(gptAd.id);
268    log(`[ads] GPT slot defined for ${gptAd.id}`, gptAd, slot);
269    return slot;
270  }
271  var omitFalsy = (obj) => Object.fromEntries(Object.entries(obj).filter(([_, value]) => value));
272  function setPageLevelTargeting() {
273    const targeting = getAdsConfig().targeting;
274    log(`[ads] GPT targeting set:`, targeting);
275    googletag.setConfig({ targeting });
276    log(`[ads] Targeting is now`, googletag.getConfig("targeting"));
277  }
278  function enableGptServices() {
279    log("[ads] GPT Services enabled");
280    googletag.setConfig({ singleRequest: true });
281    googletag.setConfig({ disableInitialLoad: true });
282    googletag.setConfig({ collapseDiv: "ON_NO_FILL" });
283    if (isOptedOut()) {
284      googletag.setConfig({
285        privacyTreatments: { treatments: ["disablePersonalization"] }
286      });
287    }
288    googletag.pubads().setPrivacySettings({
289      nonPersonalizedAds: isOptedOut(),
290      restrictDataProcessing: isOptedOut(),
291      limitedAds: isOptedOut()
292      // childDirectedTreatment: false, // @TODO: maybe set this if we can categorize institutions some day
293    });
294    const ppid2 = getPublisherProvidedId();
295    if (ppid2) {
296      googletag.pubads().setPublisherProvidedId(ppid2);
297      log("[ads] PPID enabled");
298    }
299    googletag.enableServices();
300  }
301  function bindGptEventHandlers() {
302    googletag.pubads().addEventListener("slotRequested", (event) => {
303      trackPerformanceFirstCall();
304      const gptAd = getGptAdById(event.slot.getSlotElementId());
305      gptAd.called = true;
306      gptAd.element.classList.add("is-called");
307    });
308    googletag.pubads().addEventListener("slotRenderEnded", (event) => {
309      trackPerformanceFirstLoad();
310      const gptAd = getGptAdById(event.slot.getSlotElementId());
311      gptAd.slotRenderedEvent = event;
312      const possibleClasses = [
313        // No way we got here without these
314        "is-called",
315        "is-registered",
316        "is-empty",
317        "is-loaded",
318        "is-custom-size",
319        "is-fluid-size",
320        "is-standard-size"
321      ];
322      let desiredClasses = ["is-registered", "is-called"];
323      desiredClasses.push(event.isEmpty ? "is-empty" : "is-loaded");
324      gptAd.loaded = true;
325      if (event.size) {
326        gptAd.size = event.size;
327        let size = event.size;
328        if (Array.isArray(size)) {
329          size = size.join("x");
330        }
331        if (IAB_SIZES.includes(size)) {
332          desiredClasses.push("is-standard-size");
333        } else if (size === "fluid") {
334          desiredClasses.push("is-fluid-size");
335        } else if (PREMIUM_SIZES.includes(size)) {
336          desiredClasses.push("is-premium-size");
337        } else {
338          desiredClasses.push("is-custom-size");
339        }
340      }
341      possibleClasses.forEach((cls) => {
342        if (desiredClasses.includes(cls)) {
343          gptAd.element.classList.add(cls);
344        } else {
345          gptAd.element.classList.remove(cls);
346        }
347      });
348      if (event.isEmpty) {
349        log(`[ads] ${gptAd.id}: rendered empty`);
350        return;
351      }
352      log(`[ads] ${gptAd.id}: loaded as ${event.size.join("x")}`);
353    });
354    googletag.pubads().addEventListener("impressionViewable", (event) => {
355      const gptAd = getGptAdById(event.slot.getSlotElementId());
356      gptAd.viewableImpression = true;
357      log(`[ads] ${gptAd.id}: viewable-impression`);
358    });
359  }
360  function callAds(gptAds2) {
361    if (!gptAds2.length) return;
362    log("[ads] called:", gptAds2.map((ad) => ad.id).join(", "));
363    const slots = gptAds2.map((gptAd) => gptAd.slot);
364    gptAds2.forEach((ad) => {
365      ad.called = true;
366    });
367    googletag.pubads().refresh(slots);
368  }
369
370  
370// src/sciads/observers.js
371  var OBSERVERS = {};
372  function bindLazyLoadIntersectionObserver(gptAd) {
373    if (!OBSERVERS["lazyload"]) {
374      const lazyLoadDistance = Math.max(window.innerHeight, 750);
375      OBSERVERS["lazyload"] = new IntersectionObserver(
376        (entries) => {
377          entries.map((entry) => {
378            const gptAd2 = getGptAdById(entry.target.id);
379            gptAd2.inLazyRange = entry.isIntersecting;
380          });
381        },
382        {
383          rootMargin: `0px 0px ${lazyLoadDistance}px 0px`
384        }
385      );
386    }
387    const observer = OBSERVERS["lazyload"];
388    observer.unobserve(gptAd.element);
389    observer.observe(gptAd.element);
390  }
391  function bindRefreshLoadIntersectionObserver(gptAd) {
392    if (!OBSERVERS["lazyload-200"]) {
393      OBSERVERS["refresh"] = new IntersectionObserver(
394        (entries) => {
395          entries.map((entry) => {
396            const gptAd2 = getGptAdById(entry.target.id);
397            gptAd2.inRefreshRange = entry.isIntersecting;
398          });
399        },
400        {
401          rootMargin: `0px 0px 0px 0px`
402        }
403      );
404    }
405    const observer = OBSERVERS["refresh"];
406    observer.unobserve(gptAd.element);
407    observer.observe(gptAd.element);
408  }
409  var BREAKPOINTS = [];
410  function handleCrossBreakpoint({ width }) {
411    const gptAds2 = getAllAds().filter((gptAd) => {
412      const matchingBreakpoint = gptAd.breakpoints.map((bp) => bp.width).includes(width);
413      return matchingBreakpoint && gptAd.called;
414    }
414).forEach((gptAd) => {
415      gptAd.reset();
416    });
417  }
418  function bindBreakpointObserver(gptAd) {
419    gptAd.breakpoints.forEach((bp) => {
420      if (BREAKPOINTS.includes(bp)) {
421        return;
422      }
423      BREAKPOINTS.push(bp);
424      window.matchMedia(`(min-width: ${bp.width}px)`).addEventListener("change", (e) => {
425        handleCrossBreakpoint({ width: bp.width });
426      });
427      log(`Registered ad breakpoint at ${bp.width}px`);
428    });
429  }
430
431  
431// src/sciads/debug.js
432  var params = new URLSearchParams(window.location.search);
433  var excludedSizes = void 0;
434  function debugExcludeSizes(sizes) {
435    if (sizes.length === 0) {
436      return;
437    }
438    if (excludedSizes === void 0) {
439      excludedSizes = (params.get("debug:ads-exclude") || "").split(",");
440      if (excludedSizes.length) {
441        log(`[ads] debug:ads-exclude active, filtering out sizes`, excludedSizes);
442      }
443    }
444    if (excludedSizes === null) {
445      return sizes;
446    }
447    sizes = sizes.filter((size) => {
448      return excludedSizes.indexOf(size) === -1;
449    });
450    if (sizes.length === 0) {
451      sizes.push("404x1");
452    }
453    return sizes;
454  }
455
456  
456// src/sciads/models.js
457  var adCounter = 0;
458  var gptAds = [];
459  var adsConfig = {
460    unitpath: "",
461    targeting: {}
462  };
463  function setKeyValues(params2) {
464    for (let key in params2) {
465      adsConfig.targeting[key] = params2[key];
466    }
467  }
468  function setAdsConfig(newConfig, reset = false) {
469    if (reset) {
470      adsConfig = newConfig;
471    } else {
472      Object.assign(adsConfig, newConfig);
473    }
474    if (!adsConfig.unitpath) {
475      console.error("unitpath was not set. Ads will not work.");
476    }
477  }
478  function getAdsConfig() {
479    return adsConfig;
480  }
481  var GptAd = class {
482    constructor(gptElement) {
483      gptElement.classList.add("is-registered");
484      this.element = gptElement;
485      this.getPropertiesFromElement();
486      this.setId();
487      this.slotRenderedEvent = null;
488      this.called = false;
489      this.loaded = false;
490      this.empty = false;
491      this.viewableImpression = false;
492      this.size = null;
493      gptQueue.push(() => {
494        this.slot = defineSlot(this);
495      });
496      this.inLazyRange = false;
497      this.inRefreshRange = false;
498      bindLazyLoadIntersectionObserver(this);
499      bindRefreshLoadIntersectionObserver(this);
500      bindBreakpointObserver(this);
501    }
502    /**
503     * Clear out the ad and make it eligible for refresh.
504     */
505    reset() {
506      const id = this.element.id;
507      this.element.removeAttribute("id");
508      this.element.classList.remove("is-registered");
509      gptAds.splice(gptAds.indexOf(this), 1);
510      log(`[ad] reset for ${id}`);
511      requestAnimationFrame(() => {
512        registerAds();
513      });
514    }
515    /**
516     * Convert HTML into data structure
517     */
518    getPropertiesFromElement() {
519      this.unitpath = adsConfig.unitpath;
520      if (this.element.getAttribute("unitpath")) {
521        this.unitpath = this.unitpath + "/" + this.element.getAttribute("unitpath");
522      }
523      const attributes = Object.values(this.element.attributes);
524      this.targeting = {};
525      attributes.filter((attr) => attr.name.startsWith("targeting-")).forEach((attr) => {
526        let key = attr.name.replace(/^targeting-/, "");
527        this.targeting[key] = attr.value;
528      });
529      this.breakpoints = [];
530      attributes.filter((attr) => attr.name.startsWith("sizes-from-")).forEach((attr) => {
531        let width = parseInt(attr.name.replace(/^sizes-from-/, ""), 10);
532        let sizes = attr.value.split(",");
533        sizes = debugExcludeSizes(sizes);
534        this.breakpoints.push({ width, sizes });
535      });
536      this.breakpoints = this.breakpoints.sort((a, b) => {
537        return a.width - b.width;
538      });
539    }
540    setId() {
541      adCounter++;
542      const template = this.element.getAttribute("id-format") || "gpt-unit-{}";
543      const id = template.replace("{}", adCounter);
544      this.id = id;
545      this.element.setAttribute("id", id);
546    }
547    getSizesForBreakpoint() {
548      let sizes = [];
549      for (let bp of this.breakpoints) {
550        if (bp.width <= window.innerWidth) {
551          sizes = bp.sizes;
552        }
553      }
554      return sizes;
555    }
556  };
557  function registerAds() {
558    [...document.querySelectorAll("gpt-ad:not(.is-registered)")].forEach((el) => {
559      let ad = new GptAd(el);
560      gptAds.push(ad);
561      log(`Registered <gpt-ad> for ${ad.id}`, ad);
562    });
563  }
564  function getGptAdById(id) {
565    return gptAds.find((ad) => {
566      return ad.id === id;
567    });
568  }
569  function getAllAds() {
570    return gptAds;
571  }
572
573  
573// src/sciads/utils/load.js
574  function loadScript(src, async = true) {
575    const script = document.createElement("script");
576    script.src = src;
577    if (async) script.setAttribute("async", "");
578    return new Promise((resolve, reject) => {
579      script.onload = resolve;
580      script.onerror = reject;
581      document.body.appendChild(script);
582    });
583  }
584
585  
585// src/sciads/plugins/mediatrust.js
586  function mediaTrust() {
587    const src = "https://scripts.webcontentassessor.com/scripts/4ca93f8138ee391320844a5f73486804166750feebd4d600d41de59ec8102b3d";
588    return loadScript(src);
589  }
590
591  
591// src/sciads/plugins/polar.js
592  function polar() {
593    const src = "https://cdn.mediavoice.com/nativeads/script/scientificamerican/pl-track.js";
594    const isSponsorContent = window.location.pathname.startsWith("/custom-media/");
595    if (isSponsorContent) {
596      loadScript(src);
597    }
598  }
599
600  
600// src/sciads/plugins/testing.js
601  function testingKeyValues() {
602    const params2 = new URLSearchParams(window.location.search);
603    let targeting = {};
604    [...params2.keys()].filter((key) => {
605      return key.startsWith("adkv_");
606    }).forEach((key) => {
607      let value = params2.getAll(key);
608      let tkey = key.replace("adkv_", "", 1);
609      targeting[tkey] = value;
610    });
611    setKeyValues(targeting);
612  }
613
614  
614// src/sciads/plugins/entitlements.js
615  function getStateKey(uid) {
616    return `_ad_user_state-${uid}`;
617  }
618  var state = {
619    features: []
620    // Chargebee
621  };
622  function loadState(uid) {
623    try {
624      let data = sessionStorage.getItem(getStateKey(uid));
625      if (data) {
626        state = JSON.parse(data);
627        return true;
628      }
629    } catch (e) {
630      console.error(e);
631    }
632    return false;
633  }
634  function userDataTargeting() {
635    return new Promise((resolve) => {
636      const user = getUserData();
637      if (!user) {
638        log(`[ads] user not logged in`);
639        state.resources = [];
640        setKeyValues({ entitlements: ["no"] });
641        return resolve();
642      }
643      const entitlements = user?.features ?? [];
644      if (entitlements) {
645        state.features = entitlements ?? [];
646        setKeyValues({ entitlements: entitlements ?? ["no"] });
647        return resolve();
648      }
649      const uid = user["https://sciam.com/user_id"];
650      const hasCachedState = loadState(uid);
651      if (hasCachedState) {
652        log(`[ads] using entitlements from session cache`, state);
653        resolve();
654      }
655    });
656  }
657  function getIsAdFree() {
658    const cb_adfree = (state.features || []).includes("ad-free-access");
659    return cb_adfree;
660  }
661
662  
662// src/sciads/plugins/upsapi.js
663  function upsapi() {
664    log("[ads] creating __uspapi");
665    window.__uspapi = function(command, versionRequested, callback) {
666      if (command !== "getUSPData") {
667        console.error(`Command <${command}> not implemented. Only <getUSPData> is supported.`);
668        return;
669      }
670      const version = 1;
671      const noticeGiven = "Y";
672      const optedOut = isOptedOut() ? "Y" : "N";
673      const limited_service_provider_agreement_member = "-";
674      callback(
675        {
676          version,
677          uspString: [version, noticeGiven, optedOut, limited_service_provider_agreement_member].join(
678            ""
679          )
680        },
681        true
682      );
683    };
684  }
685
686  
686// src/sciads/plugins/interests.js
687  function interests() {
688    if (isOptedOut()) return;
689    let travel = readCookie("sciam_interest-travel");
690    if (travel) {
691      setKeyValues({ travel: travel.split(",") });
692    }
693  }
694
695  
695// src/sciads/plugins/index.js
696  var PERF_KEYS2 = {
697    pluginsStart: "ads:plugins_start",
698    pluginsEnd: "ads:plugins_ready",
699    measurePlugins: "ads:measure_plugins"
700  };
701  async function runPlugins() {
702    performance.mark(PERF_KEYS2.pluginsStart);
703    await Promise.allSettled([
704      upsapi(),
705      testingKeyValues(),
706      userDataTargeting(),
707      mediaTrust(),
708      polar(),
709      publisherProvidedId(),
710      interests()
711    ]);
712    performance.mark(PERF_KEYS2.pluginsEnd);
713    performance.measure(PERF_KEYS2.measurePlugins, PERF_KEYS2.pluginsStart, PERF_KEYS2.pluginsEnd);
714    logMeasurement(PERF_KEYS2.measurePlugins, "Ads: plugins complete in");
715  }
716
717  
717// src/sciads/rendering.js
718  var _scrollPos = 0;
719  window.requestAnimationFrame(() => {
720    _scrollPos = window.scrollY;
721  });
722  function render() {
723    let previousScrollPos = _scrollPos;
724    _scrollPos = window.scrollY;
725    let scrollDelta = Math.abs(previousScrollPos - _scrollPos);
726    if (scrollDelta > 500) {
727      log("Scrolling too fast - ad render skipped");
728      return;
729    }
730    let adsToRender = getAllAds().filter((gptAd) => {
731      return !gptAd.called && gptAd.inLazyRange;
732    });
733    callAds(adsToRender);
734  }
735
736  
736// src/sciads/utils/testing.js
737  var abTestGroup;
738  function getABTestGroup() {
739    if (abTestGroup !== void 0) {
740      return abTestGroup;
741    }
742    abTestGroup = Math.floor(Math.random() * 10) + 1;
743    log(`[ads] ABT group ${abTestGroup}`);
744    setKeyValues({ abt: abTestGroup.toString() });
745    return abTestGroup;
746  }
747
748  
748// src/sciads/controller.js
749  async function initAdsLifeCycle() {
750    getABTestGroup();
751    registerAds();
752    const [geo] = await Promise.allSettled([getGeo(), getConsent()]);
753    let gptSrc = "https://securepubads.g.doubleclick.net/tag/js/gpt.js";
754    if (isOptedOut()) {
755      const gdprApplies = geo.value?.country ? gdprCountries.includes(geo.value.country) : window.Osano?.cm?.gdprApplies;
756      const debugGdpr = window.location.search.match(/debug=gdpr/);
757      if (gdprApplies || debugGdpr) return log(`[ads] GDPR opt-out, no ads`);
758      gptSrc = "https://pagead2.googlesyndication.com/tag/js/gpt.js";
759    }
760    loadScript(gptSrc).then(() => {
761      log("GPT loaded");
762      performance.mark(PERF_KEYS.gptLoaded);
763    });
764    await runPlugins();
765    if (getIsAdFree()) {
766      return log(`[ads] Premium user, no ads`);
767    }
768    gptQueue.push(() => {
769      performance.mark(PERF_KEYS.gptQueue);
770      log("Started GPT Queue");
771      setPageLevelTargeting();
772      bindGptEventHandlers();
773      enableGptServices();
774      render();
775      setInterval(() => {
776        registerAds();
777        render();
778      }, 250);
779    });
780  }
781
782  
782// src/sciads/commands.js
783  var COMMANDS = {
784    /**
785     * The "pagview" describes the page fo adtech
786     * purposes. This starts the lifecycle.
787     *
788     * @param {adsConfig} adsConfig
789     */
790    page: function(adsConfig2) {
791      performance.mark(PERF_KEYS.page);
792      setAdsConfig(adsConfig2, true);
793      initAdsLifeCycle();
794    },
795    get: function(id) {
796      console.log(getGptAdById(id));
797    }
798  };
799  function handleCommand(cmdArr) {
800    const [cmd, args] = cmdArr;
801    log(`SciAds command "${cmd}"`, args);
802    const callback = COMMANDS[cmd];
803    if (callback) {
804      callback(args);
805    }
806  }
807
808  
808// src/sciads.js
809  performance.mark(PERF_KEYS.libLoaded);
810  function createApi() {
811    window.__ads = window.__ads || [];
812    window.__ads.push = handleCommand;
813    window.__ads.forEach(handleCommand);
814  }
815  function init() {
816    log("Hello SciAds");
817    createApi();
818  }
819  init();
820})();
821//# sourceMappingURL=sciads.js.map

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.