PageSourceSearch

https://www.smartcitiesdive.com/static/js/divecounter.js?520919230926

js smartcitiesdive.com collected 2026-09-24 08:30:30 UTC 15,709 bytes, 454 lines download raw bytes

1function Divecounter() {
2  // Number of pages to visit until interstitial is displayed
3  const interstitialPageCount = 3;
4  // Number of days after the interstitial was last displayed before it can be displayed again
5  const interstitialMinDays = 3;
6  // Cookie names for the three-cookie split (Osano compliance)
7  const coreCookieName = '_divecounter_core';
8  const personalizationCookieName = '_divecounter_personalization';
9  const marketingCookieName = '_divecounter_marketing';
10  const cookieExpire = 365;
11  // Core data (cookie classified in Osano as "Essential")
12  let coreData = {
13    page_views: 0,
14    last_shown: 0,
15    first_page_view: 0,
16    is_staff: false,
17  };
18  // Personalization data (cookie classified in Osano as "Personalization")
19  let personalizationData = {
20    is_subscriber: false,
21  };
22  // Marketing data (cookie classified in Osano as "Marketing")
23  let marketingData = {
24    is_subscriber: false,
25  };
26
27  this.testing = function() {
28    // Disable interstitial testing params if configured (e.g., for WardsAuto to prevent bot signups)
29    if (this.getOption('disableInterstitialTestingParams')) {
30      return false;
31    }
32    return location.search.indexOf('show_hit_ad') !== -1 || location.search.indexOf('show_interstitial') !== -1;
33  };
34
35  this.resetCookie = function() {
36    // Expiration in past deletes cookies.
37    setCookie(coreCookieName, '', -100);
38    setCookie(personalizationCookieName, '', -100);
39    setCookie(marketingCookieName, '', -100);
40    // Also delete old _divecounter cookie if it exists. TODO: remove refs to old _divecounter cookie after 5/1/2027
41    setCookie('_divecounter', '', -100);
42  };
43
44  this.countPageViews = function() {
45    if (coreData.page_views === 0) {
46      coreData.first_page_view = (new Date()).getTime();
47    }
48    coreData.page_views++;
49    return coreData.page_views;
50  };
51
52  this.canShow = function(pageCount = interstitialPageCount, minDays = interstitialMinDays) {
53    // Check personalization data to see if user is a subscriber
54    if (personalizationData.is_subscriber) {
55      return false;
56    }
57    // Interstitial is displayed after 'pageCount' pages, no less than 'minDays' days from the last time it was displayed
58    if (coreData.page_views > 0 && coreData.page_views % pageCount === 0) {
59      if (!coreData.last_shown || (new Date()).getTime() - coreData.last_shown >= minDays * 24 * 60 * 60 * 1000) {
60        return true;
61      }
62    }
63    return false;
64  };
65
66  this.markShown = function() {
67    coreData.last_shown = (new Date()).getTime();
68  };
69
70  // Helper function to check Osano consent state
71  const hasOsanoConsent = function(category) {
72    // If Osano isn't loaded, conservatively assume no consent
73    // Optional chaining returns undefined if any part of the chain is undefined
74    const consent = window.Osano?.cm?.getConsent?.();
75    // Osano uses 'ACCEPT' / 'DENY' strings, not booleans
76    return consent?.[category] === 'ACCEPT';
77  };
78
79  this.markSubscriber = function(valueIn) {
80    // Typecast to bool with default value of true.
81    const value = (typeof valueIn === 'undefined') ? true : !!valueIn;
82
83    // Only set personalization if user has consented to Personalization
84    if (hasOsanoConsent('PERSONALIZATION')) {
85      personalizationData.is_subscriber = value;
86    } else {
87      personalizationData.is_subscriber = false;
88    }
89
90    // Only set marketing if user has consented to Marketing
91    if (hasOsanoConsent('MARKETING')) {
92      marketingData.is_subscriber = value;
93    } else {
94      marketingData.is_subscriber = false;
95    }
96  };
97
98  this.markStaff = function(valueIn) {
99    // Typecast to bool with default value of true.
100    const value = (typeof valueIn === 'undefined') ? true : !!valueIn;
101    coreData.is_staff = value;
102  };
103
104  this.save = function() {
105    writeCoreData();
106    writePersonalizationData();
107    writeMarketingData();
108  };
109
110  // Accessor methods for specific cookie data
111  this.getCoreData = function() {
112    return coreData;
113  };
114
115  this.getPersonalizationData = function() {
116    return personalizationData;
117  };
118
119  this.getMarketingData = function() {
120    return marketingData;
121  };
122
123  this.getOption = function(key) {
124    const defaults = {
125      showSignupAd: false,
126      showWhichAd: false, /** "original" or "choice" **/
127      markStaff: false,
128    };
129    if ( (typeof divecounter_settings !== 'undefined') && (key in divecounter_settings) ) {
130      return divecounter_settings[key];
131    }
132    return defaults[key];
133  };
134
135  // The purpose of this method is to make debugging easier by printing a string that consists of
136  // cookie values setup in `divecounter.js` and screen dimensions
137  this.prettyPrint = function() {
138    let s = '';
139
140    // Core data
141    for (const core_key in coreData) {
142      if (!coreData.hasOwnProperty(core_key)) continue;
143      let core_value = coreData[core_key];
144      // Check for keys to convert to date strings.
145      if (['first_page_view', 'last_shown'].indexOf(core_key) >= 0) {
146        if (parseInt(core_value, 10) > 0) {
147          core_value = (new Date(parseInt(core_value, 10))).toString(); // JS timestamp to string.
148        } else {
149          core_value = '';
150        }
151      }
152      s += core_key + ': ' + core_value + '\n';
153    }
154
155    // Personalization data
156    s += 'is_subscriber (personalization): ' + personalizationData.is_subscriber + '\n';
157
158    // Marketing data
159    s += 'is_subscriber (marketing): ' + marketingData.is_subscriber + '\n';
160
161    // Add in bonus stats not technically part of divecounter data.
162    s += 'initial referrer: ' + getCookie('_referrer') + '\n';
163    s += 'screen dimensions: ' + window.screen.availWidth.toString() + 'x' + window.screen.availHeight.toString() + '\n';
164
165    return s.trim();
166  };
167
168  const setCookie = function(c_name, value, expiredays) {
169    const exdate = new Date();
170    exdate.setDate(exdate.getDate() + expiredays);
171    document.cookie = c_name + '=' + escape(value) +
172            ((expiredays === null) ? '' : ';expires=' + exdate.toUTCString()) + '; path=/';
173  };
174
175  const getCookie = function(c_name) {
176    let c_start;
177    let c_end;
178    if (document.cookie.length > 0) {
179      c_start = document.cookie.indexOf(c_name + '=');
180      if (c_start !== -1) {
181        c_start = c_start + c_name.length + 1;
182        c_end = document.cookie.indexOf(';', c_start);
183        if (c_end === -1) c_end = document.cookie.length;
184        return unescape(document.cookie.substring(c_start, c_end));
185      }
186    }
187    return '';
188  };
189
190  // Core cookie read/write functions
191  const readCoreData = function() {
192    const c = getCookie(coreCookieName);
193    if (c !== '') {
194      const arr = c.split('\t');
195      coreData.page_views = arr[0];
196      coreData.last_shown = arr[1];
197      if (arr.length > 2) {
198        coreData.first_page_view = arr[2];
199      }
200      if (arr.length > 3) {
201        coreData.is_staff = !!parseInt(arr[3], 10);
202      }
203    }
204  };
205
206  const buildCoreCookie = function() {
207    const arr = [
208      coreData.page_views,
209      coreData.last_shown,
210      coreData.first_page_view,
211      coreData.is_staff ? 1 : 0,
212    ];
213    return arr.join('\t');
214  };
215
216  const writeCoreData = function() {
217    setCookie(coreCookieName, buildCoreCookie(), cookieExpire);
218  };
219
220  // Personalization cookie read/write functions
221  const readPersonalizationData = function() {
222    const c = getCookie(personalizationCookieName);
223    if (c !== '') {
224      personalizationData.is_subscriber = !!parseInt(c, 10);
225    }
226  };
227
228  const writePersonalizationData = function() {
229    setCookie(personalizationCookieName, personalizationData.is_subscriber ? '1' : '0', cookieExpire);
230  };
231
232  // Marketing cookie read/write functions
233  const readMarketingData = function() {
234    const c = getCookie(marketingCookieName);
235    if (c !== '') {
236      marketingData.is_subscriber = !!parseInt(c, 10);
237    }
238  };
239
240  const writeMarketingData = function() {
241    setCookie(marketingCookieName, marketingData.is_subscriber ? '1' : '0', cookieExpire);
242  };
243
244  // Auto-load all cookies on initialization
245  readCoreData();
246  readPersonalizationData();
247  readMarketingData();
248
249  // Deleting old _divecounter cookie on first load. TODO: Remove any _divecounter cookie code after 5/1/2027
250  if (getCookie('_divecounter') !== '') {
251    setCookie('_divecounter', '', -100);
252  }
253}
254
255
256function divecounter_close_ad() {
257  $.modal.close();
258  // adds back in scrolling to body
259  $('body').removeClass('no-scroll');
260}
261
262function isAtLeastOneNewsletterSelected() {
263  return $('#newsletter-list-section li input:checked').length > 0;
264}
265
266function isSignupWithMultiPub() {
267  return $('#newsletter-list-section li input').length > 0;
268}
269
270function isNewsletterSelectionValid() {
271  return isSignupWithMultiPub() ? isAtLeastOneNewsletterSelected() : true;
272}
273
274function isUserConsentChecked() {
275  return $('#interstitial-consent-container input:checked').length > 0;
276}
277
278function windowLargeEnough() {
279  return $(window).width() > 640 && $(window).height() > 640;
280}
281
282function divecounter_open_ad() {
283  // Figure out if window is wide/high enough to show a prestitial
284  if (windowLargeEnough()) {
285    $(function() {
286      var $form = $('#signup-inter').find('form');
287      $form.find('.newsletter-partner').hide(); // hide all partnership info, see #1029
288      var current_dialog;
289      $('#signup-inter').modal({
290        minWidth: 400,
291        maxWidth: 600,
292        minHeight: 180,
293        autoResize: true,
294        autoPosition: true,
295        overlayClose: false,
296        focus: false,
297        opacity: 80,
298        closeHTML: `<a href="#" title="${window.pgettext('verb', 'Close')}" class="modal-close"></a>`,
299        onClose: divecounter_close_ad,
300        // force resize of box. See https://stackoverflow.com/questions/1407059/jquery-simplemodal-dynamic-height
301        onShow: function(dialog) {
302          $('#interstitial-error').hide();
303          current_dialog = dialog;
304          // prevents scrolling on body when modal is open
305          $('body').addClass('no-scroll');
306          dialog.container.css('height', 'auto');
307          $.modal.setContainerDimensions();
308          $.modal.setPosition();
309        },
310      });
311
312
313      // add email checking -- see email_check_js.html
314      $form.find('input.email').on('blur', function() {
315        checkEmailAndRespond(this);  // eslint-disable-line no-undef
316      });
317
318      $form.find('input.email').focus();
319      if (current_dialog !== 'undefined') {
320        $(current_dialog.container).css('height', 'auto');
321      }
322
323      // Validation for submit.
324      $('#signup-inter form').submit(function(event) {
325        var email_regex =  new RegExp('[^@]+@[^@]+[\.][^@]+');
326        var email_value = $(this.email).val();
327        var match_test = email_regex.test(email_value);
328        var $interstitialError = $('#interstitial-error');
329        var error_msg = '';
330
331        $interstitialError.hide();
332        $(this).find('input[type=email]').removeClass('form-error__highlight');
333
334        if (match_test && isNewsletterSelectionValid() && isUserConsentChecked()) {
335          return true;
336        } else if (!match_test) {
337          error_msg = window.gettext('That is not a valid email address. Please enter a valid email.');
338          $(this).find('input[type=email]').addClass('form-error__highlight');
339        } else if (!isNewsletterSelectionValid() && !isUserConsentChecked()) {
340          error_msg = window.gettext('You must select at least one newsletter and agree to our Terms of Use and Privacy Policy.');
341        } else if (!isUserConsentChecked()) {
342          error_msg = window.gettext('You must agree to our Terms of Use and Privacy Policy.');
343        } else if (!isNewsletterSelectionValid()) {
344          error_msg = window.gettext('Please select at least one newsletter.');
345        }
346
347        $interstitialError.text(error_msg);
348        $interstitialError.show();
349        // force resize of box. See https://stackoverflow.com/questions/1407059/jquery-simplemodal-dynamic-height
350        $('#simplemodal-container').css({
351          'height': 'auto',
352          'max-height': '90%',
353        });
354        // Make sure the content is scrollable if it exceeds the container height
355        $('.simplemodal-wrap').css('overflow', 'scroll');
356        $.modal.setContainerDimensions();
357        $.modal.setPosition();
358        event.preventDefault();
359        return false;
360      });
361
362      // wait a moment and then allow user to close by clicking on the background
363      setTimeout(function() { $('.simplemodal-overlay').click($.modal.close); }, 750);
364    });
365  }
366}
367
368var divecounter = new Divecounter();
369
370divecounter.countPageViews();
371
372if (window.location.search.indexOf('utm_source=Sailthru') !== -1) {
373  // if they came from a newsletter, assume they are a subscriber
374  divecounter.markSubscriber();
375}
376
377if (divecounter.getOption('markStaff')) {
378  divecounter.markStaff();
379}
380
381if (divecounter.getOption('showSignupAd') || divecounter.testing()) {
382  if (divecounter.canShow() || divecounter.testing()) {
383    // Use a 50 ms interval to detect whether prestitial.js has determined whether the
384    // page is prestitial eligible or if the prestitial ad was empty. Interstitial should display only if
385    // prestitial is not displaying - prestitial should always have preference.
386    var prestitial_sniffer = setInterval(function() {
387      if (typeof prestitial_ad_is_empty === 'undefined' || prestitial_ad_is_empty === true) {  // eslint-disable-line no-undef
388        clearInterval(prestitial_sniffer);
389        divecounter_open_ad();
390        // Don't set cookies if we're testing
391        if (!divecounter.testing()) {
392          divecounter.markShown();
393          divecounter.save();
394        }
395      } else if (typeof prestitial_ad_is_loaded !== 'undefined' && prestitial_ad_is_loaded === true) {  // eslint-disable-line no-undef
396        // If the prestitial loads, we can stop sniffing
397        clearInterval(prestitial_sniffer);
398        clearInterval(window.prestitial_sniffer_clear);
399      }
400      // Make sure prestitial_ads exists and we only set the timeout once
401      if (typeof window.prestitialAds !== 'undefined' && typeof window.prestitial_sniffer_clear === 'undefined') {
402        // Stop looking for the prestitial after the prescribed timeout + 1 second of flex
403        window.prestitial_sniffer_clear = setTimeout(
404          function() {
405            clearInterval(prestitial_sniffer);
406          },
407          3900  // this number is 2900 from prestitial.html + 1000
408        );  // eslint-disable-line no-undef
409      }
410    }, 50);
411  }
412}
413
414divecounter.save();
415
416//
417// Following is not actually stored in divecounter cookier (yet)
418//
419
420// Save initial referrer
421(function() {
422  function createCookie(name, value, days) {
423    if (days) {
424      var date = new Date();
425      date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
426      var expires = '; expires=' + date.toGMTString();
427    } else {
428      var expires = '';  // eslint-disable-line no-redeclare
429    }
430    document.cookie = name + '=' + value + expires + '; path=/';
431  }
432
433  function readCookie(name) {
434    var nameEQ = name + '=';
435    var ca = document.cookie.split(';');
436    for (var i = 0; i < ca.length; i++) {
437      var c = ca[i];
438      while (c.charAt(0) === ' ') c = c.substring(1, c.length);
439      if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
440    }
441    return null;
442  }
443
444  // save referrer if doesn't already exist
445  if (!readCookie('_referrer')) {
446    // get referring page unless it's unavailable, then grab current (i.e. first) page
447    var referrer = document.referrer ? document.referrer : window.location;
448    // limit length
449    if (referrer.length > 150) {
450      referrer = referrer.slice(0, 150);
451    }
452    createCookie('_referrer', referrer);
453  }
454})();

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.