PageSourceSearch

https://www.hello.vote/js/core.js?c=1560456347

js hello.vote collected 2026-10-03 21:20:26 UTC 28,706 bytes, 936 lines download raw bytes

1/**
2 *
3 * @source: https://github.com/fightforthefuture/eunetneutrality
4 *
5 * @licstart  The following is the entire license notice for the
6 *  JavaScript code in this page.
7 *
8 * Copyright (C) Fight for the Future
9 *
10 *
11 * The JavaScript code in this page is free software: you can
12 * redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GNU GPL) as published by the Free Software
14 * Foundation, either version 3 of the License, or (at your option)
15 * any later version.  The code is distributed WITHOUT ANY WARRANTY;
16 * without even the implied warranty of MERCHANTABILITY or FITNESS
17 * FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.
18 *
19 * As additional permission under GNU GPL version 3 section 7, you
20 * may distribute non-source (e.g., minimized or compacted) forms of
21 * that code without the copy of the GNU GPL normally required by
22 * section 4, provided you include this license notice and a URL
23 * through which recipients can access the Corresponding Source.
24 *
25 * @licend  The above is the entire license notice
26 * for the JavaScript code in this page.
27 *
28 */
29
30/**
31 *  util : random grab bag functions
32 */
33
34var $c  = document.createElement.bind(document);
35var $el = document.getElementById.bind(document);
36
37if (!util) var util = {};
38
39util.getReferrerTag = function() {
40  var ref = document.referrer;
41  if (ref.indexOf('facebook.com') !== -1)
42    return 'from-facebook';
43  else if (ref.indexOf('twitter.com') !== -1 || ref.indexOf('t.co') !== -1)
44    return 'from-twitter';
45  else if (ref.indexOf('reddit.com') !== -1)
46    return 'from-reddit';
47  else if (window.location.href.indexOf('_src=ga') !== -1)
48    return 'from-google-adwords';
49  else if (ref.indexOf('google.com') !== -1)
50    return 'from-google';
51};
52
53util.parseQueryString = function () {
54  var
55    i,
56    pairs,
57    queryObject = {},
58    queryString = window.location.search;
59
60  if (queryString[0] === '?') {
61    queryString = queryString.substr(1);
62  }
63
64  pairs = queryString.split('&');
65  i = pairs.length;
66
67  while (i--) {
68    queryObject[pairs[i].split('=')[0]] = pairs[i].split('=')[1];
69  }
70
71  // Remove extra key
72  delete queryObject[''];
73
74  return queryObject;
75};
76
77
78window.components = window.components || {};
79window.components.chat = function (doc, win) {
80  /**
81   * Retrieves petition data from Action Network API, then submits signature
82   * @param {object} doc - Document object
83   * @param {object} win - Window object
84   * */
85  "use strict";
86
87  var overlay       = doc.querySelector('.chat'),
88      chatbox       = doc.getElementById('messages'),
89      form          = doc.querySelector('.chat form'),
90      share         = doc.querySelector('.chat .share'),
91      input         = form.querySelector('input'),
92      phoneNumber   = null,
93      failures      = 0,
94      partner       = 'fftf',
95      queryString   = util.parseQueryString(),
96      isInTest      = ('isInTest' in queryString) || (document.location.pathname.indexOf('test') > -1),
97      isInMyPage    = ('isInMyPage' in queryString),
98      isInStudio    = ('isInStudio' in queryString),
99      isInCustom    = document.body.classList.contains('my'),
100      isInSafeMode  = isInStudio || isInCustom,
101      hueShift      = false,
102      brightness    = false;
103
104
105  var bubble = function(sender, html, events, textOnly) {
106    events || (events = []);
107
108    var dots = chatbox.querySelectorAll('.dots');
109
110    for (var i = 0; i < dots.length; i++)
111      chatbox.removeChild(dots[i]);
112
113    var message = $c('div'),
114        clear = $c('div');
115
116    clear.className = 'clear';
117
118    message.className = 'message minimized '+sender;
119
120    if (!textOnly)
121      message.innerHTML = html;
122    else
123      message.textContent = html;
124
125    if (hueShift)
126      doHueShift(message, hueShift, brightness);
127
128    chatbox.appendChild(message);
129    chatbox.appendChild(clear);
130
131    var links = message.querySelectorAll('a');
132
133    for (var i = 0; i < events.length; i++) {
134      if (i > links.length - 1)
135        break;
136
137      links[i].addEventListener('click', events[i]);
138    }
139
140    scrollToBottom();
141
142    setTimeout(function() {
143      message.classList.remove('minimized');
144    }, 20);
145
146  }
147
148  var scrollToBottom = function() {
149    chatbox.scrollTop = chatbox.scrollHeight;
150  }
151
152  var dots = function() {
153    var dots = $c('div');
154
155    for (var i = 0; i < 3; i++) {
156      var dot = $c('div'),
157          lol = $c('div');
158
159      dot.appendChild(lol);
160      dots.appendChild(dot);
161    }
162
163    dots.className = 'dots';
164
165    if (hueShift)
166      doHueShift(dots, hueShift, brightness);
167
168    chatbox.appendChild(dots);
169
170    scrollToBottom();
171  }
172
173  var validatePhone = function(num) {
174
175    num = num.replace(/\s/g, '').replace(/\(/g, '').replace(/\)/g, '');
176    num = num.replace("+", "").replace(/\-/g, '');
177
178    if (num.charAt(0) == "1")
179      num = num.substr(1);
180
181    if (num.length != 10)
182      return false;
183
184    return num;
185  }
186
187  var sendPhoneNumber = function() {
188    var data = {
189      "type":"web",
190      "recipients":[
191        {"username": phoneNumber}
192      ],
193      "options": {
194        "locale": iso,
195      },
196      "partner": partner
197    };
198    console.log('sending', data);
199
200    var submission = new XMLHttpRequest();
201    if (isInTest) {
202      // get conversation type from querystring
203      data['chain'] = queryString.conv || 'vote_1';
204      submission.open('POST', 'https://votebot-api-staging.herokuapp.com/conversations/', true);
205    } else {
206      submission.open('POST', 'https://votebot-api.herokuapp.com/conversations/', true);
207    }
208    submission.setRequestHeader("Content-Type", "application/json");
209    submission.send(JSON.stringify(data));
210    if (_paq) { _paq.push(['trackGoal', 1]); }
211
212    setTimeout(function() {
213      bubble('bot', l10n['CHECK_YOUR_PHONE']);
214      showShareForm();
215    }, 4000);
216
217    setTimeout(function() {
218      if (modal) modal.show('overlay1');
219    }, 4000);
220  }
221
222  var showShareForm = function() {
223    form.classList.add('hidden');
224
225    share.style.display = 'block';
226    void share.offsetWidth; // Layout
227    share.style.opacity = 1;
228
229    if (isInMyPage) {
230      top.postMessage('show-share-form', 'https://my.hello.vote');
231    }
232  }
233
234  var animationTimeouts = [];
235
236  var cancelAnimations = function() {
237    document.querySelector('#messages').textContent = '';
238    animationTimeouts.forEach(clearTimeout);
239  };
240
241  var initialAnimations = function() {
242    cancelAnimations();
243    function text(message, textOnly) {
244      return function() {
245        bubble('bot', message, false, textOnly);
246      };
247    }
248
249    dots();
250
251    var messages = window.speechBubbles.slice();
252
253    if (isInSafeMode) {
254      messages = messages.concat([
255        'I can help you register to vote, remember to vote, and remind your friends to vote too.',
256        'Try me out! Enter your phone number to start<span class="mobileOnly">, or <a href="https://m.me/hellovote">chat on Facebook Messenger</a></span>.',
257      ]);
258    }
259
260    animationTimeouts.push(setTimeout(text(messages.shift(), isInSafeMode), 1000));
261    animationTimeouts.push(setTimeout(dots, 1100));
262
263    var delay = 2500;
264    messages.forEach(function(message, i) {
265      var isFinal = (i === messages.length - 1);
266      var textOnly = isInSafeMode && !isFinal;
267      animationTimeouts.push(setTimeout(text(message, textOnly), delay));
268      delay += 500;
269
270      // Show dots, if there are more messages
271      if (isFinal) {
272        return;
273      }
274
275      animationTimeouts.push(setTimeout(dots, delay));
276      delay += 500;
277    });
278  }
279
280  form.addEventListener('submit', function(e) {
281    e.preventDefault();
282
283    if (!input.value)
284      return input.focus();
285
286    bubble('user', input.value, [], true);
287
288    if (!phoneNumber) {
289      var _tmpPhoneNumber = validatePhone(input.value)
290      if (_tmpPhoneNumber) {
291        phoneNumber = _tmpPhoneNumber;
292
293        setTimeout(function() { dots() }, 500);
294
295        sendPhoneNumber();
296
297      } else {
298        setTimeout(function() { dots() }, 500);
299        setTimeout(function() {
300          failures++;
301
302          var msg = 'Sorry, I didn\'t recognize that phone number. Please try again!'
303
304          if (failures > 1)
305            msg = 'I\'m still having trouble. Make sure you enter a U.S. phone number.';
306
307          bubble('bot', msg);
308
309          input.focus();
310        }, 1500);
311      }
312    }
313
314    input.placeholder = '';
315    input.value = '';
316  });
317
318  var localize = function() {
319    overlay.querySelector('input').placeholder = l10n['ENTER_PHONE'];
320    overlay.querySelector('button').textContent = l10n['TEXT_ME'];
321
322    if (window.partner && !window.partner.no_disclosure) {
323      overlay.querySelector('.disclosure em').innerHTML = l10n['DISCLOSURE_PARTNER'];
324      overlay.querySelector('.disclosure em a.partner').textContent = window.partner.name;
325      overlay.querySelector('.disclosure em a.partner').href = window.partner.link;
326      if (window.partner.extra_disclosure) {
327        overlay.querySelector('.disclosure em').innerHTML += window.partner.extra_disclosure;
328      }
329    } else {
330        overlay.querySelector('.disclosure em').innerHTML = l10n['DISCLOSURE'];
331    }
332    // update shortcode keyword
333    if (document.querySelector('section span.sms-keyword') && window.partner && window.partner.keyword) {
334      document.querySelector('section span.sms-keyword').textContent = window.partner.keyword.toUpperCase();
335    }
336
337    overlay.querySelector('.facebook').textContent = l10n['SHARE'];
338    overlay.querySelector('.twitter').textContent = l10n['TWEET'];
339    overlay.querySelector('.donate').textContent = l10n['DONATE'];
340
341    // JL TODO ~ may need to customize on a per-page basis outside of l10n structure
342    overlay.querySelector('.disclosure a.learn-more').textContent = l10n['LEARN_MORE'];
343
344    var poweredBy = overlay.querySelector('.powered-by strong');
345    if (poweredBy)
346      poweredBy.textContent = l10n['POWERED_BY'];
347  }
348
349  var doHueShift = function(elem, degree, brightness) {
350    elem.style["filter"] = 'hue-rotate(' + degree + 'deg) brightness('+brightness+'%)';
351    elem.style["-webkit-filter"] = 'hue-rotate(' + degree + 'deg) brightness('+brightness+'%)';
352    elem.style["-moz-filter"] = 'hue-rotate(' + degree + 'deg) brightness('+brightness+'%)';
353    elem.style["-ie-filter"] = 'hue-rotate(' + degree + 'deg) brightness('+brightness+'%)';
354    elem.style["-o-filter"] = 'hue-rotate(' + degree + 'deg) brightness('+brightness+'%)';
355  }
356
357  var determinePartner = function() {
358    var metaTag = doc.querySelector('meta[name="hellovote:partner"]')
359
360    if (queryString.partner)
361      return partner = queryString.partner;
362
363    if (metaTag && metaTag.content)
364      return partner = metaTag.content;
365  }
366
367  var prefillPhone = function() {
368    if (queryString.phone) {
369      overlay.querySelector('input').value = queryString.phone;
370    }
371  }
372
373  var iframeHandler = function() {
374    if (doc.body.className.indexOf('iframe') === -1)
375      return;
376
377    var learnMore = overlay.querySelector('.disclosure a.learn-more');
378    if (window.partner) {
379      learnMore.href = 'https://www.hello.vote/'+window.partner.slug.toLowerCase()+'/#what-is-hellovote';
380    } else {
381      learnMore.href = 'https://www.hello.vote#what-is-hellovote';
382    }
383    learnMore.target = '_blank';
384
385    if (isInMyPage) {
386      doc.body.classList.add('my-page');
387    } else {
388      customizeColors();
389    }
390  }
391
392
393
394  var parseColor = function(str) {
395    if (str.match('rgb') || str.match('#')) {
396      return unescape(str);
397    } else {
398      return '#' + unescape(str);
399    }
400  }
401
402  var customizeColors = function(newHueShift, newBrightness, newDisclosureColor, newTextColor) {
403    if (newHueShift || queryString.hueShift) {
404      hueShift = newHueShift || queryString.hueShift;
405      brightness = 100;
406      if (newBrightness || queryString.brightness) {
407        brightness = newBrightness || queryString.brightness
408      }
409      doHueShift(form, hueShift, brightness);
410
411      var dots = doc.querySelector('.dots');
412      if (dots) {
413        doHueShift(dots, hueShift, brightness);
414      }
415
416      var disclosureLinks = overlay.querySelectorAll('.disclosure a, .disclosure img');
417      for (var i=0; i<disclosureLinks.length; i++) {
418        doHueShift(disclosureLinks[i], hueShift, brightness);
419      }
420
421      var logo = document.querySelector('.logo');
422      doHueShift(logo, hueShift, brightness);
423    }
424
425    if (newTextColor || queryString.textColor) {
426      var color = parseColor(newTextColor || queryString.textColor);
427      overlay.querySelector('.message').style.color = color;
428      overlay.querySelector('button[type="submit"]').style.color = color;
429    }
430
431    if (newDisclosureColor || queryString.disclosureColor) {
432      var color = parseColor(newDisclosureColor || queryString.disclosureColor);
433      overlay.querySelector('.disclosure').style.color = color;
434    }
435  }
436
437  localize();
438  determinePartner();
439  prefillPhone();
440  iframeHandler();
441  if (isInSafeMode) {
442    // Allow for live updates
443    window.initialAnimations = initialAnimations;
444    window.customizeColors = customizeColors;
445  } else {
446    initialAnimations();
447  }
448
449};
450
451window.components = window.components || {};
452window.components.petitions = function (doc, win) {
453  /**
454   * Retrieves petition data from Action Network API, then submits signature
455   * @param {object} doc - Document object
456   * @param {object} win - Window object
457   * */
458  "use strict";
459
460  var
461    body = doc.getElementsByTagName('body')[0],
462    petitionSignatureForm = doc.getElementById('petition-form'),
463    apiHost = petitionSignatureForm.dataset.host,
464    objectIdentifier = petitionSignatureForm.dataset.petitionId,
465    submitButton = body.querySelector('[type="submit"]'),
466    countryInput = doc.getElementById('hidden-country'),
467    countrySelect = doc.getElementById('select-country'),
468    countryLabel = doc.querySelector('[for="select-country"]'),
469    queryString = win.util.parseQueryString();
470
471  function numberCommafier(number) {
472    /**
473     * Returns a string representing a number with commas
474     * @param {int} number - the number to transform
475     * */
476
477    return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
478  }
479
480  function progressBar(targetValue, targetGoal) {
481    /**
482     * Animates the value of a progress bar
483     * @param {int} targetValue - the target value attribute of the progress bar
484     * @param {int} targetGoal - the target max attribute of the progress bar
485     * */
486
487    var
488      guardedTargetVal = targetValue.isNaN ? 0 : targetValue,
489      animate,
490      value = 0,
491      progressbar = doc.getElementById('signatures-progress-bar'),
492      max = parseInt(targetGoal),
493      step = (guardedTargetVal / 1500) * 30; // 1500 ms total, 30ms minimum
494                                             // interval
495
496    if (!progressbar)
497      return false;
498
499    function loading() {
500      value += step;
501      value = Math.ceil(value);
502      if (value >= guardedTargetVal || value >= max) {
503        value = guardedTargetVal;
504        clearInterval(animate);
505      }
506
507      var
508        commafiedNumber = numberCommafier(value);
509
510      progressbar.setAttribute('max', max.toString(10));
511      progressbar.setAttribute('value', value.toString(10));
512      progressbar.setAttribute('title', commafiedNumber + ' signatures');
513      doc.getElementById('total-sigs').textContent = commafiedNumber;
514      doc.getElementById('sigs-to-go').textContent = numberCommafier(targetGoal - value) + ' needed to reach ' + numberCommafier(targetGoal);
515      progressbar.textContent = commafiedNumber + ' signatures';
516    }
517
518    animate = setInterval(function () {
519      loading();
520    }, 30);
521  }
522
523  function handleProgressBarError() {
524    /**
525     * Sets values for the progress bar even if there's a server or XHR error
526     * */
527    progressBar(1519, 2500);
528  }
529
530  function requestAPIInfo() {
531    /**
532     * Builds and sends request to API server
533     * */
534    var
535      apiData,
536      anRequest = new XMLHttpRequest();
537
538    anRequest.open('GET', apiHost + '/petition?identifier=' + objectIdentifier, true);
539    anRequest.addEventListener('load', function () {
540      if (anRequest.status >= 200 && anRequest.status < 400) {
541
542          apiData = JSON.parse(anRequest.responseText);
543
544        progressBar(apiData.signatures, apiData.goal);
545
546        // remove this after save-chelsea has > 1000 signatures
547        if (win.location.hostname === 'www.freechelsea.com' && apiData.signatures < 1000) {
548          doc.querySelector('.signatures').style.display = 'none';
549        }
550
551      } else {
552        handleProgressBarError();
553      }
554    });
555    anRequest.addEventListener('error', handleProgressBarError);
556    anRequest.send();
557  }
558
559  function updateZIPPlaceholder() {
560    /**
561     * Updates placeholder on ZIP/Post Code field to be appropriate for country
562     * selected
563     * */
564    var
565      ZIPLabel = doc.getElementById('form-zip_code');
566
567    if (countrySelect.value !== 'US') {
568      ZIPLabel.setAttribute('placeholder', 'Post Code');
569    } else {
570      ZIPLabel.setAttribute('placeholder', 'ZIP');
571    }
572  }
573
574  function toggleCountryField() {
575    /**
576     * Hides the label and shows the select when someone changes their signature
577     * country.
578     * */
579
580    countryInput.parentNode.removeChild(countryInput);
581    countrySelect.setAttribute('name', 'signature[country]');
582    countrySelect.classList.add('visible');
583    countryLabel.classList.add('hidden');
584  }
585
586  function submitForm(event) {
587    /**
588     * Submits the form to ActionNetwork. If the script doesn’t, by now, know
589     * the action_network identifier, default isn’t prevented on the event and
590     * form submission proceeds as normal.
591     * @param {event} event - Form submission event
592     * */
593
594    if (objectIdentifier) {
595      event.preventDefault();
596    }
597
598    var
599      signatureSubmission = new XMLHttpRequest();
600
601    win.callbacks.petitions.preSubmit();
602
603    function compilePayload() {
604      /**
605       * Compiles the form data into a JSON payload for Ajax submission
606       * @return {object} petitionFormData - just the info the API needs
607       * */
608      var tags = JSON.parse(doc.querySelector('[name="subscription[tag_list]"]').value);
609      if (util.getReferrerTag())
610        tags.push(util.getReferrerTag());
611
612      var formData = new FormData();
613      formData.append('guard', '');
614      formData.append('hp_enabled', true);
615      formData.append('org', window.org ? window.org : 'fftf');
616      formData.append('an_tags', JSON.stringify(tags));
617      formData.append('an_url', win.location.href);
618      formData.append('an_petition', petitionSignatureForm.action.replace('/signatures', ''));
619      formData.append('member[first_name]', doc.getElementById('form-first_name').value);
620      formData.append('member[email]', doc.getElementById('form-email').value);
621      formData.append('member[postcode]', doc.getElementById('form-zip_code').value);
622      formData.append('member[country]', countrySelect.value);
623
624      if (doc.getElementById('opt-in') && doc.getElementById('opt-in').getAttribute('type') === 'checkbox' && doc.getElementById('opt-in').checked === false) {
625        formData.append('opt_out', true);
626      }
627
628      if (doc.getElementById('form-street_address')) {
629        formData.append('member[street_address]', doc.getElementById('form-street_address').value);
630      }
631
632      if (doc.getElementById('form-phone_number')) {
633        formData.append('member[phone_number]', doc.getElementById('form-phone_number').value);
634      }
635
636      if (doc.getElementById('form-comments')) {
637        formData.append('action_comment', doc.getElementById('form-comments').value);
638      }
639
640      if (queryString.source) {
641        formData.append('subscription[source]', queryString.source);
642      }
643
644      var autoresponderHours = doc.querySelector('meta[name="autoresponder_hours"]'),
645          autoresponderActive = doc.querySelector('meta[name="autoresponder_active"]');
646      formData.append('autoresponder_hours', autoresponderHours ? autoresponderHours.content : 72);
647
648      if (autoresponderActive)
649        formData.append('autoresponder_active', 1);
650
651      var mothershipTag = document.querySelector('input[name="_mothership_tag"]');
652      if (mothershipTag && mothershipTag.value)
653        formData.append('tag', mothershipTag.value);
654      else
655        formData.append('tag', window.location.pathname);
656
657      return formData;
658    }
659
660    signatureSubmission.open('POST', 'https://queue.fightforthefuture.org/action', true);
661    // signatureSubmission.open('POST', 'http://localhost:9001/action', true); // JL DEBUG ~
662    signatureSubmission.addEventListener('error', win.callbacks.petitions.handleSigningError);
663    signatureSubmission.addEventListener('load',  win.callbacks.petitions.loadSignatureResponse);
664    signatureSubmission.send(compilePayload());
665
666    if (typeof FreeProgress !== "undefined")
667      FreeProgress.convert();
668  }
669
670  function addEventListeners() {
671    /**
672     * Attaches all the listeners all the places
673     * */
674    countryLabel.addEventListener('click', toggleCountryField);
675    countrySelect.addEventListener('change', updateZIPPlaceholder);
676    petitionSignatureForm.addEventListener('submit', submitForm);
677  }
678
679  function init() {
680    requestAPIInfo();
681    addEventListeners();
682  }
683
684  init();
685};
686
687(function (doc, win) {
688  "use strict";
689
690  win.callbacks = win.callbacks || {};
691  win.callbacks.petitions = {
692     preSubmit: function() {
693      /**
694       * Fires up the loading modal and disables the form
695       * @return {object} - modal with spinner
696       * */
697      doc.querySelector('[type="submit"]').setAttribute('disabled', true); // JL DEBUG ~ disable for testing
698    },
699
700    loadSignatureResponse: function(e) {
701      /**
702       * Does the thing after we get a response from the API server
703       * */
704
705      var xhr = e.target;
706
707      if (xhr.status >= 200 && xhr.status < 400) {
708
709        hideForm()
710
711      } else {
712        win.callbacks.petitions.handleSigningError(xhr);
713      }
714    },
715
716    handleSigningError: function(e) {
717      /**
718       * Figures out what to say at just the right moment
719       * @param {event|XMLHttpRequest} e - Might be an event, might be a response
720       * from an XMLHttpRequest
721       * */
722
723
724      doc.querySelector('[type="submit"]').removeAttribute('disabled');
725
726      alert('AN ERROR OCCURRED DURING SUBMISSION :(\n\nPlease contact [email protected]');
727    }
728  }
729
730})(document, window);
731
732(function (doc, win) {
733  "use strict";
734
735  var triggerComponents = function() {
736    win.components = win.components || {};
737    var
738      i = 0,
739      components = doc.querySelector('body').getAttribute('data-components');
740
741    if (components !== undefined) {
742      components = components.split(' ');
743      i = components.length;
744
745      while (i--) {
746        if (components[i] !== '' && win.components[components[i]] !== undefined) {
747          win.components[components[i]](doc, win);
748        }
749      }
750    }
751  }
752
753  triggerComponents(); // JL NOTE ~ disabled for "beta"
754
755  var showChat = function() {
756    doc.querySelector('.chat').classList.remove('hidden');
757    setTimeout(function() {
758      doc.querySelector('.chat').classList.remove('transparent');
759      doc.body.classList.remove('scrolling');
760    }, 10);
761  }
762
763  var hideChat = function() {
764    doc.querySelector('.chat').classList.add('transparent');
765    setTimeout(function() {
766      doc.querySelector('.chat').classList.add('hidden');
767      doc.body.classList.add('scrolling');
768    }, 500);
769  }
770
771  var handleGetStarted = function(e) {
772    if (getComputedStyle(document.querySelector('.chat')).position == 'fixed') {
773      showChat();
774    } else {
775      smoothScroll(0);
776    }
777  }
778
779  var onDomContentLoaded =function() {
780
781    var learnMore = doc.querySelector('a.learn-more');
782    if (learnMore && doc.body.className.indexOf('iframe') === -1) {
783      learnMore.addEventListener('click', function(e) {
784
785        if (getComputedStyle(document.querySelector('.chat')).position == 'fixed') {
786          hideChat();
787        } else {
788          smoothScroll(doc.querySelector('h1'));
789        }
790      });
791    }
792
793    window.onhashchange = function (e) {
794      if (e.newURL.slice(-1) == '#') {
795        handleGetStarted();
796      }
797    }
798
799    initModal();
800
801    // JL NOTE ~ strip all this junk out when the "beta" is over ---------------
802    // -------------------------------------------------------------------------
803    // var showMainPage = function(immediately) {
804    //   var triggered = function() {
805    //     doc.getElementById('beta').style.display = 'none';
806    //     triggerComponents();
807    //   }
808    //   if (!immediately) {
809    //     doc.getElementById('beta').style.opacity = 0;
810    //     setTimeout(function() {
811    //       triggered()
812    //     }, 500);
813    //   } else {
814    //     triggered()
815    //   }
816    // }
817
818    // var beta = doc.getElementById('beta');
819    // if (beta) {
820    //   doc.getElementById('beta-form').addEventListener('submit', function(e) {
821    //     e.preventDefault();
822    //     if (doc.getElementById('beta-code').value.toLowerCase() != 'porunga') {
823    //       doc.getElementById('beta-explanation').innerHTML = 'Sorry, that beta key didn\'t work. Contact <a href="mailto:[email protected]">[email protected]</a> if you need assistance.';
824    //       doc.getElementById('beta-explanation').className = 'error';
825    //       doc.getElementById('beta-form').className = 'error';
826    //     } else {
827    //       doc.getElementById('beta-form').style.display = 'none';
828    //       doc.getElementById('beta').querySelector('h2').style.display = 'block';
829    //       document.cookie = "beta=true; expires=Thu, 01 Dec 2016 12:00:00 UTC";
830    //       setTimeout(function() { showMainPage(); }, 1500);
831    //     }
832    //   });
833    // }
834    // if (document.cookie.indexOf('beta=true') !== -1)
835    //   showMainPage(true);
836    // -------------------------------------------------------------------------
837
838  };
839
840  var initModal = function() {
841    var overlay = doc.getElementById('overlay1');
842
843    if (!overlay)
844      return;
845
846    overlay.style.position = 'fixed';
847    overlay.style.left = '0px';
848    overlay.style.top = '0px';
849    overlay.style.display = 'none';
850    overlay.classList.add('hidden');
851
852    overlay.addEventListener('click', function(e) {
853      if (e.target == overlay)
854        modal.hide('overlay1');
855    });
856
857    overlay.querySelector('.x').addEventListener('click', function(e) {
858      e.preventDefault();
859      modal.hide('overlay1');
860    });
861  }
862
863  var isReady = document.readyState;
864
865  if (isReady == "complete" || isReady == "loaded" || isReady == "interactive")
866    onDomContentLoaded();
867  else if (document.addEventListener)
868    document.addEventListener('DOMContentLoaded', onDomContentLoaded, false);
869
870
871})(document, window);
872
873var modal = {
874  show: function(id) {
875    var overlay = document.getElementById(id);
876
877    if (!overlay)
878      return;
879
880    overlay.style.display = 'block';
881
882    setTimeout(function() {
883      overlay.classList.remove('hidden');
884    }, 25);
885
886  },
887  hide: function(id) {
888    var overlay = document.getElementById(id);
889
890    if (!overlay)
891      return;
892
893    overlay.classList.add('hidden');
894    setTimeout(function() {
895      overlay.style.display = 'none';
896    }, 350);
897
898  }
899}
900
901// Show archived site modal on page load
902document.addEventListener("DOMContentLoaded", function() {
903  var body = document.querySelector('body');
904  body.classList.add('modal-open');
905  var html = "<div class='overlay js-archive-modal js-close'>"+
906      "<div class='modal'>"+
907        "<img src='/images/fftf-logo-dark.svg' alt='Fight for the Future logo'>"+
908        "<p class='push-top'>"+
909          "Please note that HelloVote's functionality has been disabled. You "+
910          "can see the results from our experiment in civic engagement via "+
911          "mobile voter registration "+
912          "<a href='https://www.hello.vote/stats/'>here</a>. If you are a "+
913          "developer, the source code for HelloVote is "+
914          "<a href='https://github.com/fightforthefuture/votebot-api'>here</a>. "+
915          "Stay tuned..."+
916        "</p>"+
917        "<p>"+
918          "To see what Fight for the Future is up to, "+
919          "<a href='https://www.fightforthefuture.org/'>please visit our homepage</a>."+
920        "</p>"+
921        "<a class='x js-close'>×</a>"+
922      "</div>"+
923    "</div>";
924  var injector = document.createElement('div');
925  injector.innerHTML = html;
926  body.appendChild(injector);
927
928  document.addEventListener('click', function(ev) {
929    if (ev.target.classList.contains('js-close')) {
930      ev.preventDefault();
931      var modalEl = document.querySelectorAll('.js-archive-modal');
932      modalEl[0].classList.add('hidden');
933      body.classList.remove('modal-open');
934    }
935  }, false);
936});

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.