PageSourceSearch

https://lyricstranslate.com/files/advagg_js/js__R8WcosUVN4vrVuDMaL…ExPweKWjySkUWN0lB8pKouzehIaMkRu_KuF3dMU0.js

js lyricstranslate.com collected 2026-09-24 08:40:10 UTC 189,009 bytes, 5,149 lines download raw bytes

1/*jslint browser: true */ /*global jQuery: true */
2
3/**
4 * jQuery Cookie plugin
5 *
6 * Copyright (c) 2010 Klaus Hartl (stilbuero.de)
7 * Dual licensed under the MIT and GPL licenses:
8 * http://www.opensource.org/licenses/mit-license.php
9 * http://www.gnu.org/licenses/gpl.html
10 *
11 */
12
13// TODO JsDoc
14
15/**
16 * Create a cookie with the given key and value and other optional parameters.
17 *
18 * @example $.cookie('the_cookie', 'the_value');
19 * @desc Set the value of a cookie.
20 * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true });
21 * @desc Create a cookie with all available options.
22 * @example $.cookie('the_cookie', 'the_value');
23 * @desc Create a session cookie.
24 * @example $.cookie('the_cookie', null);
25 * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain
26 *       used when the cookie was set.
27 *
28 * @param String key The key of the cookie.
29 * @param String value The value of the cookie.
30 * @param Object options An object literal containing key/value pairs to provide optional cookie attributes.
31 * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object.
32 *                             If a negative value is specified (e.g. a date in the past), the cookie will be deleted.
33 *                             If set to null or omitted, the cookie will be a session cookie and will not be retained
34 *                             when the the browser exits.
35 * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie).
36 * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie).
37 * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will
38 *                        require a secure protocol (like HTTPS).
39 * @type undefined
40 *
41 * @name $.cookie
42 * @cat Plugins/Cookie
43 * @author Klaus Hartl/[email protected]
44 */
45
46/**
47 * Get the value of a cookie with the given key.
48 *
49 * @example $.cookie('the_cookie');
50 * @desc Get the value of a cookie.
51 *
52 * @param String key The key of the cookie.
53 * @return The value of the cookie.
54 * @type String
55 *
56 * @name $.cookie
57 * @cat Plugins/Cookie
58 * @author Klaus Hartl/[email protected]
59 */
60jQuery.cookie = function (key, value, options) {
61
62    // key and value given, set cookie...
63    if (arguments.length > 1 && (value === null || typeof value !== "object")) {
64        options = jQuery.extend({}, options);
65
66        if (value === null) {
67            options.expires = -1;
68        }
69
70        if (typeof options.expires === 'number') {
71            var days = options.expires, t = options.expires = new Date();
72            t.setDate(t.getDate() + days);
73        }
74
75        return (document.cookie = [
76            encodeURIComponent(key), '=',
77            options.raw ? String(value) : encodeURIComponent(String(value)),
78            options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
79            options.path ? '; path=' + options.path : '',
80            options.domain ? '; domain=' + options.domain : '',
81            options.secure ? '; secure' : ''
82        ].join(''));
83    }
84
85    // key and possibly options given, get cookie...
86    options = value || {};
87    var result, decode = options.raw ? function (s) { return s; } : decodeURIComponent;
88    return (result = new RegExp('(?:^|; )' + encodeURIComponent(key) + '=([^;]*)').exec(document.cookie)) ? decode(result[1]) : null;
89};
90;/*})'"*/;/*})'"*/
91(function ($) {
92
93/**
94 * Provides Ajax page updating via jQuery $.ajax (Asynchronous JavaScript and XML).
95 *
96 * Ajax is a method of making a request via JavaScript while viewing an HTML
97 * page. The request returns an array of commands encoded in JSON, which is
98 * then executed to make any changes that are necessary to the page.
99 *
100 * Drupal uses this file to enhance form elements with #ajax['path'] and
101 * #ajax['wrapper'] properties. If set, this file will automatically be included
102 * to provide Ajax capabilities.
103 */
104
105Drupal.ajax = Drupal.ajax || {};
106
107Drupal.settings.urlIsAjaxTrusted = Drupal.settings.urlIsAjaxTrusted || {};
108
109/**
110 * Attaches the Ajax behavior to each Ajax form element.
111 */
112Drupal.behaviors.AJAX = {
113  attach: function (context, settings) {
114    // Load all Ajax behaviors specified in the settings.
115    for (var base in settings.ajax) {
116      if (!$('#' + base + '.ajax-processed').length) {
117        var element_settings = settings.ajax[base];
118
119        if (typeof element_settings.selector == 'undefined') {
120          element_settings.selector = '#' + base;
121        }
122        $(element_settings.selector).each(function () {
123          element_settings.element = this;
124          Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
125        });
126
127        $('#' + base).addClass('ajax-processed');
128      }
129    }
130
131    // Bind Ajax behaviors to all items showing the class.
132    $('.use-ajax:not(.ajax-processed)').addClass('ajax-processed').each(function () {
133      var element_settings = {};
134      // Clicked links look better with the throbber than the progress bar.
135      element_settings.progress = { 'type': 'throbber' };
136
137      // For anchor tags, these will go to the target of the anchor rather
138      // than the usual location.
139      if ($(this).attr('href')) {
140        element_settings.url = $(this).attr('href');
141        element_settings.event = 'click';
142      }
143      var base = $(this).attr('id');
144      Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
145    });
146
147    // This class means to submit the form to the action using Ajax.
148    $('.use-ajax-submit:not(.ajax-processed)').addClass('ajax-processed').each(function () {
149      var element_settings = {};
150
151      // Ajax submits specified in this manner automatically submit to the
152      // normal form action.
153      element_settings.url = $(this.form).attr('action');
154      // Form submit button clicks need to tell the form what was clicked so
155      // it gets passed in the POST request.
156      element_settings.setClick = true;
157      // Form buttons use the 'click' event rather than mousedown.
158      element_settings.event = 'click';
159      // Clicked form buttons look better with the throbber than the progress bar.
160      element_settings.progress = { 'type': 'throbber' };
161
162      var base = $(this).attr('id');
163      Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
164    });
165  }
166};
167
168/**
169 * Ajax object.
170 *
171 * All Ajax objects on a page are accessible through the global Drupal.ajax
172 * object and are keyed by the submit button's ID. You can access them from
173 * your module's JavaScript file to override properties or functions.
174 *
175 * For example, if your Ajax enabled button has the ID 'edit-submit', you can
176 * redefine the function that is called to insert the new content like this
177 * (inside a Drupal.behaviors attach block):
178 * @code
179 *    Drupal.behaviors.myCustomAJAXStuff = {
180 *      attach: function (context, settings) {
181 *        Drupal.ajax['edit-submit'].commands.insert = function (ajax, response, status) {
182 *          new_content = $(response.data);
183 *          $('#my-wrapper').append(new_content);
184 *          alert('New content was appended to #my-wrapper');
185 *        }
186 *      }
187 *    };
188 * @endcode
189 */
190Drupal.ajax = function (base, element, element_settings) {
191  var defaults = {
192    url: 'system/ajax',
193    event: 'mousedown',
194    keypress: true,
195    selector: '#' + base,
196    effect: 'none',
197    speed: 'none',
198    method: 'replaceWith',
199    progress: {
200      type: 'throbber',
201      message: Drupal.t('Please wait...')
202    },
203    submit: {
204      'js': true
205    }
206  };
207
208  $.extend(this, defaults, element_settings);
209
210  this.element = element;
211  this.element_settings = element_settings;
212
213  // Replacing 'nojs' with 'ajax' in the URL allows for an easy method to let
214  // the server detect when it needs to degrade gracefully.
215  // There are five scenarios to check for:
216  // 1. /nojs/
217  // 2. /nojs$ - The end of a URL string.
218  // 3. /nojs? - Followed by a query (with clean URLs enabled).
219  //      E.g.: path/nojs?destination=foobar
220  // 4. /nojs& - Followed by a query (without clean URLs enabled).
221  //      E.g.: ?q=path/nojs&destination=foobar
222  // 5. /nojs# - Followed by a fragment.
223  //      E.g.: path/nojs#myfragment
224  this.url = element_settings.url.replace(/\/nojs(\/|$|\?|&|#)/g, '/ajax$1');
225  // If the 'nojs' version of the URL is trusted, also trust the 'ajax' version.
226  if (Drupal.settings.urlIsAjaxTrusted[element_settings.url]) {
227    Drupal.settings.urlIsAjaxTrusted[this.url] = true;
228  }
229
230  this.wrapper = '#' + element_settings.wrapper;
231
232  // If there isn't a form, jQuery.ajax() will be used instead, allowing us to
233  // bind Ajax to links as well.
234  if (this.element.form) {
235    this.form = $(this.element.form);
236  }
237
238  // Set the options for the ajaxSubmit function.
239  // The 'this' variable will not persist inside of the options object.
240  var ajax = this;
241  ajax.options = {
242    url: Drupal.sanitizeAjaxUrl(ajax.url),
243    data: ajax.submit,
244    beforeSerialize: function (element_settings, options) {
245      return ajax.beforeSerialize(element_settings, options);
246    },
247    beforeSubmit: function (form_values, element_settings, options) {
248      ajax.ajaxing = true;
249      return ajax.beforeSubmit(form_values, element_settings, options);
250    },
251    beforeSend: function (xmlhttprequest, options) {
252      ajax.ajaxing = true;
253      return ajax.beforeSend(xmlhttprequest, options);
254    },
255    success: function (response, status, xmlhttprequest) {
256      // Sanity check for browser support (object expected).
257      // When using iFrame uploads, responses must be returned as a string.
258      if (typeof response == 'string') {
259        response = $.parseJSON(response);
260      }
261
262      // Prior to invoking the response's commands, verify that they can be
263      // trusted by checking for a response header. See
264      // ajax_set_verification_header() for details.
265      // - Empty responses are harmless so can bypass verification. This avoids
266      //   an alert message for server-generated no-op responses that skip Ajax
267      //   rendering.
268      // - Ajax objects with trusted URLs (e.g., ones defined server-side via
269      //   #ajax) can bypass header verification. This is especially useful for
270      //   Ajax with multipart forms. Because IFRAME transport is used, the
271      //   response headers cannot be accessed for verification.
272      if (response !== null && !Drupal.settings.urlIsAjaxTrusted[ajax.url]) {
273        if (xmlhttprequest.getResponseHeader('X-Drupal-Ajax-Token') !== '1') {
274          var customMessage = Drupal.t("The response failed verification so will not be processed.");
275          return ajax.error(xmlhttprequest, ajax.url, customMessage);
276        }
277      }
278
279      return ajax.success(response, status);
280    },
281    complete: function (xmlhttprequest, status) {
282      ajax.ajaxing = false;
283      if (status == 'error' || status == 'parsererror') {
284        return ajax.error(xmlhttprequest, ajax.url);
285      }
286    },
287    dataType: 'json',
288    jsonp: false,
289    type: 'POST'
290  };
291
292  // For multipart forms (e.g., file uploads), jQuery Form targets the form
293  // submission to an iframe instead of using an XHR object. The initial "src"
294  // of the iframe, prior to the form submission, is set to options.iframeSrc.
295  // "about:blank" is the semantically correct, standards-compliant, way to
296  // initialize a blank iframe; however, some old IE versions (including 6,
297  // unclear for 7-10, but not 11) incorrectly report a mixed content warning
298  // when iframes with an "about:blank" src are added to a parent document with
299  // an https:// origin. jQuery Form works around this by defaulting to
300  // "javascript:false" instead, but that breaks on Chrome 83, so here we
301  // force the semantically correct behavior for all browsers except old IE.
302  // @see https://www.drupal.org/project/drupal/issues/3143016
303  // @see https://github.com/jquery-form/form/blob/df9cb101b9c9c085c8d75ad980c7ff1cf62063a1/jquery.form.js#L68
304  // @see https://bugs.chromium.org/p/chromium/issues/detail?id=1084874
305  // @see https://html.spec.whatwg.org/multipage/browsers.html#creating-browsing-contexts
306  // @see https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
307  if (navigator.userAgent.indexOf("MSIE") === -1) {
308    ajax.options.iframeSrc = 'about:blank';
309  }
310
311  // For multipart forms (e.g., file uploads), jQuery Form targets the form
312  // submission to an iframe instead of using an XHR object. The initial "src"
313  // of the iframe, prior to the form submission, is set to options.iframeSrc.
314  // "about:blank" is the semantically correct, standards-compliant, way to
315  // initialize a blank iframe; however, some old IE versions (possibly only 6)
316  // incorrectly report a mixed content warning when iframes with an
317  // "about:blank" src are added to a parent document with an https:// origin.
318  // jQuery Form works around this by defaulting to "javascript:false" instead,
319  // but that breaks on Chrome 83, so here we force the semantically correct
320  // behavior for all browsers except old IE.
321  // @see https://www.drupal.org/project/drupal/issues/3143016
322  // @see https://github.com/jquery-form/form/blob/df9cb101b9c9c085c8d75ad980c7ff1cf62063a1/jquery.form.js#L68
323  // @see https://bugs.chromium.org/p/chromium/issues/detail?id=1084874
324  // @see https://html.spec.whatwg.org/multipage/browsers.html#creating-browsing-contexts
325  // @see https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
326  if (navigator.userAgent.indexOf("MSIE") === -1) {
327    ajax.options.iframeSrc = 'about:blank';
328  }
329
330  // Bind the ajaxSubmit function to the element event.
331  $(ajax.element).bind(element_settings.event, function (event) {
332    if (!Drupal.settings.urlIsAjaxTrusted[ajax.url] && !Drupal.urlIsLocal(ajax.url)) {
333      throw new Error(Drupal.t('The callback URL is not local and not trusted: !url', {'!url': ajax.url}));
334    }
335    return ajax.eventResponse(this, event);
336  });
337
338  // If necessary, enable keyboard submission so that Ajax behaviors
339  // can be triggered through keyboard input as well as e.g. a mousedown
340  // action.
341  if (element_settings.keypress) {
342    $(ajax.element).keypress(function (event) {
343      return ajax.keypressResponse(this, event);
344    });
345  }
346
347  // If necessary, prevent the browser default action of an additional event.
348  // For example, prevent the browser default action of a click, even if the
349  // AJAX behavior binds to mousedown.
350  if (element_settings.prevent) {
351    $(ajax.element).bind(element_settings.prevent, false);
352  }
353};
354
355/**
356 * Handle a key press.
357 *
358 * The Ajax object will, if instructed, bind to a key press response. This
359 * will test to see if the key press is valid to trigger this event and
360 * if it is, trigger it for us and prevent other keypresses from triggering.
361 * In this case we're handling RETURN and SPACEBAR keypresses (event codes 13
362 * and 32. RETURN is often used to submit a form when in a textfield, and 
363 * SPACE is often used to activate an element without submitting. 
364 */
365Drupal.ajax.prototype.keypressResponse = function (element, event) {
366  // Create a synonym for this to reduce code confusion.
367  var ajax = this;
368
369  // Detect enter key and space bar and allow the standard response for them,
370  // except for form elements of type 'text' and 'textarea', where the 
371  // spacebar activation causes inappropriate activation if #ajax['keypress'] is 
372  // TRUE. On a text-type widget a space should always be a space.
373  if (event.which == 13 || (event.which == 32 && element.type != 'text' && element.type != 'textarea')) {
374    $(ajax.element_settings.element).trigger(ajax.element_settings.event);
375    return false;
376  }
377};
378
379/**
380 * Handle an event that triggers an Ajax response.
381 *
382 * When an event that triggers an Ajax response happens, this method will
383 * perform the actual Ajax call. It is bound to the event using
384 * bind() in the constructor, and it uses the options specified on the
385 * ajax object.
386 */
387Drupal.ajax.prototype.eventResponse = function (element, event) {
388  // Create a synonym for this to reduce code confusion.
389  var ajax = this;
390
391  // Do not perform another ajax command if one is already in progress.
392  if (ajax.ajaxing) {
393    return false;
394  }
395
396  try {
397    if (ajax.form) {
398      // If setClick is set, we must set this to ensure that the button's
399      // value is passed.
400      if (ajax.setClick) {
401        // Mark the clicked button. 'form.clk' is a special variable for
402        // ajaxSubmit that tells the system which element got clicked to
403        // trigger the submit. Without it there would be no 'op' or
404        // equivalent.
405        element.form.clk = element;
406      }
407
408      ajax.form.ajaxSubmit(ajax.options);
409    }
410    else {
411      ajax.beforeSerialize(ajax.element, ajax.options);
412      $.ajax(ajax.options);
413    }
414  }
415  catch (e) {
416    // Unset the ajax.ajaxing flag here because it won't be unset during
417    // the complete response.
418    ajax.ajaxing = false;
419    alert("An error occurred while attempting to process " + ajax.options.url + ": " + e.message);
420  }
421
422  // For radio/checkbox, allow the default event. On IE, this means letting
423  // it actually check the box.
424  if (typeof element.type != 'undefined' && (element.type == 'checkbox' || element.type == 'radio')) {
425    return true;
426  }
427  else {
428    return false;
429  }
430
431};
432
433/**
434 * Handler for the form serialization.
435 *
436 * Runs before the beforeSend() handler (see below), and unlike that one, runs
437 * before field data is collected.
438 */
439Drupal.ajax.prototype.beforeSerialize = function (element, options) {
440  // Allow detaching behaviors to update field values before collecting them.
441  // This is only needed when field values are added to the POST data, so only
442  // when there is a form such that this.form.ajaxSubmit() is used instead of
443  // $.ajax(). When there is no form and $.ajax() is used, beforeSerialize()
444  // isn't called, but don't rely on that: explicitly check this.form.
445  if (this.form) {
446    var settings = this.settings || Drupal.settings;
447    Drupal.detachBehaviors(this.form, settings, 'serialize');
448  }
449
450  // Prevent duplicate HTML ids in the returned markup.
451  // @see drupal_html_id()
452  options.data['ajax_html_ids[]'] = [];
453  $('[id]').each(function () {
454    options.data['ajax_html_ids[]'].push(this.id);
455  });
456
457  // Allow Drupal to return new JavaScript and CSS files to load without
458  // returning the ones already loaded.
459  // @see ajax_base_page_theme()
460  // @see drupal_get_css()
461  // @see drupal_get_js()
462  options.data['ajax_page_state[theme]'] = Drupal.settings.ajaxPageState.theme;
463  options.data['ajax_page_state[theme_token]'] = Drupal.settings.ajaxPageState.theme_token;
464  for (var key in Drupal.settings.ajaxPageState.css) {
465    options.data['ajax_page_state[css][' + key + ']'] = 1;
466  }
467  for (var key in Drupal.settings.ajaxPageState.js) {
468    options.data['ajax_page_state[js][' + key + ']'] = 1;
469  }
470};
471
472/**
473 * Modify form values prior to form submission.
474 */
475Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) {
476  // This function is left empty to make it simple to override for modules
477  // that wish to add functionality here.
478};
479
480/**
481 * Prepare the Ajax request before it is sent.
482 */
483Drupal.ajax.prototype.beforeSend = function (xmlhttprequest, options) {
484  // For forms without file inputs, the jQuery Form plugin serializes the form
485  // values, and then calls jQuery's $.ajax() function, which invokes this
486  // handler. In this circumstance, options.extraData is never used. For forms
487  // with file inputs, the jQuery Form plugin uses the browser's normal form
488  // submission mechanism, but captures the response in a hidden IFRAME. In this
489  // circumstance, it calls this handler first, and then appends hidden fields
490  // to the form to submit the values in options.extraData. There is no simple
491  // way to know which submission mechanism will be used, so we add to extraData
492  // regardless, and allow it to be ignored in the former case.
493  if (this.form) {
494    options.extraData = options.extraData || {};
495
496    // Let the server know when the IFRAME submission mechanism is used. The
497    // server can use this information to wrap the JSON response in a TEXTAREA,
498    // as per http://jquery.malsup.com/form/#file-upload.
499    options.extraData.ajax_iframe_upload = '1';
500
501    // The triggering element is about to be disabled (see below), but if it
502    // contains a value (e.g., a checkbox, textfield, select, etc.), ensure that
503    // value is included in the submission. As per above, submissions that use
504    // $.ajax() are already serialized prior to the element being disabled, so
505    // this is only needed for IFRAME submissions.
506    var v = $.fieldValue(this.element);
507    if (v !== null) {
508      options.extraData[this.element.name] = Drupal.checkPlain(v);
509    }
510  }
511
512  // Disable the element that received the change to prevent user interface
513  // interaction while the Ajax request is in progress. ajax.ajaxing prevents
514  // the element from triggering a new request, but does not prevent the user
515  // from changing its value.
516  $(this.element).addClass('progress-disabled').attr('disabled', true);
517
518  // Insert progressbar or throbber.
519  if (this.progress.type == 'bar') {
520    var progressBar = new Drupal.progressBar('ajax-progress-' + this.element.i
520d, $.noop, this.progress.method, $.noop);
521    if (this.progress.message) {
522      progressBar.setProgress(-1, this.progress.message);
523    }
524    if (this.progress.url) {
525      progressBar.startMonitoring(this.progress.url, this.progress.interval || 1500);
526    }
527    this.progress.element = $(progressBar.element).addClass('ajax-progress ajax-progress-bar');
528    this.progress.object = progressBar;
529    $(this.element).after(this.progress.element);
530  }
531  else if (this.progress.type == 'throbber') {
532    this.progress.element = $('<div class="ajax-progress ajax-progress-throbber"><div class="throbber">&nbsp;</div></div>');
533    if (this.progress.message) {
534      $('.throbber', this.progress.element).after('<div class="message">' + this.progress.message + '</div>');
535    }
536    $(this.element).after(this.progress.element);
537  }
538};
539
540/**
541 * Handler for the form redirection completion.
542 */
543Drupal.ajax.prototype.success = function (response, status) {
544  // Remove the progress element.
545  if (this.progress.element) {
546    $(this.progress.element).remove();
547  }
548  if (this.progress.object) {
549    this.progress.object.stopMonitoring();
550  }
551  $(this.element).removeClass('progress-disabled').removeAttr('disabled');
552
553  Drupal.freezeHeight();
554
555  for (var i in response) {
556    if (response.hasOwnProperty(i) && response[i]['command'] && this.commands[response[i]['command']]) {
557      this.commands[response[i]['command']](this, response[i], status);
558    }
559  }
560
561  // Reattach behaviors, if they were detached in beforeSerialize(). The
562  // attachBehaviors() called on the new content from processing the response
563  // commands is not sufficient, because behaviors from the entire form need
564  // to be reattached.
565  if (this.form) {
566    var settings = this.settings || Drupal.settings;
567    Drupal.attachBehaviors(this.form, settings);
568  }
569
570  Drupal.unfreezeHeight();
571
572  // Remove any response-specific settings so they don't get used on the next
573  // call by mistake.
574  this.settings = null;
575};
576
577/**
578 * Build an effect object which tells us how to apply the effect when adding new HTML.
579 */
580Drupal.ajax.prototype.getEffect = function (response) {
581  var type = response.effect || this.effect;
582  var speed = response.speed || this.speed;
583
584  var effect = {};
585  if (type == 'none') {
586    effect.showEffect = 'show';
587    effect.hideEffect = 'hide';
588    effect.showSpeed = '';
589  }
590  else if (type == 'fade') {
591    effect.showEffect = 'fadeIn';
592    effect.hideEffect = 'fadeOut';
593    effect.showSpeed = speed;
594  }
595  else {
596    effect.showEffect = type + 'Toggle';
597    effect.hideEffect = type + 'Toggle';
598    effect.showSpeed = speed;
599  }
600
601  return effect;
602};
603
604/**
605 * Handler for the form redirection error.
606 */
607Drupal.ajax.prototype.error = function (xmlhttprequest, uri, customMessage) {
608  Drupal.displayAjaxError(Drupal.ajaxError(xmlhttprequest, uri, customMessage));
609  // Remove the progress element.
610  if (this.progress.element) {
611    $(this.progress.element).remove();
612  }
613  if (this.progress.object) {
614    this.progress.object.stopMonitoring();
615  }
616  // Undo hide.
617  $(this.wrapper).show();
618  // Re-enable the element.
619  $(this.element).removeClass('progress-disabled').removeAttr('disabled');
620  // Reattach behaviors, if they were detached in beforeSerialize().
621  if (this.form) {
622    var settings = this.settings || Drupal.settings;
623    Drupal.attachBehaviors(this.form, settings);
624  }
625};
626
627/**
628 * Provide a series of commands that the server can request the client perform.
629 */
630Drupal.ajax.prototype.commands = {
631  /**
632   * Command to insert new content into the DOM.
633   */
634  insert: function (ajax, response, status) {
635    // Get information from the response. If it is not there, default to
636    // our presets.
637    var wrapper = response.selector ? $(response.selector) : $(ajax.wrapper);
638    var method = response.method || ajax.method;
639    var effect = ajax.getEffect(response);
640
641    // We don't know what response.data contains: it might be a string of text
642    // without HTML, so don't rely on jQuery correctly iterpreting
643    // $(response.data) as new HTML rather than a CSS selector. Also, if
644    // response.data contains top-level text nodes, they get lost with either
645    // $(response.data) or $('<div></div>').replaceWith(response.data).
646    var new_content_wrapped = $('<div></div>').html(response.data);
647    var new_content = new_content_wrapped.contents();
648
649    // For legacy reasons, the effects processing code assumes that new_content
650    // consists of a single top-level element. Also, it has not been
651    // sufficiently tested whether attachBehaviors() can be successfully called
652    // with a context object that includes top-level text nodes. However, to
653    // give developers full control of the HTML appearing in the page, and to
654    // enable Ajax content to be inserted in places where DIV elements are not
655    // allowed (e.g., within TABLE, TR, and SPAN parents), we check if the new
656    // content satisfies the requirement of a single top-level element, and
657    // only use the container DIV created above when it doesn't. For more
658    // information, please see http://drupal.org/node/736066.
659    if (new_content.length != 1 || new_content.get(0).nodeType != 1) {
660      new_content = new_content_wrapped;
661    }
662
663    // If removing content from the wrapper, detach behaviors first.
664    switch (method) {
665      case 'html':
666      case 'replaceWith':
667      case 'replaceAll':
668      case 'empty':
669      case 'remove':
670        var settings = response.settings || ajax.settings || Drupal.settings;
671        Drupal.detachBehaviors(wrapper, settings);
672    }
673
674    // Add the new content to the page.
675    wrapper[method](new_content);
676
677    // Immediately hide the new content if we're using any effects.
678    if (effect.showEffect != 'show') {
679      new_content.hide();
680    }
681
682    // Determine which effect to use and what content will receive the
683    // effect, then show the new content.
684    if ($('.ajax-new-content', new_content).length > 0) {
685      $('.ajax-new-content', new_content).hide();
686      new_content.show();
687      $('.ajax-new-content', new_content)[effect.showEffect](effect.showSpeed);
688    }
689    else if (effect.showEffect != 'show') {
690      new_content[effect.showEffect](effect.showSpeed);
691    }
692
693    // Attach all JavaScript behaviors to the new content, if it was successfully
694    // added to the page, this if statement allows #ajax['wrapper'] to be
695    // optional.
696    if (new_content.parents('html').length > 0) {
697      // Apply any settings from the returned JSON if available.
698      var settings = response.settings || ajax.settings || Drupal.settings;
699      Drupal.attachBehaviors(new_content, settings);
700    }
701  },
702
703  /**
704   * Command to remove a chunk from the page.
705   */
706  remove: function (ajax, response, status) {
707    var settings = response.settings || ajax.settings || Drupal.settings;
708    Drupal.detachBehaviors($(response.selector), settings);
709    $(response.selector).remove();
710  },
711
712  /**
713   * Command to mark a chunk changed.
714   */
715  changed: function (ajax, response, status) {
716    if (!$(response.selector).hasClass('ajax-changed')) {
717      $(response.selector).addClass('ajax-changed');
718      if (response.asterisk) {
719        $(response.selector).find(response.asterisk).append(' <span class="ajax-changed">*</span> ');
720      }
721    }
722  },
723
724  /**
725   * Command to provide an alert.
726   */
727  alert: function (ajax, response, status) {
728    alert(response.text, response.title);
729  },
730
731  /**
732   * Command to provide the jQuery css() function.
733   */
734  css: function (ajax, response, status) {
735    $(response.selector).css(response.argument);
736  },
737
738  /**
739   * Command to set the settings that will be used for other commands in this response.
740   */
741  settings: function (ajax, response, status) {
742    if (response.merge) {
743      $.extend(true, Drupal.settings, response.settings);
744    }
745    else {
746      ajax.settings = response.settings;
747    }
748  },
749
750  /**
751   * Command to attach data using jQuery's data API.
752   */
753  data: function (ajax, response, status) {
754    $(response.selector).data(response.name, response.value);
755  },
756
757  /**
758   * Command to apply a jQuery method.
759   */
760  invoke: function (ajax, response, status) {
761    var $element = $(response.selector);
762    $element[response.method].apply($element, response.arguments);
763  },
764
765  /**
766   * Command to restripe a table.
767   */
768  restripe: function (ajax, response, status) {
769    // :even and :odd are reversed because jQuery counts from 0 and
770    // we count from 1, so we're out of sync.
771    // Match immediate children of the parent element to allow nesting.
772    $('> tbody > tr:visible, > tr:visible', $(response.selector))
773      .removeClass('odd even')
774      .filter(':even').addClass('odd').end()
775      .filter(':odd').addClass('even');
776  },
777
778  /**
779   * Command to add css.
780   *
781   * Uses the proprietary addImport method if available as browsers which
782   * support that method ignore @import statements in dynamically added
783   * stylesheets.
784   */
785  add_css: function (ajax, response, status) {
786    // Add the styles in the normal way.
787    $('head').prepend(response.data);
788    // Add imports in the styles using the addImport method if available.
789    var match, importMatch = /^@import url\("(.*)"\);$/igm;
790    if (document.styleSheets[0].addImport && importMatch.test(response.data)) {
791      importMatch.lastIndex = 0;
792      while (match = importMatch.exec(response.data)) {
793        document.styleSheets[0].addImport(match[1]);
794      }
795    }
796  },
797
798  /**
799   * Command to update a form's build ID.
800   */
801  updateBuildId: function(ajax, response, status) {
802    $('input[name="form_build_id"][value="' + response['old'] + '"]').val(response['new']);
803  }
804};
805
806})(jQuery);
807;/*})'"*/;/*})'"*/
808(function ($) {
809
810  Drupal.honeypot = {};
811  Drupal.honeypot.timestampJS = new Date();
812
813  Drupal.behaviors.honeypotJS = {
814    attach: function (context, settings) {
815      $('form.honeypot-timestamp-js').once('honeypot-timestamp').bind('submit', function() {
816        var $honeypotTime = $(this).find('input[name="honeypot_time"]');
817        $honeypotTime.attr('value', Drupal.behaviors.honeypotJS.getIntervalTimestamp());
818      });
819    },
820    getIntervalTimestamp: function() {
821      var now = new Date();
822      var interval = Math.floor((now - Drupal.honeypot.timestampJS) / 1000);
823      return Drupal.settings.honeypot.jsToken + '|' + interval;
824    }
825  };
826
827  if (Drupal.ajax && Drupal.ajax.prototype && Drupal.ajax.prototype.beforeSubmit) {
828    Drupal.ajax.prototype.honeypotOriginalBeforeSubmit = Drupal.ajax.prototype.beforeSubmit;
829    Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) {
830      if (this.form && $(this.form).hasClass('honeypot-timestamp-js')) {
831        for (key in form_values) {
832          // Inject the right interval timestamp.
833          if (form_values[key].name == 'honeypot_time' && form_values[key].value == 'no_js_available') {
834            form_values[key].value = Drupal.behaviors.honeypotJS.getIntervalTimestamp();
835          }
836        }
837      }
838
839      // Call the original function in case someone else has overridden it.
840      return Drupal.ajax.prototype.honeypotOriginalBeforeSubmit(form_values, element, options);
841    }
842  }
843
844}(jQuery));
845;/*})'"*/;/*})'"*/
846(function (D) {
847  var beforeSerialize = D.ajax.prototype.beforeSerialize;
vendor: 9,254 bytes, lines 848-859
848  D.ajax.prototype.beforeSerialize = function (element, options) {
849    beforeSerialize.call(this, element, options);
850    options.data['ajax_page_state[jquery_version]'] = D.settings.ajaxPageState.jquery_version;
851  }
852})(Drupal);
853;/*})'"*/;/*})'"*/
854/*!
855	Colorbox 1.6.4
856	license: MIT
857	http://www.jacklmoore.com/colorbox
858*/
859(function(t,e,i){function n(i,n,o){var r=e.createElement(i);return n&&(r.id=Z+n),o&&(r.style.cssText=o),t(r)}function o(){return i.innerHeight?i.innerHeight:t(i).height()}function r(e,i){i!==Object(i)&&(i={}),this.cache={},this.el=e,this.value=function(e){var n;return void 0===this.cache[e]&&(n=t(this.el).attr("data-cbox-"+e),void 0!==n?this.cache[e]=n:void 0!==i[e]?this.cache[e]=i[e]:void 0!==X[e]&&(this.cache[e]=X[e])),this.cache[e]},this.get=function(e){var i=this.value(e);return t.isFunction(i)?i.call(this.el,this):i}}function h(t){var e=W.length,i=(A+t)%e;return 0>i?e+i:i}function a(t,e){return Math.round((/%/.test(t)?("x"===e?E.width():o())/100:1)*parseInt(t,10))}function s(t,e){return t.get("photo")||t.get("photoRegex").test(e)}function l(t,e){return t.get("retinaUrl")&&i.devicePixelRatio>1?e.replace(t.get("photoRegex"),t.get("retinaSuffix")):e}function d(t){"contains"in x[0]&&!x[0].contains(t.target)&&t.target!==v[0]&&(t.stopPropagation(),x.focus())}function c(t){c.str!==t&&(x.add(v).removeClass(c.str).addClass(t),c.str=t)}function g(e){A=0,e&&e!==!1&&"nofollow"!==e?(W=t("."+te).filter(function(){var i=t.data(this,Y),n=new r(this,i);return n.get("rel")===e}),A=W.index(_.el),-1===A&&(W=W.add(_.el),A=W.length-1)):W=t(_.el)}function u(i){t(e).trigger(i),ae.triggerHandler(i)}function f(i){var o;if(!G){if(o=t(i).data(Y),_=new r(i,o),g(_.get("rel")),!U){U=$=!0,c(_.get("className")),x.css({visibility:"hidden",display:"block",opacity:""}),I=n(se,"LoadedContent","width:0; height:0; overflow:hidden; visibility:hidden"),b.css({width:"",height:""}).append(I),j=T.height()+k.height()+b.outerHeight(!0)-b.height(),D=C.width()+H.width()+b.outerWidth(!0)-b.width(),N=I.outerHeight(!0),z=I.outerWidth(!0);var h=a(_.get("initialWidth"),"x"),s=a(_.get("initialHeight"),"y"),l=_.get("maxWidth"),f=_.get("maxHeight");_.w=Math.max((l!==!1?Math.min(h,a(l,"x")):h)-z-D,0),_.h=Math.max((f!==!1?Math.min(s,a(f,"y")):s)-N-j,0),I.css({width:"",height:_.h}),J.position(),u(ee),_.get("onOpen"),O.add(F).hide(),x.focus(),_.get("trapFocus")&&e.addEventListener&&(e.addEventListener("focus",d,!0),ae.one(re,function(){e.removeEventListener("focus",d,!0)})),_.get("returnFocus")&&ae.one(re,function(){t(_.el).focus()})}var p=parseFloat(_.get("opacity"));v.css({opacity:p===p?p:"",cursor:_.get("overlayClose")?"pointer":"",visibility:"visible"}).show(),_.get("closeButton")?B.html(_.get("close")).appendTo(b):B.appendTo("<div/>"),w()}}function p(){x||(V=!1,E=t(i),x=n(se).attr({id:Y,"class":t.support.opacity===!1?Z+"IE":"",role:"dialog",tabindex:"-1"}).hide(),v=n(se,"Overlay").hide(),L=t([n(se,"LoadingOverlay")[0],n(se,"LoadingGraphic")[0]]),y=n(se,"Wrapper"),b=n(se,"Content").append(F=n(se,"Title"),R=n(se,"Current"),P=t('<button type="button"/>').attr({id:Z+"Previous"}),K=t('<button type="button"/>').attr({id:Z+"Next"}),S=t('<button type="button"/>').attr({id:Z+"Slideshow"}),L),B=t('<button type="button"/>').attr({id:Z+"Close"}),y.append(n(se).append(n(se,"TopLeft"),T=n(se,"TopCenter"),n(se,"TopRight")),n(se,!1,"clear:left").append(C=n(se,"MiddleLeft"),b,H=n(se,"MiddleRight")),n(se,!1,"clear:left").append(n(se,"BottomLeft"),k=n(se,"BottomCenter"),n(se,"BottomRight"))).find("div div").css({"float":"left"}),M=n(se,!1,"position:absolute; width:9999px; visibility:hidden; display:none; max-width:none;"),O=K.add(P).add(R).add(S)),e.body&&!x.parent().length&&t(e.body).append(v,x.append(y,M))}function m(){function i(t){t.which>1||t.shiftKey||t.altKey||t.metaKey||t.ctrlKey||(t.preventDefault(),f(this))}return x?(V||(V=!0,K.click(function(){J.next()}),P.click(function(){J.prev()}),B.click(function(){J.close()}),v.click(function(){_.get("overlayClose")&&J.close()}),t(e).bind("keydown."+Z,function(t){var e=t.keyCode;U&&_.get("escKey")&&27===e&&(t.preventDefault(),J.close()),U&&_.get("arrowKey")&&W[1]&&!t.altKey&&(37===e?(t.preventDefault(),P.click()):39===e&&(t.preventDefault(),K.click()))}),t.isFunction(t.fn.on)?t(e).on("click."+Z,"."+te,i):t("."+te).live("click."+Z,i)),!0):!1}function w(){var e,o,r,h=J.prep,d=++le;if($=!0,q=!1,u(he),u(ie),_.get("onLoad"),_.h=_.get("height")?a(_.get("height"),"y")-N-j:_.get("innerHeight")&&a(_.get("innerHeight"),"y"),_.w=_.get("width")?a(_.get("width"),"x")-z-D:_.get("innerWidth")&&a(_.get("innerWidth"),"x"),_.mw=_.w,_.mh=_.h,_.get("maxWidth")&&(_.mw=a(_.get("maxWidth"),"x")-z-D,_.mw=_.w&&_.w<_.mw?_.w:_.mw),_.get("maxHeight")&&(_.mh=a(_.get("maxHeight"),"y")-N-j,_.mh=_.h&&_.h<_.mh?_.h:_.mh),e=_.get("href"),Q=setTimeout(function(){L.show()},100),_.get("inline")){var c=t(e).eq(0);r=t("<div>").hide().insertBefore(c),ae.one(he,function(){r.replaceWith(c)}),h(c)}else _.get("iframe")?h(" "):_.get("html")?h(_.get("html")):s(_,e)?(e=l(_,e),q=_.get("createImg"),t(q).addClass(Z+"Photo").bind("error."+Z,function(){h(n(se,"Error").html(_.get("imgError")))}).one("load",function(){d===le&&setTimeout(function(){var e;_.get("retinaImage")&&i.devicePixelRatio>1&&(q.height=q.height/i.devicePixelRatio,q.width=q.width/i.devicePixelRatio),_.get("scalePhotos")&&(o=function(){q.height-=q.height*e,q.width-=q.width*e},_.mw&&q.width>_.mw&&(e=(q.width-_.mw)/q.width,o()),_.mh&&q.height>_.mh&&(e=(q.height-_.mh)/q.height,o())),_.h&&(q.style.marginTop=Math.max(_.mh-q.height,0)/2+"px"),W[1]&&(_.get("loop")||W[A+1])&&(q.style.cursor="pointer",t(q).bind("click."+Z,function(){J.next()})),q.style.width=q.width+"px",q.style.height=q.height+"px",h(q)},1)}),q.src=e):e&&M.load(e,_.get("data"),function(e,i){d===le&&h("error"===i?n(se,"Error").html(_.get("xhrError")):t(this).contents())})}var v,x,y,b,T,C,H,k,W,E,I,M,L,F,R,S,K,P,B,O,_,j,D,N,z,A,q,U,$,G,Q,J,V,X={html:!1,photo:!1,iframe:!1,inline:!1,transition:"elastic",speed:300,fadeOut:300,width:!1,initialWidth:"600",innerWidth:!1,maxWidth:!1,height:!1,initialHeight:"450",innerHeight:!1,maxHeight:!1,scalePhotos:!0,scrolling:!0,opacity:.9,preloading:!0,className:!1,overlayClose:!0,escKey:!0,arrowKey:!0,top:!1,bottom:!1,left:!1,right:!1,fixed:!1,data:void 0,closeButton:!0,fastIframe:!0,open:!1,reposition:!0,loop:!0,slideshow:!1,slideshowAuto:!0,slideshowSpeed:2500,slideshowStart:"start slideshow",slideshowStop:"stop slideshow",photoRegex:/\.(gif|png|jp(e|g|eg)|bmp|ico|webp|jxr|svg)((#|\?).*)?$/i,retinaImage:!1,retinaUrl:!1,retinaSuffix:"@2x.$1",current:"image {current} of {total}",previous:"previous",next:"next",close:"close",xhrError:"This content failed to load.",imgError:"This image failed to load.",returnFocus:!0,trapFocus:!0,onOpen:!1,onLoad:!1,onComplete:!1,onCleanup:!1,onClosed:!1,rel:function(){return this.rel},href:function(){return t(this).attr("href")},title:function(){return this.title},createImg:function(){var e=new Image,i=t(this).data("cbox-img-attrs");return"object"==typeof i&&t.each(i,function(t,i){e[t]=i}),e},createIframe:function(){var i=e.createElement("iframe"),n=t(this).data("cbox-iframe-attrs");return"object"==typeof n&&t.each(n,function(t,e){i[t]=e}),"frameBorder"in i&&(i.frameBorder=0),"allowTransparency"in i&&(i.allowTransparency="true"),i.name=(new Date).getTime(),i.allowFullscreen=!0,i}},Y="colorbox",Z="cbox",te=Z+"Element",ee=Z+"_open",ie=Z+"_load",ne=Z+"_complete",oe=Z+"_cleanup",re=Z+"_closed",he=Z+"_purge",ae=t("<a/>"),se="div",le=0,de={},ce=function(){function t(){clearTimeout(h)}function e(){(_.get("loop")||W[A+1])&&(t(),h=setTimeout(J.next,_.get("slideshowSpeed")))}function i(){S.html(_.get("slideshowStop")).unbind(s).one(s,n),ae.bind(ne,e).bind(ie,t),x.removeClass(a+"off").addClass(a+"on")}function n(){t(),ae.unbind(ne,e).unbind(ie,t),S.html(_.get("slideshowStart")).unbind(s).one(s,function(){J.next(),i()}),x.removeClass(a+"on").addClass(a+"off")}function o(){r=!1,S.hide(),t(),ae.unbind(ne,e).unbind(ie,t),x.removeClass(a+"off "+a+"on")}var r,h,a=Z+"Slideshow_",s="click."+Z;return function(){r?_.get("slideshow")||(ae.unbind(oe,o),o()):_.get("slideshow")&&W[1]&&(r=!0,ae.one(oe,o),_.get("slideshowAuto")?i():n(),S.show())}}();t[Y]||(t(p),J=t.fn[Y]=t[Y]=function(e,i){var n,o=this;return e=e||{},t.isFunction(o)&&(o=t("<a/>"),e.open=!0),o[0]?(p(),m()&&(i&&(e.onComplete=i),o.each(function(){var i=t.data(this,Y)||{};t.data(this,Y,t.extend(i,e))}).addClass(te),n=new r(o[0],e),n.get("open")&&f(o[0])),o):o},J.position=function(e,i){function n(){T[0].style.width=k[0].style.width=b[0].style.width=parseInt(x[0].style.width,10)-D+"px",b[0].style.height=C[0].style.height=H[0].style.height=parseInt(x[0].style.height,10)-j+"px"}var r,h,s,l=0,d=0,c=x.offset();if(E.unbind("resize."+Z),x.css({top:-9e4,left:-9e4}),h=E.scrollTop(),s=E.scrollLeft(),_.get("fixed")?(c.top-=h,c.left-=s,x.css({position:"fixed"})):(l=h,d=s,x.css({position:"absolute"})),d+=_.get("right")!==!1?Math.max(E.width()-_.w-z-D-a(_.get("right"),"x"),0):_.get("left")!==!1?a(_.get("left"),"x"):Math.round(Math.max(E.width()-_.w-z-D,0)/2),l+=_.get("bottom")!==!1?Math.max(o()-_.h-N-j-a(_.get("bottom"),"y"),0):_.get("top")!==!1?a(_.get("top"),"y"):Math.round(Math.max(o()-_.h-N-j,0)/2),x.css({top:c.top,left
859:c.left,visibility:"visible"}),y[0].style.width=y[0].style.height="9999px",r={width:_.w+z+D,height:_.h+N+j,top:l,left:d},e){var g=0;t.each(r,function(t){return r[t]!==de[t]?(g=e,void 0):void 0}),e=g}de=r,e||x.css(r),x.dequeue().animate(r,{duration:e||0,complete:function(){n(),$=!1,y[0].style.width=_.w+z+D+"px",y[0].style.height=_.h+N+j+"px",_.get("reposition")&&setTimeout(function(){E.bind("resize."+Z,J.position)},1),t.isFunction(i)&&i()},step:n})},J.resize=function(t){var e;U&&(t=t||{},t.width&&(_.w=a(t.width,"x")-z-D),t.innerWidth&&(_.w=a(t.innerWidth,"x")),I.css({width:_.w}),t.height&&(_.h=a(t.height,"y")-N-j),t.innerHeight&&(_.h=a(t.innerHeight,"y")),t.innerHeight||t.height||(e=I.scrollTop(),I.css({height:"auto"}),_.h=I.height()),I.css({height:_.h}),e&&I.scrollTop(e),J.position("none"===_.get("transition")?0:_.get("speed")))},J.prep=function(i){function o(){return _.w=_.w||I.width(),_.w=_.mw&&_.mw<_.w?_.mw:_.w,_.w}function a(){return _.h=_.h||I.height(),_.h=_.mh&&_.mh<_.h?_.mh:_.h,_.h}if(U){var d,g="none"===_.get("transition")?0:_.get("speed");I.remove(),I=n(se,"LoadedContent").append(i),I.hide().appendTo(M.show()).css({width:o(),overflow:_.get("scrolling")?"auto":"hidden"}).css({height:a()}).prependTo(b),M.hide(),t(q).css({"float":"none"}),c(_.get("className")),d=function(){function i(){t.support.opacity===!1&&x[0].style.removeAttribute("filter")}var n,o,a=W.length;U&&(o=function(){clearTimeout(Q),L.hide(),u(ne),_.get("onComplete")},F.html(_.get("title")).show(),I.show(),a>1?("string"==typeof _.get("current")&&R.html(_.get("current").replace("{current}",A+1).replace("{total}",a)).show(),K[_.get("loop")||a-1>A?"show":"hide"]().html(_.get("next")),P[_.get("loop")||A?"show":"hide"]().html(_.get("previous")),ce(),_.get("preloading")&&t.each([h(-1),h(1)],function(){var i,n=W[this],o=new r(n,t.data(n,Y)),h=o.get("href");h&&s(o,h)&&(h=l(o,h),i=e.createElement("img"),i.src=h)})):O.hide(),_.get("iframe")?(n=_.get("createIframe"),_.get("scrolling")||(n.scrolling="no"),t(n).attr({src:_.get("href"),"class":Z+"Iframe"}).one("load",o).appendTo(I),ae.one(he,function(){n.src="//about:blank"}),_.get("fastIframe")&&t(n).trigger("load")):o(),"fade"===_.get("transition")?x.fadeTo(g,1,i):i())},"fade"===_.get("transition")?x.fadeTo(g,0,function(){J.position(0,d)}):J.position(g,d)}},J.next=function(){!$&&W[1]&&(_.get("loop")||W[A+1])&&(A=h(1),f(W[A]))},J.prev=function(){!$&&W[1]&&(_.get("loop")||A)&&(A=h(-1),f(W[A]))},J.close=function(){U&&!G&&(G=!0,U=!1,u(oe),_.get("onCleanup"),E.unbind("."+Z),v.fadeTo(_.get("fadeOut")||0,0),x.stop().fadeTo(_.get("fadeOut")||0,0,function(){x.hide(),v.hide(),u(he),I.remove(),setTimeout(function(){G=!1,u(re),_.get("onClosed")},1)}))},J.remove=function(){x&&(x.stop(),t[Y].close(),x.stop(!1,!0).remove(),v.remove(),G=!1,x=null,t("."+te).removeData(Y).removeClass(te),t(e).unbind("click."+Z).unbind("keydown."+Z))},J.element=function(){return t(_.el)},J.settings=X)})(jQuery,document,window);
860;/*})'"*/;/*})'"*/
861/**
862 * @file
863 * Colorbox module init js.
864 */
865
866(function ($) {
867
868Drupal.behaviors.initColorbox = {
869  attach: function (context, settings) {
870    if (!$.isFunction($('a, area, input', context).colorbox) || typeof settings.colorbox === 'undefined') {
871      return;
872    }
873
874    if (settings.colorbox.mobiledetect && window.matchMedia) {
875      // Disable Colorbox for small screens.
876      var mq = window.matchMedia("(max-device-width: " + settings.colorbox.mobiledevicewidth + ")");
877      if (mq.matches) {
878        return;
879      }
880    }
881
882    // Use "data-colorbox-gallery" if set otherwise use "rel".
883    settings.colorbox.rel = function () {
884      if ($(this).data('colorbox-gallery')) {
885        return $(this).data('colorbox-gallery');
886      }
887      else {
888        return $(this).attr('rel');
889      }
890    };
891
892    $('.colorbox', context)
893      .once('init-colorbox').each(function(){
894        // Only images are supported for the "colorbox" class.
895        // The "photo" setting forces the href attribute to be treated as an image.
896        var extendParams = {
897          photo: true
898        };
899        // If a title attribute is supplied, sanitize it.
900        var title = $(this).attr('title');
901        if (title) {
902          extendParams.title = Drupal.checkPlain(title);
903        }
904        $(this).colorbox($.extend({}, settings.colorbox, extendParams));
905      });
906
907    $(context).bind('cbox_complete', function () {
908      Drupal.attachBehaviors('#cboxLoadedContent');
909    });
910  }
911};
912
913})(jQuery);
914;/*})'"*/;/*})'"*/
915/**
916 * @file
917 * Colorbox module load js.
918 */
919(function ($) {
920
921Drupal.behaviors.initColorboxLoad = {
922  attach: function (context, settings) {
923    if (!$.isFunction($.colorbox) || typeof settings.colorbox === 'undefined') {
924      return;
925    }
926
927    if (settings.colorbox.mobiledetect && window.matchMedia) {
928      // Disable Colorbox for small screens.
929      var mq = window.matchMedia("(max-device-width: " + settings.colorbox.mobiledevicewidth + ")");
930      if (mq.matches) {
931        return;
932      }
933    }
934
935    $.urlParams = function (url) {
936      var p = {},
937          e,
938          a = /\+/g,  // Regex for replacing addition symbol with a space.
939          r = /([^&=]+)=?([^&]*)/g,
940          d = function (s) { return decodeURIComponent(s.replace(a, ' ')); },
941          q = url.split('?');
942      while (e = r.exec(q[1])) {
943        e[1] = d(e[1]);
944        e[2] = d(e[2]);
945        switch (e[2].toLowerCase()) {
946          case 'true':
947          case 'yes':
948            e[2] = true;
949            break;
950          case 'false':
951          case 'no':
952            e[2] = false;
953            break;
954        }
955        if (e[1] == 'width') { e[1] = 'innerWidth'; }
956        if (e[1] == 'height') { e[1] = 'innerHeight'; }
957        if (e[2]) {
958          e[2] = Drupal.checkPlain(e[2]);
959        }
960        p[e[1]] = e[2];
961      }
962      return p;
963    };
964
965    $('.colorbox-load', context)
966      .once('init-colorbox-load', function () {
967        var href = $(this).attr('href');
968        if (!hrefIsSafe(href)) {
969          return;
970        }
971
972        var params = $.urlParams(href);
973        if (!params.hasOwnProperty('title')) {
974          // If a title attribute is supplied, sanitize it.
975          var title = $(this).attr('title');
976          if (title) {
977            params.title = Drupal.checkPlain(title);
978          }
979        }
980        $(this).colorbox($.extend({}, settings.colorbox, params));
981      });
982  }
983};
984
985/**
986 * Returns true if the passed-in href string is safe for colorbox_load.
987 *
988 * @param href
989 *   The href string to be tested.
990 *
991 * @return
992 *   Boolean true if the href is safe.
993 */
994function hrefIsSafe(href) {
995  var normalizedUrl = Drupal.absoluteUrl(href);
996
997  // Only local, non-file-system URLs are allowed.
998  if (!Drupal.urlIsLocal(normalizedUrl)) {
999    return false;
1000  }
1001
1002  // Reject uploaded files from the public or private file system.
1003  if (normalizedUrl.indexOf(Drupal.settings.colorbox.file_public_path) !== -1 ||
1004    normalizedUrl.match(/\/system\/files\//) ||
1005    normalizedUrl.match(/[?|&]q=system\/files\//)) {
1006    return false;
1007  }
1008
1009  // All checks passed.
1010  return true;
1011}
1012
1013})(jQuery);
1014;/*})'"*/;/*})'"*/
1015/**
1016 * @file
1017 * Colorbox module inline js.
1018 */
1019
1020(function ($) {
1021
1022Drupal.behaviors.initColorboxInline = {
1023  attach: function (context, settings) {
1024    if (!$.isFunction($.colorbox) || typeof settings.colorbox === 'undefined') {
1025      return;
1026    }
1027
1028    if (settings.colorbox.mobiledetect && window.matchMedia) {
1029      // Disable Colorbox for small screens.
1030      var mq = window.matchMedia("(max-device-width: " + settings.colorbox.mobiledevicewidth + ")");
1031      if (mq.matches) {
1032        return;
1033      }
1034    }
1035
1036    $.urlParam = function(name, url){
1037      if (name == 'fragment') {
1038        var results = new RegExp('(#[^&#]*)').exec(url);
1039      }
1040      else {
1041        var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(url);
1042      }
1043      if (!results) { return ''; }
1044      return results[1] || '';
1045    };
1046    $('.colorbox-inline', context).once('init-colorbox-inline').colorbox({
1047      transition:settings.colorbox.transition,
1048      speed:settings.colorbox.speed,
1049      opacity:settings.colorbox.opacity,
1050      slideshow:settings.colorbox.slideshow,
1051      slideshowAuto:settings.colorbox.slideshowAuto,
1052      slideshowSpeed:settings.colorbox.slideshowSpeed,
1053      slideshowStart:settings.colorbox.slideshowStart,
1054      slideshowStop:settings.colorbox.slideshowStop,
1055      current:settings.colorbox.current,
1056      previous:settings.colorbox.previous,
1057      next:settings.colorbox.next,
1058      close:settings.colorbox.close,
1059      overlayClose:settings.colorbox.overlayClose,
1060      maxWidth:settings.colorbox.maxWidth,
1061      maxHeight:settings.colorbox.maxHeight,
1062      innerWidth:function(){
1063        return $.urlParam('width', $(this).attr('href'));
1064      },
1065      innerHeight:function(){
1066        return $.urlParam('height', $(this).attr('href'));
1067      },
1068      title:function(){
1069        return Drupal.checkPlain(decodeURIComponent(Drupal.checkPlain(
1070          $.urlParam('title', $(this).attr('href')))));
1071      },
1072      iframe:function(){
1073        return $.urlParam('iframe', $(this).attr('href'));
1074      },
1075      inline:function(){
1076        return $.urlParam('inline', $(this).attr('href'));
1077      },
1078      href:function(){
1079        return $.urlParam('fragment', $(this).attr('href'));
1080      }
1081    });
1082  }
1083};
1084
1085})(jQuery);
1086;/*})'"*/;/*})'"*/
1087/**
1088 * Async loader for rotating idea banners.
1089 */
1090(function ($, Drupal) {
1091  Drupal.behaviors.ltIdeaBanners = {
1092    attach: function (context) {
1093      $('.js-lt-idea-banner', context).once('lt-idea-banner', function () {
1094        var settings = Drupal.settings.ltadvertisingIdeas || {};
1095        var maxViewport = parseInt(settings.maxViewport, 10) || 0;
1096        if (maxViewport && window.innerWidth > maxViewport) {
1097          $(this).remove();
1098          return;
1099        }
1100
1101        var $slot = $(this);
1102        var $placeholder = $slot.find('.lt-idea-banner__placeholder');
1103        if (!$placeholder.length) {
1104          $placeholder = $('<div class="lt-idea-banner__placeholder"></div>').appendTo($slot.empty());
1105        }
1106
1107        var isDark = $('body').hasClass('dark') || $('body').hasClass('theme-dark');
1108        $placeholder
1109          .removeClass('lt-idea-banner__placeholder--light lt-idea-banner__placeholder--dark')
1110          .addClass(isDark ? 'lt-idea-banner__placeholder--dark' : 'lt-idea-banner__placeholder--light');
1111
1112        var basePath = (Drupal.settings && Drupal.settings.basePath) ? Drupal.settings.basePath : '/';
1113        var endpoint = settings.endpoint || (basePath + 'callback/adv/idea-banner');
1114
1115        $.ajax({
1116          url: endpoint,
1117          dataType: 'json',
1118          cache: false,
1119        }).done(function (response) {
1120          if (!response || response.status !== 'ok' || !response.data) {
1121            $slot.remove();
1122            return;
1123          }
1124
1125          var data = response.data;
1126          var $link = $('<a/>', {
1127            'class': 'lt-idea-banner__link',
1128            href: data.link_url,
1129            rel: 'nofollow noopener',
1130          });
1131          var $img = $('<img/>', {
1132            'class': 'lt-idea-banner__img',
1133            alt: data.alt || data.title || ''
1134          }).css({
1135            width: '100%',
1136            height: 'auto',
1137            display: 'block'
1138          });
1139
1140          $img.attr('src', data.banner_url);
1141          $link.append($img);
1142
1143          var renderBanner = function () {
1144            $img.off('.ltIdeaBanner');
1145            $slot.empty().append($link);
1146          };
1147
1148          $img
1149            .on('load.ltIdeaBanner', renderBanner)
1150            .on('error.ltIdeaBanner', function () {
1151              $slot.remove();
1152            });
1153
1154          if ($img[0].complete && $img[0].naturalWidth !== 0) {
1155            renderBanner();
1156          } else {
1157            if (!$placeholder.parent().length) {
1158              $slot.empty().append($placeholder);
1159            }
1160          }
1161        }).fail(function () {
1162          $slot.remove();
1163        });
1164      });
1165    }
1166  };
1167})(jQuery, Drupal);
1168;/*})'"*/;/*})'"*/
1169(function($) {
1170$(document).ready(function(){
1171
1172  $(document).on('click', '.thanks-button.greenbutton', function() {
1173    let $button = $(this);
1174    let $currentContainer = $button.closest('.thanks-button-container');
1175    let nid = $button.attr('nid');
1176
1177    languagevar = Drupal.settings.languagevar;
1178    url = languagevar ? '/' + languagevar : '';
1179    url += '/ajax/ltthanksbutton';
1180
1181    $button.attr('nid', '');
1182    $button.removeClass('greenbutton');
1183    $button.addClass('greenbuttondisable');
1184    $button.addClass("ajax_loading");
1185
1186    $button.find('.heart').hide();
1187
1188    $.ajax({
1189      type: "POST",
1190      dataType: 'json',
1191      url: url,
1192      data: {
1193        nid: nid
1194      },
1195      success: function(data) {
1196        $currentContainer.html(data.button);
1197
1198        $currentContainer.closest('.thankstable').find('a#displaythanks').text(data.text);
1199
1200        if (typeof gtag === 'function') {
1201          gtag('event', 'Thanks', {});
1202        }
1203      }
1204    });
1205
1206    return false;
1207  });
1208
1209  // New design thanks button (collection page, etc.)
1210  $(document).on('click', '.collection-thanks-btn, .thanks-details__btn, .who-side__btn[data-nid]', function(e) {
1211    e.preventDefault();
1212    var $btn = $(this);
1213    var nid = $btn.data('nid');
1214    if (!nid || $btn.is(':disabled')) return;
1215
1216    var wasThanked = !!$btn.data('thanked');
1217    var languagevar = Drupal.settings.languagevar;
1218    var url = languagevar ? '/' + languagevar : '';
1219    url += '/ajax/ltthanksbutton';
1220
1221    $btn.prop('disabled', true).addClass('ajax_loading_small');
1222
1223    $.ajax({
1224      type: 'POST',
1225      dataType: 'json',
1226      url: url,
1227      data: { nid: nid }
1228    }).done(function(res) {
1229      var isThanked = !!res.thanked;
1230      var stateChanged = (isThanked !== wasThanked);
1231      var isLoggedIn = $('body').hasClass('logged-in');
1232      if (stateChanged && typeof gtag === 'function') {
1233        gtag('event', 'Thanks', {});
1234      }
1235      // Prefer the server-rendered label from PHP (always translated). Fall
1236      // back to Drupal.t() only if the AJAX response omits it.
1237      var label = res.label_text
1238        || (Drupal.t ? Drupal.t(isThanked ? 'Thanked' : 'Say thanks')
1239                     : (isThanked ? 'Thanked' : 'Say thanks'));
1240      // Sync all thanks buttons with same nid on the page
1241      $('.collection-thanks-btn[data-nid="' + nid + '"], .thanks-detail
1241s__btn[data-nid="' + nid + '"], .who-side__btn[data-nid="' + nid + '"]').each(function() {
1242        var $b = $(this);
1243        $b.find('.text').text(label);
1244        $b.data('thanked', isThanked ? 1 : 0);
1245        $b.toggleClass('_thanked', isThanked);
1246        // Guests: keep disabled after thanking (no toggle)
1247        $b.prop('disabled', isThanked && !isLoggedIn);
1248      });
1249      // Update thanks counters only if state actually changed
1250      if (stateChanged) {
1251        var delta = isThanked ? 1 : -1;
1252        $('.collection-body__thanks-count .num, .row-details__likes .num, .translate__thanks-count .num, .who-side__thanks-count span:not(.icon)').each(function() {
1253          var c = parseInt($.trim($(this).text()), 10);
1254          if (!isNaN(c)) {
1255            var newCount = Math.max(0, c + delta);
1256            $(this).text(newCount);
1257            var $counter = $(this).closest('.translate__thanks-count, .row-details__likes');
1258            if ($counter.length) $counter.toggle(newCount > 0);
1259          }
1260        });
1261      }
1262    }).fail(function() {
1263      $btn.prop('disabled', false);
1264    }).always(function() {
1265      $btn.removeClass('ajax_loading_small');
1266    });
1267  });
1268
1269  // Thanks details popup (works on song, translation, collection pages)
1270  var thanksPopupSelector = '[data-thanks-nid]';
1271  var activeThanksPopup = null;
1272
1273  function positionPopup($popup, $trigger) {
1274    var el = $popup[0];
1275    var btnRect = $trigger[0].getBoundingClientRect();
1276    var padding = 8;
1277
1278    // Need to make popup visible to measure it
1279    el.style.visibility = 'hidden';
1280    el.style.display = 'block';
1281    var menuRect = el.getBoundingClientRect();
1282    el.style.visibility = '';
1283    el.style.display = '';
1284
1285    var top = btnRect.bottom + padding;
1286    var fitsBelow = (top + menuRect.height) <= (window.innerHeight - padding);
1287    var fitsAbove = (btnRect.top - menuRect.height - padding) >= padding;
1288
1289    if (!fitsBelow && fitsAbove) {
1290      top = btnRect.top - menuRect.height - padding;
1291    } else if (!fitsBelow && !fitsAbove) {
1292      var spaceBelow = window.innerHeight - btnRect.bottom - padding;
1293      var spaceAbove = btnRect.top - padding;
1294      if (spaceBelow >= spaceAbove) {
1295        top = btnRect.bottom + padding;
1296        el.style.maxHeight = spaceBelow + 'px';
1297      } else {
1298        top = padding;
1299        el.style.maxHeight = spaceAbove + 'px';
1300      }
1301      el.style.overflowY = 'auto';
1302    }
1303
1304    // Align to left of trigger, but don't overflow right
1305    var left = btnRect.left;
1306    if (left + menuRect.width > window.innerWidth - padding) {
1307      left = window.innerWidth - menuRect.width - padding;
1308    }
1309    if (left < padding) { left = padding; }
1310
1311    el.style.position = 'absolute';
1312    el.style.top = (top + window.scrollY) + 'px';
1313    el.style.left = (left + window.scrollX) + 'px';
1314    el.style.bottom = 'auto';
1315    el.style.right = 'auto';
1316    el.style.marginTop = '0';
1317  }
1318
1319  function closeThanksPopup() {
1320    if (activeThanksPopup) {
1321      activeThanksPopup.removeClass('-visible');
1322      var $p = activeThanksPopup;
1323      setTimeout(function() { $p.remove(); }, 200);
1324      activeThanksPopup = null;
1325    }
1326  }
1327  $(document).on('click', function(e) {
1328    if (activeThanksPopup && !$(e.target).closest('.like-users-popup, ' + thanksPopupSelector).length) {
1329      closeThanksPopup();
1330    }
1331  });
1332  $(document).on('keydown', function(e) {
1333    if (e.key === 'Escape' && activeThanksPopup) { closeThanksPopup(); }
1334  });
1335  $(document).on('click', thanksPopupSelector, function(e){
1336    try {
1337      if ($(e.target).closest('.like-users-popup a').length) { return; }
1338      e.preventDefault();
1339      e.stopPropagation();
1340      var nid = $(this).data('thanks-nid');
1341      if (!nid) { return; }
1342
1343      closeThanksPopup();
1344
1345      var $trigger = $(this);
1346      var languagevar = (typeof Drupal !== 'undefined' && Drupal.settings && Drupal.settings.languagevar) ? Drupal.settings.languagevar : '';
1347      var url = (languagevar ? '/' + languagevar : '') + '/callback/get/thankslist';
1348      var t = (window.Drupal && Drupal.t) ? Drupal.t : function(s){ return s; };
1349
1350      var $popup = $('<div class="like-users-popup -loading -global">
1350' +
1351        '<div class="like-users-popup__header">' + t('Thanked by') + '</div>' +
1352        '<ul class="like-users-popup__list"></ul>' +
1353        '</div>');
1354
1355      $(document.body).append($popup);
1356      setTimeout(function() {
1357        positionPopup($popup, $trigger);
1358        $popup.addClass('-visible');
1359      }, 10);
1360      activeThanksPopup = $popup;
1361
1362      $.ajax({
1363        type: 'POST',
1364        url: url,
1365        dataType: 'json',
1366        data: { nid: nid }
1367      }).done(function(res){
1368        $popup.removeClass('-loading');
1369        var html = (res && res.html) ? res.html : '';
1370        if (!html) {
1371          $popup.find('.like-users-popup__list').html('<li class="like-users-popup__item">' + t('No data') + '</li>');
1372          return;
1373        }
1374        var $temp = $('<div>').html(html);
1375        var items = [];
1376        $temp.find('.table__trow').each(function() {
1377          var $username = $(this).find('.ltnodethanks-username');
1378          var $date = $(this).find('.ltnodethanks-date');
1379          if ($username.length) {
1380            var $link = $username.find('a');
1381            var nameHtml = $link.length ? '<a href="' + $link.attr('href') + '">' + $link.text() + '</a>' : $username.text();
1382            var dateText = $date.length ? '<span class="like-users-popup__date">' + $date.text() + '</span>' : '';
1383            items.push('<li class="like-users-popup__item">' + nameHtml + dateText + '</li>');
1384          }
1385        });
1386        var $guests = $temp.find('.ltnodethanks-guests');
1387        if ($guests.length) {
1388          items.push('<li class="like-users-popup__more">' + $guests.text() + '</li>');
1389        }
1390        if (items.length) {
1391          $popup.find('.like-users-popup__list').html(items.join(''));
1392          positionPopup($popup, $trigger);
1393        } else {
1394          $popup.find('.like-users-popup__list').html('<li class="like-users-popup__item">' + t('No data') + '</li>');
1395        }
1396      }).fail(function(){
1397        $popup.removeClass('-loading');
1398        $popup.find('.like-users-popup__list').html('<li class="like-users-popup__item">' + t('Failed to load') + '</li>');
1399      });
1400    } catch(err) {}
1401  });
1402
1403  $(document).on('click', '.displaythanks-link', function(e) {
1404    let nid = $(this).data('nid');
1405    let $thanksContainer = $('#ltnodethanks-' + nid);
1406
1407    languagevar = Drupal.settings.languagevar;
1408    url = languagevar ? '/' + languagevar : '';
1409    url += '/callback/get/thankslist';
1410
1411    $.ajax({
1412      type: "POST",
1413      dataType: 'json',
1414      url: url,
1415      data: {
1416        nid: nid
1417      },
1418      success: function(data) {
1419        $thanksContainer.html(data.html);
1420        $thanksContainer.css('background', '');
1421      }
1422    });
1423  });
1424
1425});
1426})(jQuery);
1427;/*})'"*/;/*})'"*/
1428$(document).ready(function(){
1429
1430  var tooltipsterConfig = {
1431    content: 'Loading...',
1432    delay: 700,
1433    side: ['right', 'top', 'bottom', 'left'],
1434    contentAsHTML: true,
1435    theme: ['tooltipster-shadow', 'tooltipster-userinfo'],
1436    maxWidth: 280,
1437    minWidth: 280,
1438    updateAnimation: null,
1439    interactive: true,
1440    functionBefore: function(instance, helper) {
1441
1442        var $origin = $(helper.origin);
1443
1444        languagevar = Drupal.settings.languagevar;
1445        url = languagevar ? '/' + languagevar : '';
1446        url += '/callback/get/userinfo';
1447
1448        // we set a variable so the data is only loaded once via Ajax, not every time the tooltip opens
1449        if ($origin.data('loaded') !== true) {
1450
1451          var $uid = instance.elementOrigin().slot || instance.elementOrigin().rel;
1452
1453          if ($uid !== 'nofollow') {
1454
1455            $.get(url, { id: $uid }, function(data) {
1456
1457                // call the 'content' method to update the content of our tooltip with the returned data.
1458                instance.content(data);
1459
1460                // to remember that the data has been loaded
1461                $origin.data('loaded', true);
1462            });
1463          }
1464        }
1465    }
1466  };
1467
1468  // Lazy init: only create tooltipster on first hover
1469  $(document).on('mouseenter', '.userpopupinfo', function() {
1470    var $el = $(this);
1471    if ($el.hasClass('tooltipstered')) return; // already initialized
1472    $el.tooltipster(tooltipsterConfig);
1473    $el.tooltipster('open');
1474  });
1475
1476});
1477;/*})'"*/;/*})'"*/
1478function addSpinner(el) {
1479    el.length || (el = $(el));
1480    
1481    el.addClass('spinner');
1482}
1483
1484
1485function removeSpinner(el) {
1486    el.length || (el = $(el));
1487    el.length || (el = $('body'));
1488    
1489    el.removeClass('spinner');
1490}
1491;/*})'"*/;/*})'"*/
1492var utils = {
1493  escapeRegExChars: function (value) {
1494    return value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
1495  }
1496};
1497
1498$(document).ready(function () {
1499  // Hide stale Devbridge autocomplete dropdowns when returning via back/forward cache
1500  $(window).on('pageshow', function(event) {
1501    var isBackForward = false;
1502    try {
1503      var navEntries = (window.performance && performance.getEntriesByType) ? performance.getEntriesByType('navigation') : [];
1504      if (navEntries && navEntries.length && navEntries[0].type === 'back_forward') {
1505        isBackForward = true;
1506      }
1507    } catch (e) {}
1508    if (!isBackForward && event && event.originalEvent && event.originalEvent.persisted) {
1509      isBackForward = true;
1510    }
1511
1512    if (isBackForward) {
1513      $('.autocomplete-suggestions').hide();
1514    }
1515  });
1516
1517  var searchwidth = (deviceType() === 'mobile') ? '280' : 'auto';
1518  var searchheight = (deviceType() === 'mobile') ? '300' : '500';
1519
1520  var serviceUrl = '/' + Drupal.settings.languagevarq + '/ajax/lyricstranslategoogleautocomplete/autocomplete';
1521  // Check if artistnid is set and append it as a query parameter
1522  if (typeof Drupal.settings.artistnid !== 'undefined') {
1523    serviceUrl += '?artistnid=' + encodeURIComponent(Drupal.settings.artistnid);
1524  }
1525
1526  // Initialize autocomplete (exclude header input to avoid double-binding with Algolia)
1527  var searchforminputs = $('#edit-filter3');
1528
1529  if (searchforminputs.length) {
1530    searchforminputs.autocomplete({
1531      serviceUrl: serviceUrl, //
1532      minChars: 2,
1533      deferRequestBy: 150,
1534      width: searchwidth,
1535      maxHeight: searchheight,
1536      groupBy: 'category',
1537      triggerSelectOnValidInput: false,
1538      preventBadQueries: false,
1539      preserveInput: true,
1540      onSearchStart: function(params){
1541        try {
1542          // Show placeholder row after a short delay to avoid flicker on fast responses
1543          if (window.__acLoadingTimer) { clearTimeout(window.__acLoadingTimer); }
1544          var ac = $(this).data('autocomplete');
1545          window.__acLoadingTimer = setTimeout(function(){
1546            try {
1547              if (!ac || !ac.suggestionsContainer) { return; }
1548              var $box = $(ac.suggestionsContainer);
1549              var text = (window.Drupal && Drupal.t) ? Drupal.t('Loading…') : 'Loading…';
1550              var html = '<div class="autocomplete-suggestion ac-loading-item" data-index="-1">'
1551                + '<span class="ajax_loading_small" style="display:inline-block;width:16px;height:16px;margin:0 8px 0 4px;vertical-align:middle;"></span>'
1552                + '<span class="text">' + text + '</span>'
1553                + '</div>';
1554              ac.fixPosition();
1555              $box.html(html).show();
1556              ac.visible = true;
1557              ac.selectedIndex = -1;
1558            } catch(e) {}
1559          }, 250);
1560        } catch(e) {}
1561      },
1562      onSearchComplete: function(query, suggestions){
1563        try {
1564          if (window.__acLoadingTimer) { clearTimeout(window.__acLoadingTimer); window.__acLoadingTimer = null; }
1565          var ac = this;
1566          if (ac && ac.suggestionsContainer) { ac.suggestionsContainer.find('.ac-loading-item').remove(); }
1567        } catch(e) {}
1568      },
1569      beforeRender: function(container){
1570        try { $(container).find('.ac-loading-item').remove(); } catch(e) {}
1571      },
1572      onHide: function(container){
1573        try { $(container).find('.ac-loading-item').remove(); } catch(e) {}
1574      },
1575      onSearchError: function(query, jqXHR, textStatus, errorThrown){
1576        try {
1577          if (window.__acLoadingTimer) { clearTimeout(window.__acLoadingTimer); window.__acLoadingTimer = null; }
1578          var ac = this;
1579          if (ac && ac.suggestionsContainer) { ac.suggestionsContainer.find('.ac-loading-item').remove(); }
1580        } catch(e) {}
1581      },
1582      onSelect: function(suggestion){
1583        var form = $(this).closest("form");
1584        var submit = form.find(':submit');
1585        submit.addClass("ajax_loading_small");
1586
1587        $(this).autocomplete('close');
1588        $(this).autocomplete('disable');
1589
1590        if (suggestion.data.url) {
1591           window.location.href = suggestion.data.url;
1592           if (typeof gtag !== 'undefined') {
1593             gtag('event', 'search_suggestion_click', {});
1594           }
1595        }
1596        else {
1597          form.submit();
1598        }
1599      },
1600        noSuggestionNotice: Drupal.t('Hit the search icon and let the magic happen!'),
1601        showNoSuggestionNotice: true
1602    });
1603
1604    // Ensure arrow keys navigate suggestions list reliably (DEVBRIDGE autocomplete)
1605    searchforminputs.off('keydown.autocomplete-fix').on('keydown.autocomplete-fix', function(e){
1606      var instance = $(this).data('autocomplete');
1607      if (!instance) { return; }
1608      // Down arrow
1609      if (e.which === 40) {
1610        if (!instance.visible && instance.currentValue) {
1611          instance.suggest();
1612        } else {
1613          instance.moveDown();
1614        }
1615        e.preventDefault();
1616        e.stopImmediatePropagation();
1617      }
1618      // Up arrow
1619      else if (e.which === 38) {
1620        if (instance.visible) {
1621          instance.moveUp();
1622          e.preventDefault();
1623          e.stopImmediatePropagation();
1624        }
1625      }
1626    });
1627  }
1628
1629  var usernameinputs = $('#edit-formtree-ltsearchusername, #edit-formtree-ltloglistactionusername, #edit-formtree-ltloglistnodeusername');
1630
1631  if (usernameinputs.length) {
1632    usernameinputs.autocomplete({
1633      serviceUrl: '/' + Drupal.settings.languagevarq + '/ajax/lyricstranslategoogleautocomplete/autocomplete/getmember', //
1634      minChars: 2 ,
1635      deferRequestBy: 150,
1636      triggerSelectOnValidInput: false,
1637      preventBadQueries: false,
1638    });
1639
1640    // Attach keyboard navigation fix for these inputs as well
1641    usernameinputs.off('keydown.autocomplete-fix').on('keydown.autocomplete-fix', function(e){
1642      var instance = $(this).data('autocomplete');
1643      if (!instance) { return; }
1644      if (e.which === 40) { if (!instance.visible && instance.currentValue) { instance.suggest(); } else { instance.moveDown(); } e.preventDefault(); e.stopImmediatePropagation(); }
1645      else if (e.which === 38) { if (instance.visible) { instance.moveUp(); e.preventDefault(); e.stopImmediatePropagation(); } }
1646    });
1647  }
1648
1649  var usernameinputs = $('#lyricstranslatesearchusername input, #lyricstranslatesearchtextmembers input');
1650
1651  if (usernameinputs.length) {
1652    usernameinputs.autocomplete({
1653      serviceUrl: '/' + Drupal.settings.languagevarq + '/ajax/lyricstranslategoogleautocomplete/autocomplete/getmember', //
1654      minChars: 2 ,
1655      deferRequestBy: 150,
1656      triggerSelectOnValidInput: false,
1657      preventBadQueries: false,
1658      onSelect: function(suggestion){
1659        var form = $(this).closest("form");
1660        var submit = form.find(':submit');
1661        submit.addClass("ajax_loading_small");
1662        form.submit();
1663      }
1664    });
1665
1666    usernameinputs.off('keydown.autocomplete-fix').on('keydown.autocomplete-fix', function(e){
1667      var instance = $(this).data('autocomplete');
1668      if (!instance) { return; }
1669      if (e.which === 40) { if (!instance.visible && instance.currentValue) { instance.suggest(); } else { instance.moveDown(); } e.preventDefault(); e.stopImmediatePropagation(); }
1670      else if (e.which === 38) { if (instance.visible) { instance.moveUp(); e.preventDefault(); e.stopImmediatePropagation(); } }
1671    });
1672  }
1673
1674  var artistinputs = $('#lyricstranslatesearchtextartist input');
1675
1676  if (artistinputs.length) {
1677    artistinputs.autocomplete({
1678      serviceUrl: '/' + Drupal.settings.languagevarq + '/ajax/lyricstranslategoogleautocomplete/autocomplete/getartist', //
1679      minChars: 2 ,
1680      deferRequestBy: 150,
1681      triggerSelectOnValidInput: false,
1682      preventBadQueries: false,
1683      onSelect: function(suggestion){
1684        var form = $(this).closest("form");
1685        var submit = form.find(':submit');
1686        submit.addClass("ajax_loading_small");
1687        form.submit();
1688      }
1689    });
1690
1691    artistinputs.off('keydown.autocomplete-fix').on('keydown.autocomplete-fix', function(e){
1692      var instance = $(this).data('autocomplete');
1693      if (!instance) { return; }
1694      if (e.which === 40) { if (!instance.visible && instance.currentValue) { instance.suggest(); } else { instance.moveDown(); } e.preventDefault(); e.stopImmediatePropagation(); }
1695      else if (e.which === 38) { if (instance.visible) { instance.moveUp(); e.preventDefault(); e.stopImmediatePropagation(); } }
1696    });
1697  }
1698
1699  var songinputs = $('#lyricstranslatesearchtextsong input');
1700
1701  if (songinputs.length) {
1702
1703    songinputs.autocomplete({
1704      serviceUrl: '/' + Drupal.settings.languagevarq + '/ajax/lyricstranslategoogleautocomplete/autocomplete/song', //
1705      minChars: 2 ,
1706      deferRequestBy: 150,
1707      maxHeight: 170,
1708      triggerSelectOnValidInput: false,
1709      preventBadQueries: false,
1710      params: {
1711        artist: function () {
1712          if ($('#lyricstranslatesearchtextartist input').length) {
1713            return $('#lyricstranslatesearchtextartist input').val();
1714          }
1715        }
1716      },
1717      onSelect: function(suggestion){
1718        var form = $(this).closest("form");
1719        var submit = form.find(':submit');
1720        submit.addClass("ajax_loading_small");
1721        form.submit();
1722      }
1723    });
1724
1725    songinputs.off('keydown.autocomplete-fix').on('keydown.autocomplete-fix', function(e){
1726      var instance = $(this).data('autocomplete');
1727      if (!instance) { return; }
1728      if (e.which === 40) { if (!instance.visible && instance.currentValue) { instance.suggest(); } else { instance.moveDown(); } e.preventDefault(); e.stopImmediatePropagation(); }
1729      else if (e.which === 38) { if (instance.visible) { instance.moveUp(); e.preventDefault(); e.stopImmediatePropagation(); } }
1730    });
1731  }
1732
1733
1734  //для формы добавления эквивалента
1735   if($("div#phrasesanalogtitle input.form-text").length) {
1736    $('div#phrasesanalogtitle input.form-text').autocomplete({
1737      triggerSelectOnValidInput: false,
1738      preventBadQueries: false,
1739      minChars: 2,
1740      deferRequestBy: 150,
1741      serviceUrl: '/' + Drupal.settings.languagevarq + '/ajax/lyricstranslategoogleautocomplete/autocomplete/getphrases',
1742      onSelect: function(suggestion){
1743        $('div#phraseanaloghelp p').html(
1744        'Phrase <i>'.concat( $('h1').html() , '</i> will be added as equivalent to the phrase: <i>' , suggestion.value ,'</i>'));
1745        $('div#phraseanaloghelp p').show();
1746      },
1747    });
1748
1749    $('div#phrasesanalogtitle input.form-text').off('keydown.autocomplete-fix').on('keydown.autocomplete-fix', function(e){
1750      var instance = $(this).data('autocomplete');
1751      if (!instance) { return; }
1752      if (e.which === 40) { if (!instance.visible && instance.currentValue) { instance.suggest(); } else { instance.moveDown(); } e.preventDefault(); e.stopImmediatePropagation(); }
1753      else if (e.which === 38) { if (instance.visible) { instance.moveUp(); e.preventDefault(); e.stopImmediatePropagation(); } }
1754    });
1755  }
1756
1757
1758  var phrasesinputs = $("div#phrasestitle input.form-text")
1759   if(phrasesinputs.length) {
1760    phrasesinputs.autocomplete({
1761      serviceUrl: '/' + Drupal.settings.languagevarq + '/ajax/lyricstranslategoogleautocomplete/autocomplete/getphrases',
1762      minChars: 2,
1763      deferRequestBy: 150,
1764      triggerSelectOnValidInput: false,
1765      preventBadQueries: false,
1766      onSelect: function(suggestion){
1767        var form = $(this).closest("form");
1768        var submit = form.find(':submit');
1769        submit.addClass("ajax_loading_small");
1770        form.submit();
1771      }
1772    });
1773
1774    phrasesinputs.off('keydown.autocomplete-fix').on('keydown.autocomplete-fix', function(e){
1775      var instance = $(this).data('autocomplete');
1776      if (!instance) { return; }
1777      if (e.which === 40) { if (!instance.visible && instance.currentValue) { instance.suggest(); } else { instance.moveDown(); } e.preventDefault(); e.stopImmediatePropagation(); }
1778      else if (e.which === 38) { if (instance.visible) { instance.moveUp(); e.preventDefault(); e.stopImmediatePropagation(); } }
1779    });
1780  }
1781
1782
1783});
1784;/*})'"*/;/*})'"*/
1785/**
1786 *  Ajax Autocomplete for jQuery, version 1.5.0
1787 *  (c) 2017 Tomas Kirda
1788 *
1789 *  Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT
1789-style license.
1790 *  For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete
1791 */
1792
1793// Expose plugin as an AMD module if AMD loader is present:
1794(function (factory) {
1795    "use strict";
1796    if (typeof define === "function" && define.amd) {
1797        // AMD. Register as an anonymous module.
1798        define(["jquery"], factory);
1799    } else if (typeof exports === "object" && typeof require === "function") {
1800        // Browserify
1801        factory(require("jquery"));
1802    } else {
1803        // Browser globals
1804        factory(jQuery);
1805    }
1806})(
1807    /**
1808     * @param {jQuery} $
1809     */
1810    function ($) {
1811        "use strict";
1812
1813        var utils = {
1814                escapeRegExChars: function (value) {
1815                    return value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
1816                },
1817                createNode: function (containerClass) {
1818                    var div = document.createElement("div");
1819                    div.className = containerClass;
1820                    div.style.position = "absolute";
1821                    div.style.display = "none";
1822                    return div;
1823                },
1824            },
1825            keys = { ESC: 27, TAB: 9, RETURN: 13, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40 },
1826            noop = $.noop;
1827
1828        function Autocomplete(el, options) {
1829            var that = this;
1830
1831            // Shared variables:
1832            that.element = el;
1833            that.el = $(el);
1834            that.suggestions = [];
1835            that.badQueries = [];
1836            that.selectedIndex = -1;
1837            that.currentValue = that.element.value;
1838            that.timeoutId = null;
1839            that.cachedResponse = {};
1840            that.onChangeTimeout = null;
1841            that.onChange = null;
1842            that.isLocal = false;
1843            that.suggestionsContainer = null;
1844            that.noSuggestionsContainer = null;
1845            that.options = $.extend(true, {}, Autocomplete.defaults, options);
1846            that.classes = {
1847                selected: "autocomplete-selected",
1848                suggestion: "autocomplete-suggestion",
1849            };
1850            that.hint = null;
1851            that.hintValue = "";
1852            that.selection = null;
1853
1854            // Initialize and set options:
1855            that.initialize();
1856            that.setOptions(options);
1857        }
1858
1859        Autocomplete.utils = utils;
1860
1861        $.Autocomplete = Autocomplete;
1862
1863        Autocomplete.defaults = {
1864            ajaxSettings: {},
1865            autoSelectFirst: false,
1866            appendTo: "body",
1867            serviceUrl: null,
1868            lookup: null,
1869            onSelect: null,
1870            onHint: null,
1871            width: "auto",
1872            minChars: 1,
1873            maxHeight: 300,
1874            deferRequestBy: 0,
1875            params: {},
1876            formatResult: _formatResult,
1877            formatGroup: _formatGroup,
1878            delimiter: null,
1879            zIndex: 9999,
1880            type: "GET",
1881            noCache: false,
1882            onSearchStart: noop,
1883            onSearchComplete: noop,
1884            onSearchError: noop,
1885            preserveInput: false,
1886            containerClass: "autocomplete-suggestions",
1887            tabDisabled: false,
1888            dataType: "text",
1889            currentRequest: null,
1890            triggerSelectOnValidInput: true,
1891            preventBadQueries: true,
1892            lookupFilter: _lookupFilter,
1893            paramName: "query",
1894            transformResult: _transformResult,
1895            showNoSuggestionNotice: false,
1896            noSuggestionNotice: "No results",
1897            orientation: "bottom",
1898            forceFixPosition: false,
1899        };
1900
1901        function _lookupFilter(suggestion, originalQuery, queryLowerCase) {
1902            return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1;
1903        }
1904
1905        function _transformResult(response) {
1906            return typeof response === "string" ? JSON.parse(response) : response;
1907        }
1908
1909        function _formatResult(suggestion, currentValue) {
1910            // Do not replace anything if the current value is empty
1911            if (!currentValue) {
1912                return suggestion.value;
1913            }
1914
1915            var pattern = "(" + utils.escapeRegExChars(currentValue) + ")";
1916
1917            return suggestion.value
1918                .replace(new RegExp(pattern, "gi"), "<strong>$1</strong>")
1919                .replace(/&/g, "&amp;")
1920                .replace(/</g, "&lt;")
1921                .replace(/>/g, "&gt;")
1922                .replace(/"/g, "&quot;")
1923                .replace(/&lt;(\/?strong)&gt;/g, "<$1>");
1924        }
1925
1926        function _formatGroup(suggestion, category) {
1927            return '<div class="autocomplete-group">' + category + "</div>";
1928        }
1929
1930        Autocomplete.prototype = {
1931            initialize: function () {
1932                var that = this,
1933                    suggestionSelector = "." + that.classes.suggestion,
1934                    selected = that.classes.selected,
1935                    options = that.options,
1936                    container;
1937
1938                that.element.setAttribute("autocomplete", "off");
1939
1940                // html() deals with many types: htmlString or Element or Array or jQuery
1941                that.noSuggestionsContainer = $('<div class="autocomplete-no-suggestion"></div>')
1942                    .html(this.options.noSuggestionNotice)
1943                    .get(0);
1944
1945                that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass);
1946
1947                container = $(that.suggestionsContainer);
1948
1949                container.appendTo(options.appendTo || "body");
1950
1951                // Only set width if it was provided:
1952                if (options.width !== "auto") {
1953                    container.css("width", options.width);
1954                }
1955
1956                // Listen for mouse over event on suggestions list:
1957                container.on("mouseover.autocomplete", suggestionSelector, function () {
1958                    that.activate($(this).data("index"));
1959                });
1960
1961                // Deselect active element when mouse leaves suggestions container:
1962                container.on("mouseout.autocomplete", function () {
1963                    that.selectedIndex = -1;
1964                    container.children("." + selected).removeClass(selected);
1965                });
1966
1967                // Listen for click event on suggestions list:
1968                container.on("click.autocomplete", suggestionSelector, function () {
1969                    that.select($(this).data("index"));
1970                });
1971
1972                container.on("click.autocomplete", function () {
1973                    clearTimeout(that.blurTimeoutId);
1974                });
1975
1976                that.fixPositionCapture = function () {
1977                    if (that.visible) {
1978                        that.fixPosition();
1979                    }
1980                };
1981
1982                $(window).on("resize.autocomplete", that.fixPositionCapture);
1983
1984                that.el.on("keydown.autocomplete", function (e) {
1985                    that.onKeyPress(e);
1986                });
1987                that.el.on("keyup.autocomplete", function (e) {
1988                    that.onKeyUp(e);
1989                });
1990                that.el.on("blur.autocomplete", function () {
1991                    that.onBlur();
1992                });
1993                that.el.on("focus.autocomplete", function () {
1994                    that.onFocus();
1995                });
1996                that.el.on("change.autocomplete", function (e) {
1997                    that.onKeyUp(e);
1998                });
1999                that.el.on("input.autocomplete", function (e) {
2000                    that.onKeyUp(e);
2001                });
2002            },
2003
2004            onFocus: function () {
2005                var that = this;
2006
2007                if (that.disabled) {
2008                    return;
2009                }
2010
2011                that.fixPosition();
2012
2013                if (that.el.val().length >= that.options.minChars) {
2014                    that.onValueChange();
2015                }
2016            },
2017
2018            onBlur: function () {
2019                var that = this,
2020                    options = that.options,
2021                    value = that.el.val(),
2022                    query = that.getQuery(value);
2023
2024                // If user clicked on a suggestion, hide() will
2025                // be canceled, otherwise close suggestions
2026                that.blurTimeoutId = setTimeout(function () {
2027                    that.hide();
2028
2029                    if (that.selection && that.currentValue !== query) {
2030                        (options.onInvalidateSelection || $.noop).call(that.element);
2031                    }
2032                }, 200);
2033            },
2034
2035            abortAjax: function () {
2036                var that = this;
2037                if (that.currentRequest) {
2038                    that.currentRequest.abort();
2039                    that.currentRequest = null;
2040                }
2041            },
2042
2043            setOptions: function (suppliedOptions) {
2044                var that = this,
2045                    options = $.extend({}, that.options, suppliedOptions);
2046
2047                that.isLocal = Array.isArray(options.lookup);
2048
2049                if (that.isLocal) {
2050                    options.lookup = that.verifySuggestionsFormat(options.lookup);
2051                }
2052
2053                options.orientation = that.validateOrientation(options.orientation, "bottom");
2054
2055                // Adjust height, width and z-index:
2056                $(that.suggestionsContainer).css({
2057                    "max-height": options.maxHeight + "px",
2058                    width: options.width + "px",
2059                    "z-index": options.zIndex,
2060                });
2061
2062                this.options = options;
2063            },
2064
2065            clearCache: function () {
2066                this.cachedResponse = {};
2067                this.badQueries = [];
2068            },
2069
2070            clear: function () {
2071                this.clearCache();
2072                this.currentValue = "";
2073                this.suggestions = [];
2074            },
2075
2076            disable: function () {
2077                var that = this;
2078                that.disabled = true;
2079                clearTimeout(that.onChangeTimeout);
2080                that.abortAjax();
2081            },
2082
2083            enable: function () {
2084                this.disabled = false;
2085            },
2086
2087            fixPosition: function () {
2088                // Use only when container has already its content
2089
2090                var that = this,
2091                    $container = $(that.suggestionsContainer),
2092                    containerParent = $container.parent().get(0);
2093                // Fix position automatically when appended to body.
2094                // In other cases force parameter must be given.
2095                if (containerParent !== document.body && !that.options.forceFixPosition) {
2096                    return;
2097                }
2098
2099                // Choose orientation
2100                var orientation = that.options.orientation,
2101                    containerHeight = $container.outerHeight(),
2102                    height = that.el.outerHeight(),
2103                    offset = that.el.offset(),
2104                    styles = { top: offset.top, left: offset.left };
2105
2106                if (orientation === "auto") {
2107                    var viewPortHeight = $(window).height(),
2108                        scrollTop = $(window).scrollTop(),
2109                        topOverflow = -scrollTop + offset.top - containerHeight,
2110                        bottomOverflow =
2111                            scrollTop + viewPortHeight - (offset.top + height + containerHeight);
2112
2113                    orientation =
2114                        Math.max(topOverflow, bottomOverflow) === topOverflow ? "top" : "bottom";
2115                }
2116
2117                if (orientation === "top") {
2118                    styles.top += -containerHeight;
2119                } else {
2120                    styles.top += height;
2121                }
2122
2123                // If container is not positioned to body,
2124                // correct its position using offset parent offset
2125                if (containerParent !== document.body) {
2126                    var opacity = $container.css("opacity"),
2127                        parentOffsetDiff;
2128
2129                    if (!that.visible) {
2130                        $container.css("opacity", 0).show();
2131                    }
2132
2133                    parentOffsetDiff = $container.offsetParent().offset();
2134                    styles.top -= parentOffsetDiff.top;
2135                    styles.top += containerParent.scrollTop;
2136                    styles.left -= parentOffsetDiff.left;
2137
2138                    if (!that.visible) {
2139                        $container.css("opacity", opacity).hide();
2140                    }
2141                }
2142
2143                if (that.options.width === "auto") {
2144                    styles.width = that.el.outerWidth() + "px";
2145                }
2146
2147                $container.css(styles);
2148            },
2149
2150            isCursorAtEnd: function () {
2151                var that = this,
2152                    valLength = that.el.val().length,
2153                    selectionStart = that.element.selectionStart,
2154                    range;
2155
2156                if (typeof selectionStart === "number") {
2157                    return selectionStart === valLength;
2158                }
2159                if (document.selection) {
2160                    range = document.selection.createRange();
2161                    range.moveStart("character", -valLength);
2162                    return valLength === range.text.length;
2163                }
2164                return true;
2165            },
2166
2167            onKeyPress: function (e) {
2168                var that = this;
2169
2170                // If suggestions are hidden and user presses arrow down, display suggestions:
2171                if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) {
2172                    that.suggest();
2173                    return;
2174                }
2175
2176                if (that.disabled || !that.visible) {
2177                    return;
2178                }
2179
2180                switch (e.which) {
2181                    case keys.ESC:
2182                        that.el.val(that.currentValue);
2183                        that.hide();
2184                        break;
2185                    case keys.RIGHT:
2186                        if (that.hint && that.options.onHint && that.isCursorAtEnd()) {
2187                            that.selectHint();
2188                            break;
2189                        }
2190                        return;
2191                    case keys.TAB:
2192                        if (that.hint && that.options.onHint) {
2193                            that.selectHint();
2194                            return;
2195                        }
2196                        if (that.selectedIndex === -1) {
2197                            that.hide();
2198                            return;
2199                        }
2200                        that.select(that.selectedIndex);
2201                        if (that.options.tabDisabled === false) {
2202                            return;
2203                        }
2204                        break;
2205                    case keys.RETURN:
2206                        if (that.selectedIndex === -1) {
2207                            that.hide();
2208                            return;
2209                        }
2210                        that.select(that.selectedIndex);
2211                        break;
2212                    case keys.UP:
2213                        that.moveUp();
2214                        break;
2215                    case keys.DOWN:
2216                        that.moveDown();
2217                        break;
2218                    default:
2219                        return;
2220                }
2221
2222                // Cancel event if function did not return:
2223                e.stopImmediatePropagation();
2224                e.preventDefault();
2225            },
2226
2227            onKeyUp: function (e) {
2228                var that = this;
2229
2230                if (that.disabled) {
2231                    return;
2232                }
2233
2234                switch (e.which) {
2235                    case keys.UP:
2236                    case keys.DOWN:
2237                        return;
2238                }
2239
2240                clearTimeout(that.onChangeTimeout);
2241
2242                if (that.currentValue !== that.el.val()) {
2243                    that.findBestHint();
2244                    if (that.options.deferRequestBy > 0) {
2245                        // Defer lookup in case when value changes very quickly:
2246                        that.onChangeTimeout = setTimeout(function () {
2247                            that.onValueChange();
2248                        }, that.options.deferRequestBy);
2249                    } else {
2250                        that.onValueChange();
2251                    }
2252                }
2253            },
2254
2255            onValueChange: function () {
2256                if (this.ignoreValueChange) {
2257                    this.ignoreValueChange = false;
2258                    return;
2259                }
2260
2261                var that = this,
2262                    options = that.options,
2263                    value = that.el.val(),
2264                    query = that.getQuery(value);
2265
2266                if (that.selection && that.currentValue !== query) {
2267                    that.selection = null;
2268                    (options.onInvalidateSelection || $.noop).call(that.element);
2269                }
2270
2271                clearTimeout(that.onChangeTimeout);
2272                that.currentValue = value;
2273                that.selectedIndex = -1;
2274
2275                // Check existing suggestion for the match before proceeding:
2276                if (options.triggerSelectOnValidInput && that.isExactMatch(query)) {
2277                    that.select(0);
2278                    return;
2279                }
2280
2281                if (query.length < options.minChars) {
2282                    that.hide();
2283                } else {
2284                    that.getSuggestions(query);
2285                }
2286            },
2287
2288            isExactMatch: function (query) {
2289                var suggestions = this.suggestions;
2290
2291                return (
2292                    suggestions.length === 1 &&
2293                    suggestions[0].value.toLowerCase() === query.toLowerCase()
2294                );
2295            },
2296
2297            getQuery: function (value) {
2298                var delimiter = this.options.delimiter,
2299                    parts;
2300
2301                if (!delimiter) {
2302                    return value;
2303                }
2304                parts = value.split(delimiter);
2305                return parts[parts.length - 1].trim();
2306            },
2307
2308            getSuggestionsLocal: function (query) {
2309                var that = this,
2310                    options = that.options,
2311                    queryLowerCase = query.toLowerCase(),
2312                    filter = options.lookupFilter,
2313                    limit = parseInt(options.lookupLimit, 10),
2314                    data;
2315
2316                data = {
2317                    suggestions: $.grep(options.lookup, function (suggestion) {
2318                        return filter(suggestion, query, queryLowerCase);
2319                    }),
2320                };
2321
2322                if (limit && data.suggestions.length > limit) {
2323                    data.suggestions = data.suggestions.slice(0, limit);
2324                }
2325
2326                return data;
2327            },
2328
2329            getSuggestions: function (q) {
2330                var response,
2331                    that = this,
2332                    options = that.options,
2333                    serviceUrl = options.serviceUrl,
2334                    params,
2335                    cacheKey,
2336                    ajaxSettings;
2337
2338                options.params[options.paramName] = q;
2339
2340                if (options.onSearchStart.call(that.element, options.params) === false) {
2341                    return;
2342                }
2343
2344                params = options.ignoreParams ? null : options.params;
2345
2346                if (typeof options.lookup === "function") {
2347                    options.lookup(q, function (data) {
2348                        that.suggestions = data.suggestions;
2349                        that.suggest();
2350                        options.onSearchComplete.call(that.element, q, data.suggestions);
2351                    });
2352                    return;
2353                }
2354
2355                if (that.isLocal) {
2356                    response = that.getSuggestionsLocal(q);
2357                } else {
2358                    if (typeof serviceUrl === "function") {
2359                        serviceUrl = serviceUrl.call(that.element, q);
2360                    }
2361                    cacheKey = serviceUrl + "?" + $.param(params || {});
2362                    response = that.cachedResponse[cacheKey];
2363                }
2364
2365                if (response && Array.isArray(response.suggestions)) {
2366                    that.suggestions = response.suggestions;
2367                    that.suggest();
2368                    options.onSearchComplete.call(that.element, q, response.suggestions);
2369                } else if (!that.isBadQuery(q)) {
2370                    that.abortAjax();
2371
2372                    ajaxSettings = {
2373                        url: serviceUrl,
2374                        data: params,
2375                        type: options.type,
2376                        dataType: options.dataType,
2377                    };
2378
2379                    $.extend(ajaxSettings, options.ajaxSettings);
2380
2381                    that.currentRequest = $.ajax(ajaxSettings)
2382                        .done(function (data) {
2383                            var result;
2384                            that.currentRequest = null;
2385                            result = options.transformResult(data, q);
2386                            that.processResponse(result, q, cacheKey);
2387                            options.onSearchComplete.call(that.element, q, result.suggestions);
2388                        })
2389                        .fail(function (jqXHR, textStatus, errorThrown) {
2390                            options.onSearchError.call(
2391                                that.element,
2392                                q,
2393                                jqXHR,
2394                                textStatus,
2395                                errorThrown
2396                            );
2397                        });
2398                } else {
2399                    options.onSearchComplete.call(that.element, q, []);
2400                }
2401            },
2402
2403            isBadQuery: function (q) {
2404                if (!this.options.preventBadQueries) {
2405                    return false;
2406                }
2407
2408                var badQueries = this.badQueries,
2409                    i = badQueries.length;
2410
2411                while (i--) {
2412                    if (q.indexOf(badQueries[i]) === 0) {
2413                        return true;
2414                    }
2415                }
2416
2417                return false;
2418            },
2419
2420            hide: function () {
2421                var that = this,
2422                    container = $(that.suggestionsContainer);
2423
2424                if (typeof that.options.onHide === "function" && that.visible) {
2425                    that.options.onHide.call(that.element, container);
2426                }
2427
2428                that.visible = false;
2429                that.selectedIndex = -1;
2430                clearTimeout(that.onChangeTimeout);
2431                $(that.suggestionsContainer).hide();
2432                that.onHint(null);
2433            },
2434
2435            suggest: function () {
2436                if (!this.suggestions.length) {
2437                    if (this.options.showNoSuggestionNotice) {
2438                        this.noSuggestions();
2439                    } else {
2440                        this.hide();
2441                    }
2442                    return;
2443                }
2444
2445                var that = this,
2446                    options = that.options,
2447                    groupBy = options.groupBy,
2448                    formatResult = options.formatResult,
2449                    value = that.getQuery(that.currentValue),
2450                    className = that.classes.suggestion,
2451                    classSelected = that.classes.selected,
2452                    container = $(that.suggestionsContainer),
2453                    noSuggestionsContainer = $(that.noSuggestionsContainer),
2454                    beforeRender = options.beforeRender,
2455                    html = "",
2456                    category,
2457                    formatGroup = function (suggestion) {
2458                        var currentCategory = suggestion.data[groupBy];
2459
2460                        if (category === currentCategory) {
2461                            return "";
2462                        }
2463
2464                        category = currentCategory;
2465
2466                        return options.formatGroup(suggestion, category);
2467                    };
2468
2469                if (options.triggerSelectOnValidInput && that.isExactMatch(value)) {
2470                    that.select(0);
2471                    return;
2472                }
2473
2474                // Build suggestions inner HTML:
2475                $.each(that.suggestions, function (i, suggestion) {
2476                    if (groupBy) {
2477                        html += formatGroup(suggestion, value, i);
2478                    }
2479
2480                    html +=
2481                        '<div class="' +
2482                        className +
2483                        '" data-index="' +
2484                        i +
2485                        '">' +
2486                        formatResult(suggestion, value, i) +
2487                        "</div>";
2488                });
2489
2490                this.adjustContainerWidth();
2491
2492                noSuggestionsContainer.detach();
2493                container.html(html);
2494
2495                if (typeof beforeRender === "function") {
2496                    beforeRender.call(that.element, container, that.suggestions);
2497                }
2498
2499                that.fixPosition();
2500                container.show();
2501
2502                // Select first value by default:
2503                if (options.autoSelectFirst) {
2504                    that.selectedIndex = 0;
2505                    container.scrollTop(0);
2506                    container
2507                        .children("." + className)
2508                        .first()
2509                        .addClass(classSelected);
2510                }
2511
2512                that.visible = true;
2513                that.findBestHint();
2514            },
2515
2516            noSuggestions: function () {
2517                var that = this,
2518                    beforeRender = that.options.beforeRender,
2519                    container = $(that.suggestionsContainer),
2520                    noSuggestionsContainer = $(that.noSuggestionsContainer);
2521
2522                this.adjustContainerWidth();
2523
2524                // Some explicit steps. Be careful here as it easy to get
2525                // noSuggestionsContainer removed from DOM if not detached properly.
2526                noSuggestionsContainer.detach();
2527
2528                // clean suggestions if any
2529                container.empty();
2530                container.append(noSuggestionsContainer);
2531
2532                if (typeof beforeRender === "function") {
2533                    beforeRender.call(that.element, container, that.suggestions);
2534                }
2535
2536                that.fixPosition();
2537
2538                container.show();
2539                that.visible = true;
2540            },
2541
2542            adjustContainerWidth: function () {
2543                var that = this,
2544                    options = that.options,
2545                    width,
2546                    container = $(that.suggestionsContainer);
2547
2548                // If width is auto, adjust width before displaying suggestions,
2549                // because if instance was created before input had width, it will be zero.
2550                // Also it adjusts if input width has changed.
2551                if (options.width === "auto") {
2552                    width = that.el.outerWidth();
2553                    container.css("width", width > 0 ? width : 300);
2554                } else if (options.width === "flex") {
2555                    // Trust the source! Unset the width property so it will be the max length
2556                    // the containing elements.
2557                    container.css("width", "");
2558                }
2559            },
2560
2561            findBestHint: function () {
2562                var that = this,
2563                    value = that.el.val().toLowerCase(),
2564                    bestMatch = null;
2565
2566                if (!value) {
2567                    return;
2568                }
2569
2570                $.each(that.suggestions, function (i, suggestion) {
2571                    var foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0;
2572                    if (foundMatch) {
2573                        bestMatch = suggestion;
2574                    }
2575                    return !foundMatch;
2576                });
2577
2578                that.onHint(bestMatch);
2579            },
2580
2581            onHint: function (suggestion) {
2582                var that = this,
2583                    onHintCallback = that.options.onHint,
2584                    hintValue = "";
2585
2586                if (suggestion) {
2587                    hintValue =
2588                        that.currentValue + suggestion.value.substr(that.currentValue.length);
2589                }
2590                if (that.hintValue !== hintValue) {
2591                    that.hintValue = hintValue;
2592                    that.hint = suggestion;
2593                    if (typeof onHintCallback === "function") {
2594                        onHintCallback.call(that.element, hintValue);
2595                    }
2596                }
2597            },
2598
2599            verifySuggestionsFormat: function (suggestions) {
2600                // If suggestions is string array, convert them to supported format:
2601                if (suggestions.length && typeof suggestions[0] === "string") {
2602                    return $.map(suggestions, function (value) {
2603                        return { value: value, data: null };
2604                    });
2605                }
2606
2607                return suggestions;
2608            },
2609
2610            validateOrientation: function (orientation, fallback) {
2611                orientation = (orientation || "").trim().toLowerCase();
2612
2613                if ($.inArray(orientation, ["auto", "bottom", "top"]) === -1) {
2614                    orientation = fallback;
2615                }
2616
2617                return orientation;
2618            },
2619
2620            processResponse: function (result, originalQuery, cacheKey) {
2621                var that = this,
2622                    options = that.options;
2623
2624                result.suggestions = that.verifySuggestionsFormat(result.suggestions);
2625
2626                // Cache results if cache is not disabled:
2627                if (!options.noCache) {
2628                    that.cachedResponse[cacheKey] = result;
2629                    if (options.preventBadQueries && !result.suggestions.length) {
2630                        that.badQueries.push(originalQuery);
2631                    }
2632                }
2633
2634                // Return if originalQuery is not matching current query:
2635                if (originalQuery !== that.getQuery(that.currentValue)) {
2636                    return;
2637                }
2638
2639                that.suggestions = result.suggestions;
2640                that.suggest();
2641            },
2642
2643            activate: function (index) {
2644                var that = this,
2645                    activeItem,
2646                    selected = that.classes.selected,
2647                    container = $(that.suggestionsContainer),
2648                    children = container.find("." + that.classes.suggestion);
2649
2650                container.find("." + selected).removeClass(selected);
2651
2652                that.selectedIndex = index;
2653
2654                if (that.selectedIndex !== -1 && children.length > that.selectedIndex) {
2655                    activeItem = children.get(that.selectedIndex);
2656                    $(activeItem).addClass(selected);
2657                    return activeItem;
2658                }
2659
2660                return null;
2661            },
2662
2663            selectHint: function () {
2664                var that = this,
2665                    i = $.inArray(that.hint, that.suggestions);
2666
2667                that.select(i);
2668            },
2669
2670            select: function (i) {
2671                var that = this;
2672                that.hide();
2673                that.onSelect(i);
2674            },
2675
2676            moveUp: function () {
2677                var that = this;
2678
2679                if (that.selectedIndex === -1) {
2680                    return;
2681                }
2682
2683                if (that.selectedIndex === 0) {
2684                    $(that.suggestionsContainer)
2685                        .children("." + that.classes.suggestion)
2686                        .first()
2687                        .removeClass(that.classes.selected);
2688                    that.selectedIndex = -1;
2689                    that.ignoreValueChange = false;
2690                    that.el.val(that.currentValue);
2691                    that.findBestHint();
2692                    return;
2693                }
2694
2695                that.adjustScroll(that.selectedIndex - 1);
2696            },
2697
2698            moveDown: function () {
2699                var that = this;
2700
2701                if (that.selectedIndex === that.suggestions.length - 1) {
2702                    return;
2703                }
2704
2705                that.adjustScroll(that.selectedIndex + 1);
2706            },
2707
2708            adjustScroll: function (index) {
2709                var that = this,
2710                    activeItem = that.activate(index);
2711
2712                if (!activeItem) {
2713                    return;
2714                }
2715
2716                var offsetTop,
2717                    upperBound,
2718                    lowerBound,
2719                    heightDelta = $(activeItem).outerHeight();
2720
2721                offsetTop = activeItem.offsetTop;
2722                upperBound = $(that.suggestionsContainer).scrollTop();
2723                lowerBound = upperBound + that.options.maxHeight - heightDelta;
2724
2725                if (offsetTop < upperBound) {
2726                    $(that.suggestionsContainer).scrollTop(offsetTop);
2727                } else if (offsetTop > lowerBound) {
2728                    $(that.suggestionsContainer).scrollTop(
2729                        offsetTop - that.options.maxHeight + heightDelta
2730                    );
2731                }
2732
2733                if (!that.options.preserveInput) {
2734                    // During onBlur event, browser will trigger "change" event,
2735                    // because value has changed, to avoid side effect ignore,
2736                    // that event, so that correct suggestion can be selected
2737                    // when clicking on suggestion with a mouse
2738                    that.ignoreValueChange = true;
2739                    that.el.val(that.getValue(that.suggestions[index].value));
2740                }
2741
2742                that.onHint(null);
2743            },
2744
2745            onSelect: function (index) {
2746                var that = this,
2747                    onSelectCallback = that.options.onSelect,
2748                    suggestion = that.suggestions[index];
2749
2750                that.currentValue = that.getValue(suggestion.value);
2751
2752                if (that.currentValue !== that.el.val() && !that.options.preserveInput) {
2753                    that.el.val(that.currentValue);
2754                }
2755
2756                that.onHint(null);
2757                that.suggestions = [];
2758                that.selection = suggestion;
2759
2760                if (typeof onSelectCallback === "function") {
2761                    onSelectCallback.call(that.element, suggestion);
2762                }
2763            },
2764
2765            getValue: function (value) {
2766                var that = this,
2767                    delimiter = that.options.delimiter,
2768                    currentValue,
2769                    parts;
2770
2771                if (!delimiter) {
2772                    return value;
2773                }
2774
2775                currentValue = that.currentValue;
2776                parts = currentValue.split(delimiter);
2777
2778                if (parts.length === 1) {
2779                    return value;
2780                }
2781
2782                return (
2783                    currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) +
2784                    value
2785                );
2786            },
2787
2788            dispose: function () {
2789                var that = this;
2790                that.el.off(".autocomplete").removeData("autocomplete");
2791                $(window).off("resize.autocomplete", that.fixPositionCapture);
2792                $(that.suggestionsContainer).remove();
2793            },
2794        };
2795
2796        // Create chainable jQuery plugin:
2797        $.fn.devbridgeAutocomplete = function (options, args) {
2798            var dataKey = "autocomplete";
2799            // If function invoked without argument return
2800            // instance of the first matched element:
2801            if (!arguments.length) {
2802                return this.first().data(dataKey);
2803            }
2804
2805            return this.each(function () {
2806                var inputElement = $(this),
2807                    instance = inputElement.data(dataKey);
2808
2809                if (typeof options === "string") {
2810                    if (instance && typeof instance[options] === "function") {
2811                        instance[options](args);
2812                    }
2813                } else {
2814                    // If instance already exists, destroy it:
2815                    if (instance && instance.dispose) {
2816                        instance.dispose();
2817                    }
2818                    instance = new Autocomplete(this, options);
2819                    inputElement.data(dataKey, instance);
2820                }
2821            });
2822        };
2823
2824        // Don't overwrite if it already exists
2825        if (!$.fn.autocomplete) {
2826            $.fn.autocomplete = $.fn.devbridgeAutocomplete;
2827        }
2828    }
2829);
2830;/*})'"*/;/*})'"*/
2831(function ($) {
2832
2833Drupal.behaviors.quote = {
2834  attach: function(context, settings) {
2835    var level = Drupal.settings.quote_nest - 1;
2836    if (level >= 0) {
2837      var top = $('blockquote.quote-nest-1', context);
2838      $('blockquote.quote-msg:eq(' + level + ')', top)
2839      .hide()
2840      .after('<div class="quote-snip">' + Drupal.t('<a href="#">[snip]</a>') + '</div>')
2841      .next('.quote-snip')
2842      .children('a')
2843      .click(function(e) {
2844        $(this).parent().siblings('.quote-msg').toggle();
2845        e.preventDefault();
2846      });
2847    }
2848  }
2849};
2850
2851}(jQuery));
2852;/*})'"*/;/*})'"*/
2853(function ($) {
2854
2855  Drupal.behaviors.captcha = {
2856    attach: function (context) {
2857
2858      // Turn off autocompletion for the CAPTCHA response field.
2859      // We do it here with JavaScript (instead of directly in the markup)
2860      // because this autocomplete attribute is not standard and
2861      // it would break (X)HTML compliance.
2862      $("#edit-captcha-response").attr("autocomplete", "off");
2863
2864    }
2865  };
2866
2867  Drupal.behaviors.captchaAdmin = {
2868    attach: function (context) {
2869      // Add onclick handler to checkbox for adding a CAPTCHA description
2870      // so that the textfields for the CAPTCHA description are hidden
2871      // when no description should be added.
2872      // @todo: div.form-item-captcha-description depends on theming, maybe
2873      // it's better to add our own wrapper with id (instead of a class).
2874      $("#edit-captcha-add-captcha-description").click(function() {
2875        if ($("#edit-captcha-add-captcha-description").is(":checked")) {
2876          // Show the CAPTCHA description textfield(s).
2877          $("div.form-item-captcha-description").show('slow');
2878        }
2879        else {
2880          // Hide the CAPTCHA description textfield(s).
2881          $("div.form-item-captcha-description").hide('slow');
2882        }
2883      });
2884      // Hide the CAPTCHA description textfields if option is disabled on page load.
2885      if (!$("#edit-captcha-add-captcha-description").is(":checked")) {
2886        $("div.form-item-captcha-description").hide();
2887      }
2888    }
2889
2890  };
2891
2892})(jQuery);
2893;/*})'"*/;/*})'"*/
2894(function ($) {
2895
2896Drupal.behaviors.textarea = {
2897  attach: function (context, settings) {
2898    $('.form-textarea-wrapper.resizable', context).once('textarea', function () {
2899      var staticOffset = null;
2900      var textarea = $(this).addClass('resizable-textarea').find('textarea');
2901      var grippie = $('<div class="grippie"></div>').mousedown(startDrag);
2902
2903      grippie.insertAfter(textarea);
2904
2905      function startDrag(e) {
2906        staticOffset = textarea.height() - e.pageY;
2907        textarea.css('opacity', 0.25);
2908        $(document).mousemove(performDrag).mouseup(endDrag);
2909        return false;
2910      }
2911
2912      function performDrag(e) {
2913        textarea.height(Math.max(32, staticOffset + e.pageY) + 'px');
2914        return false;
2915      }
2916
2917      function endDrag(e) {
2918        $(document).unbind('mousemove', performDrag).unbind('mouseup', endDrag);
2919        textarea.css('opacity', 1);
2920      }
2921    });
2922  }
2923};
2924
2925})(jQuery);
2926;/*})'"*/;/*})'"*/
2927(function ($) {
2928
2929/**
2930 * A progressbar object. Initialized with the given id. Must be inserted into
2931 * the DOM afterwards through progressBar.element.
2932 *
2933 * method is the function which will perform the HTTP request to get the
2934 * progress bar state. Either "GET" or "POST".
2935 *
2936 * e.g. pb = new progressBar('myProgressBar');
2937 *      some_element.appendChild(pb.element);
2938 */
2939Drupal.progressBar = function (id, updateCallback, method, errorCallback) {
2940  var pb = this;
2941  this.id = id;
2942  this.method = method || 'GET';
2943  this.updateCallback = updateCallback;
2944  this.errorCallback = errorCallback;
2945
2946  // The WAI-ARIA setting aria-live="polite" will announce changes after users
2947  // have completed their current activity and not interrupt the screen reader.
2948  this.element = $('<div class="progress" aria-live="polite"></div>').attr('id', id);
2949  this.element.html('<div class="bar"><div class="filled"></div></div>' +
2950                    '<div class="percentage"></div>' +
2951                    '<div class="message">&nbsp;</div>');
2952};
2953
2954/**
2955 * Set the percentage and status message for the progressbar.
2956 */
2957Drupal.progressBar.prototype.setProgress = function (percentage, message) {
2958  if (percentage >= 0 && percentage <= 100) {
2959    $('div.filled', this.element).css('width', percentage + '%');
2960    $('div.percentage', this.element).html(percentage + '%');
2961  }
2962  $('div.message', this.element).html(message);
2963  if (this.updateCallback) {
2964    this.updateCallback(percentage, message, this);
2965  }
2966};
2967
2968/**
2969 * Start monitoring progress via Ajax.
2970 */
2971Drupal.progressBar.prototype.startMonitoring = function (uri, delay) {
2972  this.delay = delay;
2973  this.uri = uri;
2974  this.sendPing();
2975};
2976
2977/**
2978 * Stop monitoring progress via Ajax.
2979 */
2980Drupal.progressBar.prototype.stopMonitoring = function () {
2981  clearTimeout(this.timer);
2982  // This allows monitoring to be stopped from within the callback.
2983  this.uri = null;
2984};
2985
2986/**
2987 * Request progress data from server.
2988 */
2989Drupal.progressBar.prototype.sendPing = function () {
2990  if (this.timer) {
2991    clearTimeout(this.timer);
2992  }
2993  if (this.uri) {
2994    var pb = this;
2995    // When doing a post request, you need non-null data. Otherwise a
2996    // HTTP 411 or HTTP 406 (with Apache mod_security) error may result.
2997    $.ajax({
2998      type: this.method,
2999      url: this.uri,
3000      data: '',
3001      dataType: 'json',
3002      success: function (progress) {
3003        // Display errors.
3004        if (progress.status == 0) {
3005          pb.displayError(progress.data);
3006          return;
3007        }
3008        // Update display.
3009        pb.setProgress(progress.percentage, progress.message);
3010        // Schedule next timer.
3011        pb.timer = setTimeout(function () { pb.sendPing(); }, pb.delay);
3012      },
3013      error: function (xmlhttp) {
3014        pb.displayError(Drupal.ajaxError(xmlhttp, pb.uri));
3015      }
3016    });
3017  }
3018};
3019
3020/**
3021 * Display errors on the page.
3022 */
3023Drupal.progressBar.prototype.displayError = function (string) {
3024  var error = $('<div class="messages error"></div>').html(string);
3025  $(this.element).before(error).hide();
3026
3027  if (this.errorCallback) {
3028    this.errorCallback(this);
3029  }
3030};
3031
3032})(jQuery);
3033;/*})'"*/;/*})'"*/
3034/*!
3035	Autosize 3.0.17
3036	license: MIT
3037	http://www.jacklmoore.com/autosize
3038*/
3039(function (global, factory) {
3040	if (typeof define === 'function' && define.amd) {
3041		define(['exports', 'module'], factory);
3042	} else if (typeof exports !== 'undefined' && typeof module !== 'undefined') {
3043		factory(exports, module);
3044	} else {
3045		var mod = {
3046			exports: {}
3047		};
3048		factory(mod.exports, mod);
3049		global.autosize = mod.exports;
3050	}
3051})(this, function (exports, module) {
3052	'use strict';
3053
3054	var set = typeof Set === 'function' ? new Set() : (function () {
3055		var list = [];
3056
3057		return {
3058			has: function has(key) {
3059				return Boolean(list.indexOf(key) > -1);
3060			},
3061			add: function add(key) {
3062				list.push(key);
3063			},
3064			'delete': function _delete(key) {
3065				list.splice(list.indexOf(key), 1);
3066			} };
3067	})();
3068
3069	var createEvent = function createEvent(name) {
3070		return new Event(name);
3071	};
3072	try {
3073		new Event('test');
3074	} catch (e) {
3075		// IE does not support `new Event()`
3076		createEvent = function (name) {
3077			var evt = document.createEvent('Event');
3078			evt.initEvent(name, true, false);
3079			return evt;
3080		};
3081	}
3082
3083	function assign(ta) {
3084		if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || set.has(ta)) return;
3085
3086		var heightOffset = null;
3087		var clientWidth = ta.clientWidth;
3088		var cachedHeight = null;
3089
3090		function init() {
3091			var style = window.getComputedStyle(ta, null);
3092
3093			if (style.resize === 'vertical') {
3094				ta.style.resize = 'none';
3095			} else if (style.resize === 'both') {
3096				ta.style.resize = 'horizontal';
3097			}
3098
3099			if (style.boxSizing === 'content-box') {
3100				heightOffset = -(parseFloat(style.paddingTop) + parseFloat(style.paddingBottom));
3101			} else {
3102				heightOffset = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
3103			}
3104			// Fix when a textarea is not on document body and heightOffset is Not a Number
3105			if (isNaN(heightOffset)) {
3106				heightOffset = 0;
3107			}
3108
3109			update();
3110		}
3111
3112		function changeOverflow(value) {
3113			{
3114				// Chrome/Safari-specific fix:
3115				// When the textarea y-overflow is hidden, Chrome/Safari do not reflow the text to account for the space
3116				// made available by removing the scrollbar. The following forces the necessary text reflow.
3117				var width = ta.style.width;
3118				ta.style.width = '0px';
3119				// Force reflow:
3120				/* jshint ignore:start */
3121				ta.offsetWidth;
3122				/* jshint ignore:end */
3123				ta.style.width = width;
3124			}
3125
3126			ta.style.overflowY = value;
3127
3128			resize();
3129		}
3130
3131		function getParentOverflows(el) {
3132			var arr = [];
3133
3134			while (el && el.parentNode && el.parentNode instanceof Element) {
3135				if (el.parentNode.scrollTop) {
3136					arr.push({
3137						node: el.parentNode,
3138						scrollTop: el.parentNode.scrollTop });
3139				}
3140				el = el.parentNode;
3141			}
3142
3143			return arr;
3144		}
3145
3146		function resize() {
3147			var originalHeight = ta.style.height;
3148			var overflows = getParentOverflows(ta);
3149			var docTop = document.documentElement && document.documentElement.scrollTop; // Needed for Mobile IE (ticket #240)
3150
3151			ta.style.height = 'auto';
3152
3153			var endHeight = ta.scrollHeight + heightOffset + 25;
3154
3155			if (ta.scrollHeight === 0) {
3156				// If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM.
3157				ta.style.height = originalHeight;
3158				return;
3159			}
3160
3161			ta.style.height = endHeight + 'px';
3162
3163			// used to check if an update is actually necessary on window.resize
3164			clientWidth = ta.clientWidth;
3165
3166			// prevents scroll-position jumping
3167			overflows.forEach(function (el) {
3168				el.node.scrollTop = el.scrollTop;
3169			});
3170
3171			if (docTop) {
3172				document.documentElement.scrollTop = docTop;
3173			}
3174		}
3175
3176		function update() {
3177			resize();
3178
3179			var computed = window.getComputedStyle(ta, null);
3180			var computedHeight = Math.round(parseFloat(computed.height));
3181			var styleHeight = Math.round(parseFloat(ta.style.height));
3182
3183			// The computed height not matching the height set via resize indicates that
3184			// the max-height has been exceeded, in which case the overflow should be set to visible.
3185			if (computedHeight !== styleHeight) {
3186				if (computed.overflowY !== 'visible') {
3187					changeOverflow('visible');
3188				}
3189			} else {
3190				// Normally keep overflow set to hidden, to avoid flash of scrollbar as the textarea expands.
3191				if (computed.overflowY !== 'hidden') {
3192					changeOverflow('hidden');
3193				}
3194			}
3195
3196			if (cachedHeight !== computedHeight) {
3197				cachedHeight = computedHeight;
3198				var evt = createEvent('autosize:resized');
3199				ta.dispatchEvent(evt);
3200			}
3201		}
3202
3203		var pageResize = function pageResize() {
3204			if (ta.clientWidth !== clientWidth) {
3205				update();
3206			}
3207		};
3208
3209		var destroy = (function (style) {
3210			window.removeEventListener('resize', pageResize, false);
3211			ta.removeEventListener('input', update, false);
3212			ta.removeEventListener('keyup', update, false);
3213			ta.removeEventListener('autosize:destroy', destroy, false);
3214			ta.removeEventListener('autosize:update', update, false);
3215			set['delete'](ta);
3216
3217			Object.keys(style).forEach(function (key) {
3218				ta.style[key] = style[key];
3219			});
3220		}).bind(ta, {
3221			height: ta.style.height,
3222			resize: ta.style.resize,
3223			overflowY: ta.style.overflowY,
3224//			overflowX: ta.style.overflowX,
3225//			wordWrap: ta.style.wordWrap 
3226		});
3227
3228		ta.addEventListener('autosize:destroy', destroy, false);
3229
3230		// IE9 does not fire onpropertychange or oninput for deletions,
3231		// so binding to onkeyup to catch most of those events.
3232		// There is no way that I know of to detect something like 'cut' in IE9.
3233		if ('onpropertychange' in ta && 'oninput' in ta) {
3234			ta.addEventListener('keyup', update, false);
3235		}
3236
3237		window.addEventListener('resize', pageResize, false);
3238		ta.addEventListener('input', update, false);
3239		ta.addEventListener('autosize:update', update, false);
3240		set.add(ta);
3241//		ta.style.overflowX = 'hidden';
3242//		ta.style.wordWrap = 'break-word';
3243
3244		init();
3245	}
3246
3247	function destroy(ta) {
3248		if (!(ta && ta.nodeName && ta.nodeName === 'TEXTAREA')) return;
3249		var evt = createEvent('autosize:destroy');
3250		ta.dispatchEvent(evt);
3251	}
3252
3253	function update(ta) {
3254		if (!(ta && ta.nodeName && ta.nodeName === 'TEXTAREA')) return;
3255		var evt = createEvent('autosize:update');
3256		ta.dispatchEvent(evt);
3257	}
3258
3259	var autosize = null;
3260
3261	// Do nothing in Node.js environment and IE8 (or lower)
3262	if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') {
3263		autosize = function (el) {
3264			return el;
3265		};
3266		autosize.destroy = function (el) {
3267			return el;
3268		};
3269		autosize.update = function (el) {
3270			return el;
3271		};
3272	} else {
3273		autosize = function (el, options) {
3274			if (el) {
3275				Array.prototype.forEach.call(el.length ? el : [el], function (x) {
3276					return assign(x, options);
3277				});
3278			}
3279			return el;
3280		};
3281		autosize.destroy = function (el) {
3282			if (el) {
3283				Array.prototype.forEach.call(el.length ? el : [el], destroy);
3284			}
3285			return el;
3286		};
3287		autosize.update = function (el) {
3288			if (el) {
3289				Array.prototype.forEach.call(el.length ? el : [el], update);
3290			}
3291			return el;
3292		};
3293	}
3294
3295	module.exports = autosize;
3296});
3297;/*})'"*/;/*})'"*/
3298$(document).ready(function(){
3299
3300  $('div#blockrighttabs ul').each(function() {
3301    $(this).find('li').each(function(i) {
3302      $(this).click(function(){
3303        $(this).addClass('active topics-side__tab--active').siblings().removeClass('active topics-side__tab--active')
3304          .parents('div.block').find('div.blockrighttab-div').hide().end().find('div.blockrighttab-div:eq('+i+')').fadeIn(150);
3305        return false;
3306      });
3307    });
3308  });
3309
3310});
3311;/*})'"*/;/*})'"*/
3312const LYRICS_TIME_OFFSET = 0.5;
3313if (typeof gtag === 'undefined') { window.gtag = function() {}; }
3314
3315jQuery.event.special.touchstart = {
3316    setup: function( _, ns, handle ) {
3317        this.addEventListener('touchstart', handle, { passive: !ns.includes('noPreventDefault') });
3318    }
3319};
3320jQuery.event.special.touchmove = {
3321    setup: function( _, ns, handle ) {
3322        this.addEventListener('touchmove', handle, { passive: !ns.includes('noPreventDefault') });
3323    }
3324};
3325
3326
3327/**
3328 * Gets the HTML content for a footnote tooltip by finding the corresponding <li>
3329 * based on the link's href attribute.
3330 *
3331 * @param {HTMLElement} linkElement The <a> element linking to the footnote.
3332 * @returns {string} The HTML content of the footnote, or an empty string.
3333 */
3334function getFootnoteTooltipContent(linkElement) {
3335  if (!linkElement) {
3336    return '';
3337  }
3338  var href = linkElement.getAttribute('href');
3339  if (href && href.startsWith('#')) {
3340      var targetId = href.substring(1);
3341      // Use plain JS for finding element by ID for potentially better performance/reliability
3342      var targetLi = document.getElementById(targetId);
3343      if (targetLi) {
3344          // Clone the li using jQuery to easily manipulate it
3345          var $cloneLi = $(targetLi).clone();
3346          // Remove the back-link label(s)
3347          $cloneLi.find('a.footnote-label, a.footnote-multi').remove();
3348          // Get the remaining HTML, trim whitespace
3349          var contentHtml = $cloneLi.html().trim();
3350          if (contentHtml) {
3351            // Wrap the HTML string in a jQuery object/detached element
3352            // This is safer for Tooltipster when contentAsHTML is true.
3353            return $('<div></div>').html(contentHtml);
3354          }
3355          return ''; // Return empty string (Tooltipster can handle this)
3356                     // or an empty jQuery object: $()
3357      }
3358  }
3359  return ''; // Return empty if href is missing or target not found
3360}
3361
3362$(document).ready(function() {
3363
3364  // Navigation menu
3365  var $navWidth = (deviceType() == 'mobile') ? '290' : '310';
3366  var $userNavWidth = (deviceType() == 'mobile') ? '290' : '400';
3366 // Wider for desktop
3367  var $navPosition = 'right'; // Always open from right for consistency
3368  var $isRTL = $('body.rtl').length > 0;
3369
3370  var $navTitleLT = $('<img src="/sites/all/themes/lt7/img/logo.svg" alt="Home" id="logo-mobile-img" width="48" height="36">');
3371  if ($('body.not-logged-in').length) {
3372    // Guests: defer hcOffcanvasNav init to first burger click (saves ~553+ cloned DOM elements)
3373    var _guestNavInited = false;
3374    var _guestIsNavEmpty = true;
3375
3376    $(document).on('click', '[data-menu-burger]', function(e) {
3377      e.preventDefault();
3378      if (!_guestNavInited) {
3379        _guestNavInited = true;
3380        var $nav = $('#main-nav').hcOffcanvasNav({
3381          customToggle: $('.main-nav-toggle'),
3382          navTitle: $navTitleLT,
3383          width: $navWidth,
3384          levelTitles: true,
3385          levelTitleAsBack: true,
3386          levelOpen: 'expand',
3387          labelBack: Drupal.t('Back'),
3388          position: $navPosition,
3389          rtl: $isRTL,
3390          swipeGestures: false
3391        });
3392        var Nav = $nav.data('hcOffcanvasNav');
3393
3394        Nav.on('open', function(e, settings) {
3395          if (_guestIsNavEmpty) {
3396            $(e.currentTarget).find('.nav-content').addClass('ajax_loading');
3397            var languagevarq = Drupal.settings.languagevarq;
3398            $.ajax({
3399              type: "POST",
3400              dataType: 'json',
3401              url: '/' + languagevarq + '/callback/nav1',
3402              success: function(data) {
3403                $('#main-nav').html(data.html);
3404                Nav.update(true);
3405                _guestIsNavEmpty = false;
3406              },
3407            });
3408          }
3409        });
3410
3411        Nav.open();
3412      } else {
3413        var navInstance = $('#main-nav').data('hcOffcanvasNav');
3414        if (navInstance) navInstance.open();
3415      }
3416    });
3417    // Guests have no .user-avatar or #user-nav content — skip user-nav init entirely
3418  }
3419  else {
3420    // Logged-in: immediate init (user-specific content in both navs)
3421    var $nav = $('#main-nav').hcOffcanvasNav({
3422      customToggle: $('.main-nav-toggle'),
3423      navTitle: $navTitleLT,
3424      width: $navWidth,
3425      levelTitles: true,
3426      levelTitleAsBack: true,
3427      levelOpen: 'expand',
3428      labelBack: Drupal.t('Back'),
3429      position: $navPosition,
3430      rtl: $isRTL,
3431      swipeGestures: false
3432    });
3433
3434    var $navTitleUser = $('.user-avatar').clone();
3435    if (!$navTitleUser.find('.text').length) {
3436      $navTitleUser.append( $('#user-name').text() );
3437    }
3438
3439    $navPosition = $isRTL ? 'left' : 'right';
3440
3441    $('#user-nav').hcOffcanvasNav({
3442      customToggle: $('.user-nav-toggle'),
3443      navTitle: $navTitleUser,
3444      width: $userNavWidth,
3445      levelTitles: true,
3446      levelTitleAsBack: true,
3447      levelOpen: 'expand',
3448      position: $navPosition,
3449      rtl: $isRTL,
3450      swipeGestures: false
3451    });
3452
3453    $(document).on('click', '[data-menu-burger]', function(e) {
3454      e.preventDefault();
3455      var navInstance = $('#main-nav').data('hcOffcanvasNav');
3456      if (navInstance) navInstance.open();
3457    });
3458
3459    $(document).on('click', '.user-avatar', function(e) {
3460      e.preventDefault();
3461      var userNavInstance = $('#user-nav').data('hcOffcanvasNav');
3462      if (userNavInstance) userNavInstance.open();
3463    });
3464  }
3465
3466  // tooltips
3467  // https://www.heteroclito.fr/modules/tooltipster/
3468
3469
3470
3471
3472  // Initialize tooltipster for footnotes (.see-footnote) individually using custom content logic
3473  $('.see-footnote').each(function() {
3474    var currentLink = this; // Capture the current link element
3475
3476    $(this).tooltipster({
3477      theme: ['tooltipster-shadow', 'tooltipster-large-padding'],
3478      maxWidth: 400,
3479      delay: 100,
3480      side: ['right', 'top', 'bottom', 'left'],
3481      interactive: true,
3482      multiple: true,
3483      contentAsHTML: true, // Needed for footnotes with HTML
3484      // Use the refactored function to get content
3485      content: function(instance, helper) {
3486        // Pass the originally captured link element to the function
3487        return getFootnoteTooltipContent(currentLink);
3488      }
3489    });
3490  });
3491
3492  // Initialize tooltipster for generic tooltips (.tooltip) using default behavior
3493  $('.tooltip').tooltipster({
3494    theme: 'tooltipster-shadow',
3495    maxWidth: 400,
3496    delay: 100,
3497    side: ['right', 'top', 'bottom', 'left'],
3498    interactive: true,
3499    multiple: true,
3500    // Using default content discovery (likely title attribute)
3501    // Add functionFormat to ensure content from title attribute is treated as plain text
3502    functionFormat: function(instance, helper, content) {
3503      // The 'content' parameter is the string from the title attribute.
3504      // Returning it directly should make Tooltipster use it as plain text,
3505      // respecting the default contentAsHTML: false.
3506      return content;
3507    }
3508  });
3509
3510  $('.tooltiptop, img.badge').tooltipster({
3511    theme: 'tooltipster-shadow',
3512    maxWidth: 400,
3513    side: ['top', 'bottom', 'tight', 'left'],
3514    interactive: true,
3515    trigger: 'hover',
3516    delay: 100,
3517    multiple: true,
3518  });
3519
3520  $('.tooltipclick').tooltipster({
3521    theme: ['tooltipster-shadow', 'tooltipster-large-padding'],
3522    maxWidth: 400,
3523    side: ['top', 'right', 'bottom', 'left'],
3524    interactive: true,
3525    contentAsHTML: true,
3526    trigger: 'click'
3527  });
3528
3529  // Initialize tooltipster for comment action buttons (like, reply, more)
3530  $('.comment-like-btn, .comment-reply-btn, .comment-more-btn').tooltipster({
3531    theme: 'tooltipster-shadow',
3532    maxWidth: 200,
3533    side: ['top', 'bottom', 'left', 'right'],
3534    delay: 200,
3535  });
3536
3537  // Video preview tooltip — delegated, init on first hover
3538  $(document).on('mouseenter', '.video-icon-player.active-player', function() {
3539    var $el = $(this);
3540    if ($el.data('tooltipsterNs')) return; // already initialized
3541    $el.tooltipster({
3542      content: '<div class="ajax_loading"></div>',
3543      contentAsHTML: true,
3544      interactive: true,
3545      delay: 100,
3546      animation: 'fade',
3547      updateAnimation: false,
3548      theme: ['tooltipster-shadow', 'tooltipster-shadow-customized'],
3549      functionBefore: function(instance, helper) {
3550        var youtubeId = $(helper.origin).attr('youtube');
3551        if (!youtubeId) return;
3552        instance.content('<div class="ajax_loading"></div>');
3553        var img = new Image();
3554        img.onload = function() {
3555          instance.content('<div class="tooltip-background" style="background-image: url(' + img.src + ');"><span class="lty-playbtn"></span></div>');
3556        };
3557        img.src = 'https://i.ytimg.com/vi/' + youtubeId + '/hqdefault.jpg';
3558      },
3559      functionReady: function(instance, helper) {
3560        $(helper.tooltip).on('click', function() {
3561          $(helper.origin).trigger('click');
3562        });
3563      }
3564    });
3565    $el.tooltipster('open');
3566  });
3567
3568  // auto direction (rtl or ltr)
3569  $("textarea, input, #comments p, .field-field-author-comments .field-items p, .authorcommenttext p, .forum-post-content p, .privatemsg-message-body p").each( function() {
3570    $(this).attr("dir","auto");
3571  });
3572
3573  // green_buttons: track action select changes (new design)
3574  $(document).on('change', '.songs__action-btn select', function() {
3575    var val = $(this).val();
3576    if (!val) return;
3577    var act = 'unknown';
3578    if (val.indexOf('add/song') !== -1 || val.indexOf('add/transrequest') !== -1) act = 'add_song';
3579    else if (val.indexOf('add/translate') !== -1) act = 'add_translation';
3580    else if (val.indexOf('add/request') !== -1) act = 'add_request';
3581    else if (val.indexOf('add/subtitles') !== -1) act = 'add_subtitles';
3582    else if (val.indexOf('proofreading') !== -1) act = 'proofreading';
3583    if (typeof gtag === 'function') {
3584      gtag('event', 'green_buttons', { 'user_action': act });
3585    }
3586  });
3587
3588  // green_buttons: track add/request links in overlay menus (mobile)
3589  $(document).on('click', '.menu-b__link[href*="add/translate"], .menu-b__link[href*="add/request"], .menu-b__link[href*="add/song"], .menu-b__link[href*="add/transrequest"], .menu-b__link[href*="add/subtitles"]', function() {
3590    var href = $(this).attr('href') || '';
3591    var act = 'unknown';
3592    if (href.indexOf('add/song') !== -1 || href.indexOf('add/transrequest') !== -1) act = 'add_song';
3593    else if (href.indexOf('add/translate') !== -1) act = 'add_translation';
3594    else if (href.indexOf('add/request') !== -1) act = 'add_request';
3595    else if (href.indexOf('add/subtitles') !== -1) act = 'add_subtitles';
3596    if (typeof gtag === 'function') {
3597      gtag('event', 'green_buttons', { 'user_action': act });
3598    }
3599  });
3600
3601
3602  /**
3603   * Clearable text inputs
3604   */
3605  function tog(v){return v ? "addClass" : "removeClass";}
3606  $(document).on("input", ".clearable", function(){
3607      $(this)[tog(this.value)]("x");
3608  }).on("mousemove", ".x", function( e ){
3609      $(this)[tog(this.offsetWidth-18 < e.clientX-this.getBoundingClientRect().left)]("onX");
3610  }).on("touchstart click", ".onX", function( ev ){
3611      ev.preventDefault();
3612      $(this).removeClass("x onX").val("").change();
3613  });
3614  $("input.clearable").trigger("input");
3615
3616
3617
3618  // analytics for form submit
3619  $(document).on('submit','form',function(){
3620    var form = $(this);
3621    var fid = form.attr('id');
3622    if (! fid) {
3623      fid = 'noname';
3624    }
3625    gtag('event', 'form_submit', {
3626     'form_id': fid,
3627    });
3628  });
3629
3630  // monitor click on video
3631  $(document).on("click", "lite-youtube", function(e){
3632    gtag('event', 'video_load', {
3633      'video_category' : 'lite-youtube'
3634    });
3635  });
3636
3637  // close alert
3638  $(document).on("click", "a.alert-close", function(e){
3639    e.preventDefault();
3640    $el = $(this);
3641    $p  = $el.parent();
3642    $p.hide();
3643  });
3644
3645
3646  var $el, $ps, $up, $totalHeight;
3647  $(document).on("click", ".expand-text-wrapper .expand-button", function(e){
3648    e.preventDefault();
3649    $totalHeight = 0;
3650    $el = $(this);
3651    $p  = $el.parent();
3652    $up = $p.parent();
3653    $up
3654      .css({
3655        "height": 'auto',
3656        "max-height": 9999
3657      });
3658    $p.css('display', 'none');
3659  });
3660
3661
3662  $('.wrapper-visible-part').on('click', function (e) {
3663    $(this).find('.wrapper-hidden-part').show();
3664    $(this).find('.wrapper-hide-on-click').hide();
3665  });
3666
3667
3668  $(document).on('click', '.wrapper-visible-part-inline', function (e) {
3669    $(this).find('.wrapper-hidden-part').css( "display", "inline-flex" );
3670    $(this).find('.wrapper-hide-on-click').hide();
3671  });
3672
3673
3674  var loc = getPageType();
3675  var loggedIn = $("body.logged-in").length ? 'user' : 'guest';
3676  var isGuest = $("body").hasClass("not-logged-in") ? true : false;
3677
3678  if (loggedIn) {
3679    $("#lyrics-formatting-confirm").click( function(e) {
3680      $('#lyrics-formatting-confirm').addClass("ajax_loading_small");
3681      linkurl = '/' + Drupal.settings.languagevarq + '/callback/set/lyricsformattingconfirm';
3682
3683      $.ajax({
3684        type: "POST",
3685        url: linkurl,
3686        data: {},
3687        dataType: 'json',
3688        success: function(result) {
3689          if(result && (result.ok == 'ok')) {
3690            $('.announcement-container').fadeOut();
3691          }
3692          else {
3693            console.log('can not confirm');
3694          }
3695        }
3696      });
3697    });
3698  }
3699
3700
3701  if (loggedIn || (isGuest && (loc != 'song') && (loc != 'translation') && (loc != 'artist'))) {
3702    if ($(".messages").length) {
3703      // Prepend a close button to each message.
3704      $(".messages:not(.messageclose-processed)").each( function() {
3705        $(this).addClass('messageclose-processed');
3706        $(this).prepend('<a href="#" class="messageclose" title="' + Drupal.t('close') + '">&times;</a>');
3707      });
3708
3709      // When a close button is clicked hide this message.
3710      $(".messages a.messageclose").click( function(event) {
3711        event.preventDefault();
3712        $(this).parent().fadeOut("slow", function() {
3713          var messages_left = $("#messages .section").children().size();
3714          if (messages_left == 1) {
3715            $("#messages").remove();
3716          }
3717          else {
3718            $(this).remove();
3719          }
3720        });
3721      });
3722    }
3723  }
3724
3725  // colorbox for some links
3726  if ($("body").hasClass("front") && $("body").hasClass("not-logged-in") && $(".front-search-block-right a").length) {
3727    $('.front-search-block-right a').colorboxNode({'launch': false, 'width':'320px', 'height':'550px'});
3728  }
3729
3730  // show/hide part of long texts
3731  textMinMore(200);
3732
3733  // share_content: track share menu open
3734  $(document).on('click', '[data-more-toggle*="share"], #artist-share-toggle-guest', function() {
3735    if (typeof gtag === 'function') {
3736      gtag('event', 'share_content', { 'share_content_action': 'open' });
3737    }
3738  });
3739
3740  // share_content: track final clicks in share overlay (Facebook, X links)
3741  $(document).on('click', '[data-more-content*="share"] .menu-b__link:not(.social-copy-url):not(#system-share-btn)', function() {
3742    if (typeof gtag === 'function') {
3743      var provider = $.trim($(this).find('.text').text()).toLowerCase() || 'unknown';
3744      gtag('event', 'share_content', { 'share_content_provider': provider });
3745    }
3746  });
3747
3748  // share_content: track direct FB/X links below lyrics (translate__btns)
3749  $(document).on('click', '.translate__btn[target="_blank"]', function() {
3750    if (typeof gtag === 'function') {
3751      var title = $(this).attr('title') || '';
3752      var provider = 'unknown';
3753      if (title.indexOf('Facebook') !== -1) provider = 'facebook';
3754      else if (title.indexOf('X') !== -1) provider = 'x';
3755      gtag('event', 'share_content', { 'share_content_provider': provider });
3756    }
3757  });
3758
3759  // block click statistics
3760  $(document).on("click", "div.block", function(e){
3761
3762    var block_id = $(this).attr('id');
3763
3764    if (block_id) {
3765      block_id = block_id.replace("lyricstranslateblocks-", "");
3766      block_id = block_id.replace("lyricstranslate", "");
3767      gtag('event', 'block_use', {
3768        'block_id' : block_id
3769      });
3770    }
3771
3772  });
3773
3774  // sticky affiliate link
3775  var stickyBanner = $("#tonefuse_container1");
3776
3777  // tonefuse statistics (GA4) — gtag survives page unload via its own
3778  // pagehide flush, so the bubble phase is fine for monitorTonefuse.
3779  $(document).on("click", "#tonefuse_container1", function(e){
3780    monitorTonefuse(tonefuseLabel(this), 'top', tonefuseExtras(this));
3781  });
3782
3783  $(document).on("click", "#tonefuse_container2", function(e){
3784    monitorTonefuse(tonefuseLabel(this), 'bottom', tonefuseExtras(this));
3785  });
3786
3787});
3788
3789
3790$(document).ready(function() {
3791
3792
3793  // sticky ads for guests on song/translation pages
3794  if ($("body").hasClass("not-logged-in") && $("#sd").length && $(".content__side").length) {
3795    var $sidebar = $(".content__side");
3796    var $footer = $("#footer, .footer");
3797
3798    if ($footer.length && $sidebar.length) {
3799      var dis = $footer.offset().top - $sidebar.offset().top;
3800
3801      if ((dis > 700) && ($(window).width() > 750)) {
3802        setTimeout(function(){
3803          if ($.fn.hcSticky) {
3804            $sidebar.hcSticky({
3805              stickTo: ".content__container",
3806              innerSticker: "#sd",
3807              followScroll: false,
3808              responsive: {
3809                750: {
3810                  disable: true
3811                }
3812              }
3813            });
3814          }
3815        }, 3000);
3816      }
3817    }
3818  }
3819
3820
3821  // sticky video for lyrics request
3822  if ($.fn.hcSticky && $("body.node-type-lttransrequest").length) {
3823    $('body.node-type-lttransrequest #vd').hcSticky({
3824      responsive: {
3825        750: {
3826          disable: true
3827        }
3828      }
3829    });
3830  }
3831
3832  // sticky video for songs
3833  if ($.fn.hcSticky) {
3834    $('body.node-type-song.logged-in #vd, body.node-type-song.nadlt.not-logged-in #vd').hcSticky({
3835      top: 10,
3836      followScroll: false,
3837      responsive: {
3838        750: {
3839          disable: true
3840        }
3841      }
3842    });
3843  }
3844
3845
3846
3847
3848  $(document).on("click", ".social-copy-url", function(e){
3849      e.preventDefault();
3850      if (typeof gtag === 'function') {
3851        gtag('event', 'share_content', { 'share_content_provider': 'copy_link' });
3852      }
3853      var currentButton = $(this); // Зберігаємо поточний елемент
3854      var urlToCopy = currentButton.attr('data-copy-url') || window.location.href;
3855      navigator.clipboard.writeText(urlToCopy).then(function() {
3856          // Зберігаємо іконку поточної кнопки: спершу шукаємо <i>, інакше .icon
3857          var iconEl = currentButton.find('i').first();
3858          if (!iconEl.length) { iconEl = currentButton.find('.icon').first(); }
3859          var iconHtml = iconEl.length ? iconEl.prop('outerHTML') : '';
3860          // Змінюємо текст кнопки поточної кнопки
3861          currentButton.html((iconHtml ? iconHtml + ' ' : '') + Drupal.t('Link copied'));
3862
3863          // Повертаємо початковий текст через 2 секунди
3864          setTimeout(function() {
3865              currentButton.html((iconHtml ? iconHtml + ' ' : '') + Drupal.t('Copy link'));
3866          }, 2000);
3867      }).catch(function(err) {
3868          console.error('Failed to copy link: ', err);
3869      });
3870  });
3871
3872  // Safety: prevent default navigation on lyrics copy anchors globally.
3873  // Main copy-lyrics logic lives in js_new/song.js; this ensures no page jump even if that script loads later.
3874  $(document).on("click", ".social-copy-lyrics", function(e){
3875      e.preventDefault();
3876  });
3877
3878
3879  var saveSrc = '';
3880
3881  $(window).resize(function () {
3882
3883    var player1 = $(".node-type-song .song-node-video div.youtube-player iframe");
3884    var player2 = $(".node-type-song .mobile-video-player div.youtube-player iframe");
3885
3886    if ((player1.length || player2.length) && (saveSrc == '')) {
3887      saveSrc = player1.length ? player1.attr("src") : (player2.length ? player2.attr("src") : '');
3888      saveSrc = saveSrc.replace("autoplay=1", "autoplay=0");
3889    }
3890
3891    if ($(this).width() < 1000) {
3892      if (player1.length) {
3893        player1.attr("src","");
3894      }
3895
3896      if (player2.length && (! player2.attr("src"))) {
3897        player2.attr("src", saveSrc);
3898      }
3899    }
3900    else {
3901      if (player2.length) {
3902        player2.attr("src","");
3903      }
3904
3905      if (player1.length && (! player1.attr("src"))) {
3906        player1.attr("src", saveSrc);
3907      }
3908    }
3909
3910  });
3911
3912});
3913
3914function textMinMore(num) {
3915  var minimized_elements = $('.minimize-' + num);
3916
3917  minimized_elements.each(function(){
3918    var t = $(this).text();
3919    if(t.length < num) return;
3920
3921    $(this).html(
3922        t.slice(0,num)+'<span>... </span><a href="#" class="minmore">&#8921;</a>'+
3923        '<span style="display:none;">'+ t.slice(num,t.length)+' <a href="#" class="minless">&#8920;</a></span>'
3924    );
3925  });
3926
3927  $('a.minmore', minimized_elements).click(function(event){
3928      event.preventDefault();
3929      $(this).hide().prev().hide();
3930      $(this).next().show();
3931  });
3932
3933  $('a.minless', minimized_elements).click(function(event){
3934      event.preventDefault();
3935      $(this).parent().hide().prev().show().prev().show();
3936  });
3937}
3938
3939function deviceType() {
3940
3941  var dev = 'desktop';
3942  var mobileBreakpoint = 36.31125 * 16; // 36.31125rem in pixels
3943
3944  if ($(window).width() < mobileBreakpoint) {
3945    dev = 'mobile';
3946  }
3947  else if ($(window).width() < 1000) {
3948    dev = 'tablet';
3949  }
3950
3951  return dev;
3952}
3953
3954function hasUrl(s) {
3955  var regexp1 = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
3956  var regexp2 = /<a\s/i;
3957  if (regexp1.test(s) || regexp2.test(s)) {
3958    return true;
3959  }
3960  else {
3961    return false;
3962  }
3963}
3964
3965function getPageType() {
3966
3967  var loc = 'unknown';
3968
3969  if ($("body").hasClass( "node-type-song" )) {
3970    loc = 'song';
3971  }
3972  else if ($("body").hasClass( "node-type-translation" )) {
3973    loc = 'translation';
3974  }
3975  else if ($("body").hasClass( "node-type-request" )) {
3976    loc = 'request';
3977  }
3978  else if ($("body").hasClass( "node-type-artist" )) {
3979    loc = 'artist';
3980  }
3981  else if ($("body").hasClass( "node-type-collection" )) {
3982    loc = 'collection';
3983  }
3984  else if ($("body").hasClass( "page-user" )) {
3985    loc = 'user';
3986  }
3987  else if ($("body").hasClass( "node-type-lttransrequest" )) {
3988    loc = 'transrequest';
3989  }
3990  else if ($("body").hasClass( "front" )) {
3991    loc = 'front';
3992  }
3993
3994  return loc;
3995}
3996
3997function getUserType() {
3998
3999  var loc = 'guest';
4000
4001  if ($("body.logged-in").length) {
4002    loc = 'user';
4003  }
4004
4005  return loc;
4006
4007}
4008
4009function ispinner(obj) {
4010  var html = '<div class="ispinner gray animating">';
4011  for (var i = 0; i < 12; i++) {
4012    html += '<div class="ispinner-blade"></div>';
4013  }
4014  html += '</div>';
4015  obj.html(html);
4016}
4017
4018// ToneFuse injects <style>/<script> into the container in a way that
4019// innerText doesn't always exclude (rules from injected <style> can show up
4020// in the label, ~4k events/day). Clone and strip them before reading.
4021function tonefuseLabel(el) {
4022  if (!el) return '';
4023  var clone = el.cloneNode(true);
4024  var junk = clone.querySelectorAll('style, script');
4025  for (var i = 0; i < junk.length; i++) junk[i].parentNode.removeChild(junk[i]);
4026  return ((clone.innerText || clone.textContent) || '').replace(/\s+/g, ' ').trim();
4027}
4028
4029// Extracts ToneFuse's own identifiers from <tonefuse-ad data-tf_context="...">
4030// Returns null when the ad has not rendered yet or markup changed.
4031function tonefuseExtras(el) {
4032  if (!el || !el.querySelector) return null;
4033  var ctxEl = el.querySelector('[data-tf_context]');
4034  if (!ctxEl) return null;
4035  try {
4036    var ctx = JSON.parse(ctxEl.getAttribute('data-tf_context'));
4037    return { creative_id: ctx.creative_id, geo: ctx.geo_country, ad_unit: ctx.ad_unit_id };
4038  } catch (e) {
4039    return null;
4040  }
4041}
4042
4043// Brand from the ad label — ToneFuse keeps the brand name in the label text
4044// ("... on Apple Music", "Amazon Music Unlimited (ad)", "... on Bandsintown").
4045// "синтаун" catches Bandsintown transliterated into Cyrillic (sr/bg locales).
4046function tonefuseBrand(label) {
4047  if (!label) return '';
4048  if (label.indexOf('Apple Music') !== -1)  return 'Apple';
4049  if (label.indexOf('Amazon Music') !== -1) return 'Amazon';
4050  if (label.indexOf('Bandsintown') !== -1 || label.indexOf('синтаун') !== -1) return 'Bandsintown';
4051  return 'Other';
4052}
4053
4054// GA4 SDK coerces all-digit strings into int_value; custom dimensions read
4055// only string_value. Prefix to keep them stringly-typed.
4056function monitorTonefuse(text, location, extras) {
4057  var payload = {
4058    tonefuse_location: location,
4059    tonefuse_label: text
4060  };
4061  var brand = tonefuseBrand(text);
4062  if (brand) payload.tonefuse_brand = brand;
4063  if (extras) {
4064    if (extras.creative_id) payload.tonefuse_creative_id = 'cr-' + extras.creative_id;
4065    if (extras.geo)         payload.tonefuse_geo         = extras.geo;
4066    if (extras.ad_unit)     payload.tonefuse_ad_unit     = 'au-' + extras.ad_unit;
4067  }
4068  gtag('event', 'tonefuse', payload);
4069}
4070
4071function createCookie(name, value, days) {
4072  var expires;
4073
4074  if (days) {
4075    var date = new Date();
4076    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
4077    expires = "; expires=" + date.toGMTString();
4078  } else {
4079    expires = "";
4080  }
4081  document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + expires + "; path=/";
4082}
4083
4084function readCookie(name) {
4085  var nameEQ = encodeURIComponent(name) + "=";
4086  var ca = document.cookie.split(';');
4087  for (var i = 0; i < ca.length; i++) {
4088    var c = ca[i];
4089    while (c.charAt(0) === ' ')
4090      c = c.substring(1, c.length);
4091    if (c.indexOf(nameEQ) === 0)
4092      return decodeURIComponent(c.substring(nameEQ.length, c.length));
4093  }
4094  return null;
4095}
4096
4097function eraseCookie(name) {
4098  createCookie(name, "", -1);
4099}
4100
4101
4102function isNumeric(str) {
4103  if (typeof str != "string") return false // we only process strings!
4104  return !isNaN(str) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)...
4105         !isNaN(parseFloat(str)) // ...and ensure strings of whitespace fail
4106}
4107
4108function isMacOS() {
4109  const userAgent = window.navigator.userAgent;
4110  return /Macintosh/.test(userAgent) && !window.MSStream;
4111}
4112
4113function isiOS() {
4114  const userAgent = window.navigator.userAgent;
4115  return /iPad|iPhone|iPod/.test(userAgent) && !window.MSStream;
4116}
4117
4118function getOperatingSystem() {
4119  var userAgent = navigator.userAgent;
4120  var windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE'];
4121  var os = null;
4122
4123  if (windowsPlatforms.indexOf(window.navigator.platform) !== -1) {
4124      os = 'Windows';
4125  } else if (!os && /Mac/.test(userAgent)) {
4126      os = 'MacOS';
4127  } else if (!os && /Linux/.test(userAgent)) {
4128      os = 'Linux';
4129  } else if (!os && /Android/.test(userAgent)) {
4130      os = 'Android';
4131  } else if (!os && /iPhone|iPad|iPod/.test(userAgent)) {
4132      os = 'iOS';
4133  }
4134
4135  return os;
4136}
4137
4138// Функція для завантаження JS-файлів
4139function loadJSFile(path) {
4140    return new Promise((resolve, reject) => {
4141        $.getScript(path, function (data, textStatus, jqxhr) {
4142            if (textStatus === "success") {
4143                resolve(data);
4144            } else {
4145                reject(new Error("Failed to load script: " + path));
4146            }
4147        });
4148    });
4149}
4150
4151// Функція для завантаження даних і зберігання їх в window.ltSubtitlesData
4152function loadSubtitles(path) {
4153    return new Promise((resolve) => {
4154        $.getJSON(path)
4155            .done(function(data) {
4156                window.ltSubtitlesData = data;
4157                if (data && data['webtvv']) {
4158                    gtag('event', 'subtitles_load');
4159                }
4160                resolve(data);
4161            })
4162            .fail(function() {
4163                // Якщо щось пішло не так - повертаємо пустий об'єкт
4164                window.ltSubtitlesData = {};
4165                resolve({});
4166            });
4167    });
4168}
4169
4170function loadPlayerFunctionsScript() {
4171    return new Promise((resolve, reject) => {
4172        const script = document.createElement('script');
4173        script.src = "/sites/all/themes/lt7/js/jmenu/youtubePlayerFunctions.js?23";
4174        script.onload = resolve;
4175        script.onerror = reject;
4176        document.body.appendChild(script);
4177    });
4178}
4179;/*})'"*/;/*})'"*/
4180var vjsPlayer, vjsSongs;
4181var videoIconsCache = null;
4182
4183// Cache DOM elements for better performance
4184function initVideoIconsCache() {
4185  if (!videoIconsCache) {
4186    videoIconsCache = $('#lyrics-settings-video, .video-icon-player.active-player');
4187  }
4188  return videoIconsCache;
4189}
4190
4191// Reset cache when new icons are added
4192function resetVideoIconsCache() {
4193  videoIconsCache = null;
4194}
4195
4196function onReady() {
4197  const activeIcon = $(".isactive");
4198  setVideoIconState(activeIcon, true);
4199}
4200
4201function onStop(player) {
4202  var current = player.currentSrc();
4203  $('#lyrics-settings-video, .video-icon-player.active-player').each(function () {
4204    if (current.indexOf($( this ).attr("youtube")) > -1) {
4205      setVideoIconState($(this), false);
4206    }
4207  });
4208}
4209
4210function onPause(player) {
4211  var current = player.currentSrc();
4212  var icons = initVideoIconsCache();
4213
4214  icons.each(function () {
4215    var $this = $(this);
4216    if (current.indexOf($this.attr("youtube")) > -1) {
4217      setVideoIconState($this, false);
4218    }
4219  });
4220}
4221
4222function onPlayComplete() {
4223  if (typeof vjsPlayer === 'undefined' || !vjsPlayer) {
4224    return;
4225  }
4226  var current = vjsPlayer.currentSrc();
4227  $('#lyrics-settings-video, .video-icon-player.active-player').each(function () {
4228    if (current.indexOf($( this ).attr("youtube")) > -1) {
4229      setVideoIconState($(this), false);
4230    }
4231  });
4232  // Also reset artist-settings-video button state
4233  $("#artist-settings-video").removeClass("isactive");
4234  setVideoIconState($("#artist-settings-video"), false);
4235}
4236
4237function onPlay(player) {
4238  $(".vjs-selected")[0].scrollIntoView({behavior: "smooth", block: "center"});
4239  var current = player.currentSrc();
4240  var icons = initVideoIconsCache();
4241
4242  icons.each(function () {
4243    var $this = $(this);
4244    if (current.indexOf($this.attr("youtube")) > -1) {
4245      setVideoIconState($this, true);
4246    } else {
4247      setVideoIconState($this, false);
4248    }
4249  });
4250
4251  // var loc = getPageType();
4252  // ga('send', 'event', 'Video', 'Icon', loc);
4253}
4254
4255
4256function onFirstPlay(player) {
4257  var current = player.currentSrc();
4258  var icons = initVideoIconsCache();
4259
4260  if (current.indexOf($(".isactive").attr("youtube")) == -1) {
4261    $(".isactive").removeClass("isactive");
4262  }
4263
4264  let lyricsId = 0;
4265  let youtubeId = '';
4266
4267  // find current icon and make it active + find attributes for subtitles
4268  icons.each(function () {
4269    var $this = $(this);
4270    if (current.indexOf($this.attr("youtube")) != -1) {
4271      $this.addClass("isactive");
4272      setVideoIconState($this, true); // Set icon to playing state
4273      youtubeId = $this.attr("youtube");
4274      lyricsId = $this.attr("nid");
4275      lyricsSubtitles = $this.attr("cc");
4276    }
4277  });
4278
4279  // update "View lyrics" link
4280  if (lyricsId && $("#vjs-view-lyrics").length) {
4281    $("#vjs-view-lyrics").attr('href', '/' + Drupal.settings.languagevarq + '/node/' + lyricsId);
4282  }
4283
4284  // remove previous subtitles
4285  var tracks = player.remoteTextTracks();
4286  var trackToRemove;
4287
4288  for (var i = 0; i < tracks.length; i++) {
4289    var track = tracks[i];
4290    player.removeRemoteTextTrack(track);
4291  }
4292
4293  // load subtitles
4294  if (lyricsSubtitles) {
4295    const subtitlesPath = '/' + Drupal.settings.languagevarq + '/callback/subtitles/' + lyricsId + '/' + youtubeId;
4296    loadSubtitles(subtitlesPath)
4297      .then(() => {
4298        const data = window.ltSubtitlesData;
4299
4300        if (data.webtvv) {
4301
4302          let decodedString = data.webtvv.replace(/\\u([\d\w]{4})/gi, function (match, grp) {
4303              return String.fromCharCode(parseInt(grp, 16));
4304          });
4305
4306          const cleanedData = decodedString.replace(/^\s+/,''); // Remove leading whitespace
4307          const blob = new Blob([cleanedData], { type: 'text/vtt' });
4308          const blobUrl = URL.createObjectURL(blob);
4309
4310          const track = {
4311            src: blobUrl,
4312            kind: 'subtitles',
4313            srclang: data.langsrc,
4314            label: data.langname + ' LT',
4315            mode: 'showing'
4316          };
4317
4318          player.addRemoteTextTrack(track, true);
4319        }
4320      })
4321      .catch(error => {
4322          console.error("Error loading subtitles in onFirstPlay:", error);
4323      });
4324  }
4325
4326
4327
4328  if (typeof gtag !== 'undefined') gtag('event', 'video_load', {
4329    'video_category': 'sticky-youtube'
4330  });
4331
4332}
4333
4334function onClose() {
4335	// var player = VJS.getPlayer('vjs-player');
4336	// vjsPlayer.dispose();
4337  vjsPlayer.pause();
4338	$('#vjs-player-wrapper').hide();
4339	onPlayComplete();
4340
4341	// Disable video seek mode when player closes
4342	if (typeof disableVideoSeekMode === 'function') {
4343	  disableVideoSeekMode();
4344	}
4345}
4346
4347
4348function stickyVideoPlayer(obj) {
4349  var youtube = obj.attr('youtube');
4350  var lyricsNid = obj.attr('nid');
4351
4352  if (!youtube) {
4353    return;
4354  }
4355
4356  var path = '/sites/all/themes/lt7/js/videojs';
4357
4358  $('<link>')
4359      .appendTo('head')
4360      .attr({
4361        type: 'text/css',
4362        rel: 'stylesheet',
4363        href: path + '/video-js.min.css'
4364      });
4365
4366  $('<link>')
4367      .appendTo('head')
4368      .attr({
4369        type: 'text/css',
4370        rel: 'stylesheet',
4371        href: path + '/videojs-playlist-ui.css'
4372      });
4373
4374  $('<link>')
4375      .appendTo('head')
4376      .attr({
4377        type: 'text/css',
4378        rel: 'stylesheet',
4379        href: path + '/video-js-custom.css?new14'
4380      });
4381
4382  var dev = deviceType();
4383
4384  var offsetX = (dev === 'mobile') ? 0 : 5;
4385  var baseOffsetY = (dev === 'mobile') ? 0 : 5;
4386  function getAdhesiveHeight() {
4387    var ad = document.querySelector('.snigel-adhesive.bottom:not(.adhesive-hidden)');
4388    if (!ad) return 0;
4389    return ad.offsetHeight;
4390  }
4391  var offsetY = baseOffsetY + getAdhesiveHeight();
4392  var maxwidth = ($(window).width() > 1100) ? 500 : ((dev === 'mobile') ? window.innerWidth : 400);
4393  var maxheight = ($(window).width() > 1100) ? 280 : 225;
4394  maxwidth = ($("body.logged-in.node-type-lttransrequest").length || $("body.logged-in.page-node-edit.node-type-song").length || $("body.logged-in.page-node-edit.node-type-translation").length) ? 400 : maxwidth;
4395  maxheight = ($("body.logged-in.node-type-lttransrequest").length || $("body.logged-in.page-node-edit.node-type-song").length || $("body.logged-in.page-node-edit.node-type-translation").length) ? 225 : maxheight;
4396  var verticalPosition = $(".iflychat-popup").length ? "top" : "bottom";
4397  var horizontalPosition = $("body.rtl").length ? "left" : "right";
4398  var addKeyboardSupport = $("body").hasClass("not-logged-in") ? 'yes' : ($("body").hasClass("front") || $("body").hasClass("page-user")) ? 'yes' : 'no';
4399
4400  if (!$('#vjs-player-wrapper').length) {
4401    var playerWrapper = document.createElement('div');
4402    playerWrapper.id = 'vjs-player-wrapper';
4403    playerWrapper.style.cssText = 'position: fixed; z-index: 9999; ' + verticalPosition + ': ' + offsetY + 'px; ' + horizontalPosition + ': ' + offsetX + 'px;';
4404
4405    // playlist wrapper
4406    var playerPlaylist = document.createElement('div');
4407    playerPlaylist.id = 'vjs-playlist';
4408    playerPlaylist.classList.add('vjs-playlist');
4409    if (deviceType() == 'mobile' ) {
4410      playerPlaylist.style.cssText = 'max-width: ' + maxwidth + 'px; display: none;';
4411    }
4412    else {
4413      playerPlaylist.style.cssText = 'max-width: ' + maxwidth + 'px;';
4414    }
4415
4416    playerWrapper.appendChild(playerPlaylist);
4417
4418    // close button
4419    var closeButton = document.createElement('div');
4420    closeButton.classList.add('vjs-close');
4421    closeButton.style.cssText = horizontalPosition + ': 0;';
4422    playerWrapper.appendChild(closeButton);
4423
4424    // view lyrics link (hide when already on the lyrics node or its translation page)
4425    var currentSongId = (typeof Drupal !== 'undefined' && Drupal.settings && Drupal.settings.songnid) ? ('' + Drupal.settings.songnid) : '';
4426    var isSameNode = currentSongId && (('' + lyricsNid) === currentSongId);
4427    // Also hide if the sticky is spawned by the main header button for the same youtube
4428    var shouldShowHeader = (lyricsNid && lyricsNid !== '0') &&
4429                           ($("#lyrics-settings-video").attr('youtube') != youtube) &&
4430                           (!isSameNode);
4431    if (shouldShowHeader) {
4432      var playerHeader = document.createElement('div');
4433      playerHeader.classList.add('vjs-header');
4434      var lyricsLink = document.createElement('a');
4435      //lyricsLink.classList.add('vjs-view-lyrics');
4436      lyricsLink.id = 'vjs-view-lyrics';
4437      lyricsLink.href = '/' + Drupal.settings.languagevarq + '/node/' + lyricsNid;
4438      lyricsLink.innerHTML = Drupal.t('View lyrics');
4439      playerHeader.style.cssText = 'width: ' + maxwidth + 'px;';
4440      playerHeader.appendChild(lyricsLink);
4441      playerWrapper.appendChild(playerHeader);
4442      closeButton.style.top = '-21px';
4443    }
4444
4445
4446    // player
4447    var player = document.createElement('video');
4448    player.id = 'vjs-player';
4449    player.classList.add('video-js', 'vjs-player', 'vjs-default-skin', 'vjs-youtube');
4450    // player.classList.add('vjs-player', 'vjs-default-skin', 'video-js');
4451    // player.classList.add('vjs-player');
4452    player.style.cssText = 'max-width: ' + maxwidth + 'px; max-height: ' + maxheight + 'px;';
4453    playerWrapper.appendChild(player);
4454    document.body.appendChild(playerWrapper);
4455
4456    // Keep video above the sticky ad (Snigel adhesive)
4457    function adjustForAdhesive() {
4458      playerWrapper.style.bottom = (baseOffsetY + getAdhesiveHeight()) + 'px';
4459    }
4460
4461    // Watch for adhesive appearing/hiding (class changes, DOM additions)
4462    var adhesiveObserver = new MutationObserver(function() {
4463      adjustForAdhesive();
4464      // Once adhesive found, also watch its size
4465      var ad = document.querySelector('.snigel-adhesive.bottom');
4466      if (ad && !ad._resizeObserved) {
4467        ad._resizeObserved = true;
4468        new ResizeObserver(adjustForAdhesive).observe(ad);
4469      }
4470    });
4471    adhesiveObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] });
4472  }
4473
4474  Promise.all([
4475      loadJSFile(path + "/video.min.js"),
4476      loadJSFile('/sites/all/modules/custom/ltsubtitles/js/ltsubtitles.view.js')
4477  ]).then(() => {
4478    VJS = videojs;
4479    vjsSongs = [];
4480
4481    var autoplay = true;
4482
4483    // Вимикаємо autoplay для:
4484    // - мобільних пристроїв
4485    // - першого запуску в редакторі субтайтлів
4486    if (deviceType() == 'mobile' || window.temporaryDisableAutoplay) {
4487      autoplay = false;
4488    }
4489
4490    var options = {
4491      "techOrder": ["youtube"],
4492      "sources": [
4493        {
4494          "type": "video/youtube",
4495          "src": 'https://www.youtube.com/watch?v=' + youtube
4496        }],
4497      "youtube": {
4498        autohide: 1,
4499        disablekb: 1
4500      },
4501      autoplay: autoplay,
4502      controls: true,
4503      'height': maxheight,
4504      'width': maxwidth,
4505      'html5': {
4506        nativeTextTracks: false
4507      },
4508      'controlBar': {
4509        'children': [
4510          'playToggle',
4511          'volumePanel',
4512          'currentTimeDisplay',
4513          'timeDivider',
4514          'durationDisplay',
4515          'progressControl',
4516          'playbackRateMenuButton',
4517          'fullscreenToggle'
4518        ]
4519      },
4520      'playbackRates': [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2],
4521      'userActions': {
4522        'hotkeys': function(event) {
4523          // Left arrow - seek back 5s
4524          if (event.which === 37) {
4525            event.preventDefault();
4526            this.currentTime(Math.max(0, this.currentTime() - 5));
4527          // Right arrow - seek forward 5s
4528          } else if (event.which === 39) {
4529            event.preventDefault();
4530            this.currentTime(Math.min(this.duration(), this.currentTime() + 5));
4531          // J - seek back 10s
4532          } else if (event.which === 74) {
4533            event.preventDefault();
4534            this.currentTime(Math.max(0, this.currentTime() - 10));
4535          // L - seek forward 10s
4536          } else if (event.which === 76) {
4537            event.preventDefault();
4538            this.currentTime(Math.min(this.duration(), this.currentTime() + 10));
4539          // < (Shift+,) - decrease playback speed
4540          } else if (event.which === 188 && event.shiftKey) {
4541            event.preventDefault();
4542            var rates = this.playbackRates();
4543            var current = this.playbackRate();
4544            for (var i = rates.length - 1; i >= 0; i--) {
4545              if (rates[i] < current) { this.playbackRate(rates[i]); break; }
4546            }
4547          // > (Shift+.) - increase playback speed
4548          } else if (event.which === 190 && event.shiftKey) {
4549            event.preventDefault();
4550            var rates = this.playbackRates();
4551            var current = this.playbackRate();
4552            for (var i = 0; i < rates.length; i++) {
4553              if (rates[i] > current) { this.playbackRate(rates[i]); break; }
4554            }
4555          // Space or K - play/pause
4556          } else if (event.which === 32 || event.which === 75) {
4557            event.preventDefault();
4558            this.paused() ? this.play() : this.pause();
4559          }
4560        }
4561      }
4562    };
4563
4564    // Add custom CSS for playback rate button
4565    var style = document.createElement('style');
4566    style.textContent = `
4567      .vjs-playback-rate .vjs-playback-rate-value {
4568        font-size: 1em;
4569        line-height: 3;
4570      }
4571      .vjs-menu-button-popup .vjs-menu .vjs-menu-content {
4572        max-height: 25em;
4573      }
4574      .video-js .vjs-menu-button-popup .vjs-menu .vjs-menu-item {
4575        font-size: 0.8em;
4576      }
4577      .vjs-control-bar .vjs-fullscreen-control {
4578        position: absolute !important;
4579        right: 0 !important;
4580      }
4581
4582      .video-js .vjs-fullscreen-control {
4583        width: 3.5em;
4584        float: right !important;
4585      }
4586      .vjs-control-bar {
4587        display: flex;
4588        justify-content: flex-start;
4589        padding-right: 3.5em;
4590      }
4591    `;
4592    document.head.appendChild(style);
4593
4594    vjsPlayer = videojs('vjs-player', options, function onPlayerReady() {
4595      onReady();
4596
4597      $(document).trigger('vjsPlayerReady', [this]);
4598
4599      delete window.temporaryDisableAutoplay;
4600
4601      // Replace spinner with pause icon when player is ready
4602      $('.video-icon-player.isactive').each(function() {
4603        var $this = $(this);
4604        if ($this.find('.ispinner').length > 0) {
4605          $this.html('<div class="video-icon playing" style="width: 24px; height: 24px;"></div>');
4606        }
4607      });
4608
4609      this.on('firstplay', function () {
4610        onFirstPlay(this);
4611      });
4612      this.on('play', function () {
4613        onPlay(this);
4614      });
4615      this.on('pause', function () {
4616        onPause(this);
4617      });
4618      this.on('stop', function () {
4619        onStop(this);
4620      });
4621      this.on('ended', function () {
4622        // onClose();
4623      });
4624
4625      // Handle video errors - auto-flag unavailable videos
4626      // VideoJS YouTube tech doesn't trigger standard 'error' event,
4627      // so we check for errors after a short delay.
4628      // videojs-youtube encodes YT error codes as 1000 + original (see
4629      // Youtube.js error() method), so we subtract to recover it.
4630      var player = this;
4631      var errorCheckInterval = setInterval(function() {
4632        var error = player.error();
4633        if (error) {
4634          clearInterval(errorCheckInterval);
4635          var ytCode = error.code - 1000;
4636          if (lyricsNid && [100, 101, 150].indexOf(ytCode) !== -1) {
4637            $.ajax({
4638              url: '/flag_video.php',
4639              type: 'GET',
4640              data: { nid: lyricsNid, code: ytCode },
4641              dataType: 'json'
4642            });
4643          }
4644        }
4645      }, 1000);
4646      // Clear interval after 10 seconds to prevent memory leak
4647      setTimeout(function() {
4648        clearInterval(errorCheckInterval);
4649      }, 10000);
4650
4651      // highlight subtitles with lyrics
4652      // Check both old #lyrics-settings-video and new .video-icon-player.active-player
4653      var $videoEl = $("#lyrics-settings-video").length ? $("#lyrics-settings-video") : $(".video-icon-player.active-player[nid='" + lyricsNid + "']");
4654      if ($videoEl.length && $videoEl.attr('youtube') == youtube) {
4655          loadSubtitles('/' + Drupal.settings.languagevarq + '/callback/subtitles/' + lyricsNid + '/' + youtube)
4656            .then(() => {
4657              const data = window.ltSubtitlesData;
4658
4659              if (data.webtvv) {
4660                const originalTVV = data.webtvv;
4661
4662                const ltvtt = transformVTT(originalTVV);
4663                const cues = parseVTT(ltvtt);
4664
4665                addIdsToLines(cues);
4666
4667                // Add video seek icons to lines (for seek-on-click functionality)
4668                if (typeof addVideoSeekIcons === 'function') {
4669                  addVideoSeekIcons(cues);
4670                }
4671
4672                // Enable video seek mode (show icons on hover) - player is loaded
4673                if (typeof enableVideoSeekMode === 'function') {
4674                  enableVideoSeekMode();
4675                }
4676
4677                const throttledTimeUpdate = throttle(function() {
4678                    timeUpdateHandler.call(this, cues);
4679                }, 100);
4680
4681                this.on('timeupdate', throttledTimeUpdate);
4682              }
4683            })
4684            .catch(error => {
4685                console.error("Error loading subtitles in onPlayerReady:", error);
4686                // Clean up placeholder icons if subtitles failed to load
4687                document.querySelectorAll('.video-seek-icon.-placeholder').forEach(function(icon) { icon.remove(); });
4688                delete window.ltDeferredSeekLine;
4689            });
4690      }
4691
4692    });
4693
4694
4695    if (deviceType() != 'mobile') {
4696
4697      // 'prev' button
4698      var button = videojs.getComponent('Button');
4699      var previousButton = videojs.extend(button, {
4700        constructor: function() {
4701          button.apply(this, arguments);
4702          this.controlText(Drupal.t('Previous'));
4703          this.addClass('vjs-icon-previous-item');
4704        },
4705        handleClick: function() {
4706          vjsPlayer.playlist.previous();
4707        }
4708      });
4709      videojs.registerComponent('previousButton', previousButton);
4710      vjsPlayer.getChild('controlBar').addChild('previousButton', {}, 0);
4711
4712      // 'next' button
4713      var button = videojs.getComponent('Button');
4714      var nextButton = videojs.extend(button, {
4715        constructor: function() {
4716          button.apply(this, arguments);
4717          this.controlText(Drupal.t('Next'));
4718          this.addClass('vjs-icon-next-item');
4719        },
4720        handleClick: function() {
4721          vjsPlayer.playlist.next();
4722        }
4723      });
4724      videojs.registerComponent('nextButton', nextButton);
4725      vjsPlayer.getChild('controlBar').addChild('nextButton', {}, 2);
4726
4727      // 'playlist' button
4728      if ($(".video-icon-player.active-player").length > 1) {
4729        var button = videojs.getComponent('Button');
4730        var playlistButton = videojs.extend(button, {
4731          constructor: function() {
4732            button.apply(this, arguments);
4733            this.controlText(Drupal.t('Playlist'));
4734            this.addClass('vjs-icon-chapters');
4735          },
4736          handleClick: function() {
4737            // this.player().dispose();
4738            var playlist = $('#vjs-playlist');
4739            if (playlist.is(':visible')) {
4740              playlist.slideUp();
4741            } else {
4742              playlist.slideDown();        }
4743          }
4744        });
4745        videojs.registerComponent('playlistButton', playlistButton);
4746        vjsPlayer.getChild('controlBar').addChild('playlistButton', {});
4747      }
4748
4749      var playlistButton = vjsPlayer.controlBar.getChild('playlistButton');
4750
4751      // змінюємо порядок кнопок тільки якщо playlistButton існує
4752      if (playlistButton) {
4753        var fullscreenToggle = vjsPlayer.controlBar.getChild('FullscreenToggle');
4754        var fullscreenToggleEl = fullscreenToggle.el();
4755
4756        fullscreenToggleEl.parentNode.insertBefore(playlistButton.el(), fullscreenToggleEl);
4757      }
4758
4759    }
4760
4761
4762    //vjsPlayer.controlBar.removeChild(vjsPlayer.controlBar.getChild('PictureInPictureToggle'));
4763
4764
4765    var currentSong = {};
4766    var youtubes = [];
4767    var index = 0;
4768    $('.video-icon-player, #lyrics-settings-video').each(function () {
4769      var src = $(this).attr('youtube');
4770
4771      if (youtubes.indexOf(src) == -1) {
4772        youtubes.push(src);
4773
4774        var lyricsNid = $(this).attr('nid');
4775        if (src === youtube) {
4776          currentSong.name = $(this).attr("songtitle") || $(this).parent().find('a').text();
4777          //currentSong.poster = '/files/styles/square_thumbnail/public/Odyn-v-kanoe-foto-1_0.jpg';
4778          currentSong.sources = [{src: 'https://www.youtube.com/watch?v=' + src, type: 'video/youtube'}];
4779          currentSong.lyricsNid = lyricsNid;
4780          currentSong.index = index;
4781          //return;
4782        }
4783        var song = {};
4784        song.name = $(this).attr("songtitle") || $(this).parent().find('a').text();
4785        //song.poster = $(this).attr('img');
4786        song.sources = [{src: 'https://www.youtube.com/watch?v=' + src, type: 'video/youtube'}];
4787        song.lyricsNid = lyricsNid;
4788        song.index = index;
4789        vjsSongs.push(song);
4790
4791        index++;
4792      }
4793
4794    });
4795
4796
4797    //vjsSongs.unshift(currentSong);
4798
4799    if ((vjsSongs.length > 1) && (deviceType() != 'mobile')) {
4800      vjsPlayer.playlist(vjsSongs);
4801      vjsPlayer.playlistUi({horizontal: true});
4802      vjsPlayer.playlist.autoadvance(0);
4803
4804      vjsPlayer.playlist.currentItem(currentSong.index);
4805    }
4806
4807  }).catch(error => {
4808      console.error("Error:", error);
4809  });
4810}
4811
4812$(document).ready(function () {
4813
4814  $(document).on("click", "#artist-settings-video", function (e) {
4815
4816    // If currently playing (button has isactive class), pause the video
4817    if ($(this).hasClass("isactive")) {
4818      // Find and click the currently active video button to pause it
4819      if ($(".video-icon-player.isactive").length) {
4820        $(".video-icon-player.isactive:first").trigger( "click" );
4821      }
4822      // Update our button state immediately
4823      $(this).removeClass("isactive");
4824      setVideoIconState($(this), false);
4825    }
4826    else {
4827      // If not playing, start playing the first video
4828      if ($(".video-icon-player.isactive").length) {
4829        $(".video-icon-player.isactive:first").trigger( "click" );
4830      }
4831      else {
4832        $(".video-icon-player:first").trigger( "click" );
4833      }
4834      // Update our button state immediately
4835      $(this).addClass("isactive");
4836      setVideoIconState($(this), true);
4837    }
4838  });
4839
4840  // click statistics
4841
4842  $(document).on("click", ".video-icon-player.active-player", function (e) {
4843    if (typeof gtag !== 'undefined') gtag('event', 'video_icon_play', {
4844      'video_category': 'sticky-youtube-icon'
4845    });
4846  });
4847
4848
4849  // play sticky video
4850  $(document).on("click", "#lyrics-settings-video, .video-icon-player.active-player", function (e) {
4851
4852    if (typeof vjsPlayer !== 'undefined') {
4853      var $newsource = 'https://www.youtube.com/watch?v=' + $(this).attr('youtube');
4854      if (vjsPlayer.currentSrc() === $newsource) {
4855        if (!vjsPlayer.paused()) {
4856          vjsPlayer.pause();
4857          setVideoIconState($(this), false);
4858          $("#artist-settings-video").removeClass("isactive");
4859          setVideoIconState($("#artist-settings-video"), false);
4860        }
4861        else {
4862          vjsPlayer.play();
4863          setVideoIconState($(this), true);
4864          $("#artist-settings-video").addClass("isactive");
4865          setVideoIconState($("#artist-settings-video"), true);
4866        }
4867      }
4868      else {
4869        $("#lyrics-settings-video, .video-icon-player.active-player").removeClass("isactive");
4870        $("#artist-settings-video").removeClass("isactive");
4871        setVideoIconState($("#artist-settings-video"), false);
4872        $(this).addClass("isactive");
4873        vjsPlayer.src({
4874          "type": "video/youtube",
4875          "src": $newsource
4876        });
4877        vjsPlayer.play();
4878        setVideoIconState($(this), true);
4879        $("#artist-settings-video").addClass("isactive");
4880        setVideoIconState($("#artist-settings-video"), true);
4881      }
4882      $('#vjs-player-wrapper').show();
4883    }
4884    else {
4885      ispinner($(this));
4886      $(this).addClass("isactive");
4887      stickyVideoPlayer($(this));
4888    }
4889  });
4890
4891  $(document).on("click", ".vjs-close", function (e) {
4892    e.preventDefault();
4893
4894		onClose();
4895
4896    return false;
4897  });
4898});
4899
4900
4901function throttle(func, limit) {
4902    let lastFunc;
4903    let lastRan;
4904    return function() {
4905        const context = this;
4906        const args = arguments;
4907        if (!lastRan) {
4908            func.apply(context, args);
4909            lastRan = Date.now();
4910        } else {
4911            clearTimeout(lastFunc);
4912            lastFunc = setTimeout(function() {
4913                if ((Date.now() - lastRan) >= limit) {
4914                    func.apply(context, args);
4915                    lastRan = Date.now();
4916                }
4917            }, limit - (Date.now() - lastRan));
4918        }
4919    }
4920}
4921
4922let currentHighlighted = null;
4923let currentHighlighted2 = null;
4924
4925
4926function timeUpdateHandler(cues) {
4927
4928      requestAnimationFrame(() => {
4929        const currentTime = this.currentTime() + LYRICS_TIME_OFFSET;
4930        let currentCue = cues.find(cue => currentTime >= cue.start && currentTime <= cue.end);
4931
4932        if (currentCue) {
4933            const newHighlightedElement = document.getElementById('l-' + currentCue.id);
4934            const newHighlightedElement2 = document.getElementById('t-' + currentCue.id);
4935
4936            if (newHighlightedElement !== currentHighlighted) {
4937                if (currentHighlighted) {
4938                    currentHighlighted.classList.remove('bgh');
4939                }
4940                if (currentHighlighted2) {
4941                    currentHighlighted2.classList.remove('bgh');
4942                }
4943
4944                if (newHighlightedElement) {
4945                  newHighlightedElement.classList.add('bgh');
4946                  currentHighlighted = newHighlightedElement;
4947                }
4948                if (newHighlightedElement2) {
4949                  newHighlightedElement2.classList.add('bgh');
4950                  currentHighlighted2 = newHighlightedElement2;
4951                }
4952
4953            }
4954        } else {
4955          if (currentHighlighted) {
4956            currentHighlighted.classList.remove('bgh');
4957            currentHighlighted = null;
4958          }
4959          if (currentHighlighted2) {
4960            currentHighlighted2.classList.remove('bgh');
4961            currentHighlighted2 = null;
4962          }
4963        }
4964    });
4965}
4966
4967// Optimized: Use CSS classes instead of HTML replacement
4968function setVideoIconState(element, isPlaying) {
4969  var videoIcon = element.find('.video-icon');
4970  if (isPlaying) {
4971    videoIcon.addClass('playing');
4972  } else {
4973    videoIcon.removeClass('playing');
4974  }
4975}
4976
4977// Legacy functions for backward compatibility - now return CSS-based icons
4978function svgPlayIcon() {
4979  return '<div class="video-icon" style="width: 24px; height: 24px;"></div>';
4980}
4981
4982function svgPauseIcon() {
4983  return '<div class="video-icon playing" style="width: 24px; height: 24px;"></div>';
4984}
4985
4986// Legacy function - now returns CSS-based icon
4987function svgDefaultIcon(cc) {
4988  return svgPlayIcon();
4989}
4990
4991function svgSubtitlesIcon() {
4992  return '<div class="video-icon subtitles-icon" style="width: 24px; height: 24px;"></div>';
4993}
4994;/*})'"*/;/*})'"*/
4995function formatState (state) {
4996  if (!state.id) {
4997    return state.text;
4998  }
4999  var baseUrl = "/user/pages/images/flags";
5000  var $state = $(
5001    '<span><img src="' + baseUrl + '/' + state.element.value.toLowerCase() + '.png" class="img-flag" /> ' + state.text + '</span>'
5002  );
5003  return $state;
5004};
5005
5006
5007function fitFooterSelectToContent() {
5008  var $wrap = $('.footer__select');
5009  if (!$wrap.length) return;
5010  var $sel = $wrap.find('select');
5011  if (!$sel.length) return;
5012
5013  var text = $sel.find('option:selected').text();
5014  if (!text) return;
5015
5016  var cs = window.getComputedStyle($sel[0]);
5017  var $temp = $('<span>').text(text).css({
5018    visibility: 'hidden',
5019    position: 'absolute',
5020    whiteSpace: 'nowrap',
5021    fontFamily: cs.fontFamily,
5022    fontSize: cs.fontSize,
5023    fontWeight: cs.fontWeight,
5024    letterSpacing: cs.letterSpacing
5025  }).appendTo('body');
5026  var textWidth = $temp[0].getBoundingClientRect().width;
5027  $temp.remove();
5028
5029  var padL = parseFloat(cs.paddingLeft) || 0;
5030  var padR = parseFloat(cs.paddingRight) || 0;
5031  var border = (parseFloat(cs.borderLeftWidth) || 0) + (parseFloat(cs.borderRightWidth) || 0);
5032  var needed = Math.ceil(textWidth + padL + padR + border);
5033  var current = parseFloat(window.getComputedStyle($wrap[0]).width);
5034
5035  if (needed > current) {
5036    $wrap[0].style.width = needed + 'px';
5037  }
5038}
5039
5040$(document).ready(function () {
5041  fitFooterSelectToContent();
5042  var resizeT;
5043  $(window).on('resize', function () {
5044    clearTimeout(resizeT);
5045    resizeT = setTimeout(fitFooterSelectToContent, 150);
5046  });
5047
5048  // Interface language select / footer
5049  $('#lang-chooser-select').bind('change', function () {
5050    var url = $(this).val();
5051    var lang = $('option:selected', this).attr('ltlang');
5052    var languagevarq = Drupal.settings.languagevarq;
5053
5054    // Show loading spinner on the lang icon
5055    $(this).closest('.footer__select').find('.icon:not(.-arrow) img')
5056      .attr('src', '/sites/all/themes/lt7/img/icons/spinner-small.gif');
5057
5058    // ga('send', 'event', 'Interface language', 'footer', languagevarq + ' -> ' + lang);
5059    gtag('event', 'interface_language_switch', {
5060      'interface_language_switch_location': 'footer'
5061    });
5062
5063    language_save_preferences(lang);
5064
5065    if (url) {
5066      window.location = url;
5067    }
5068    return false;
5069  });
5070
5071  // Interface language select / header
5072  $('#lang-chooser-select-header').bind('change', function () {
5073    var url = $(this).val();
5074    var lang = $('option:selected', this).attr('ltlang');
5075    var languagevarq = Drupal.settings.languagevarq;
5076
5077    // ga('send', 'event', 'Interface language', 'footer', languagevarq + ' -> ' + lang);
5078    gtag('event', 'interface_language_switch', {
5079      'interface_language_switch_location': 'header'
5080    });
5081
5082    language_save_preferences(lang);
5083
5084    if (url) {
5085      window.location = url;
5086    }
5087    return false;
5088  });
5089
5090  // Interface language select / panel
5091
5092  $(document).on('change', '#lang-chooser-select-panel, #lang-chooser-select-usermenu', function () {
5093    var url = $(this).val();
5094    var lang = $('option:selected', this).attr('ltlang');
5095    var languagevarq = Drupal.settings.languagevarq;
5096
5097    // ga('send', 'event', 'Interface language', 'footer', languagevarq + ' -> ' + lang);
5098    gtag('event', 'interface_language_switch', {
5099      'interface_language_switch_location': 'left-panel'
5100    });
5101
5102    language_save_preferences(lang);
5103
5104    if (url) {
5105      window.location = url;
5106    }
5107    return false;
5108  });
5109
5110
5111  $('.block-switchtheme .form-item-theme select').change(function() {
5112        this.form.submit();
5113    });
5114
5115  $('#selectedlanguage').on('click', function (e) {
5116    e.preventDefault();
5117    $('div#selectlanguages ul').addClass('selectedlanguageopen');
5118    $('div#selectlanguages ul li').show();
5119  });
5120
5121});
5122
5123function language_save_preferences(lang) {
5124
5125  languagevarq = Drupal.settings.languagevarq;
5126
5127  $.ajax({
5128    type: "POST",
5129    dataType: 'json',
5130    async: false,
5131    cache: false,
5132    url: '/' + languagevarq + '/callback/change_language',
5133    data: {
5134      lang : lang,
5135    },
5136  });
5137
5138}
5139;/*})'"*/;/*})'"*/
5140/**
5141* hoverIntent r6 // 2011.02.26 // jQuery 1.5.1+
5142* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
5143* 
5144* @param  f  onMouseOver function || An object with configuration options
5145* @param  g  onMouseOut function  || Nothing (use configuration options object)
5146* @author    Brian Cherne brian(at)cherne(dot)net
5147*/
5148(function($){$.fn.hoverIntent=function(f,g){var cfg={sensitivity:7,interval:100,timeout:0};cfg=$.extend(cfg,g?{over:f,out:g}:f);var cX,cY,pX,pY;var track=function(ev){cX=ev.pageX;cY=ev.pageY};var compare=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);if((Math.abs(pX-cX)+Math.abs(pY-cY))<cfg.sensitivity){$(ob).unbind("mousemove",track);ob.hoverIntent_s=1;return cfg.over.apply(ob,[ev])}else{pX=cX;pY=cY;ob.hoverIntent_t=setTimeout(function(){compare(ev,ob)},cfg.interval)}};var delay=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);ob.hoverIntent_s=0;return cfg.out.apply(ob,[ev])};var handleHover=function(e){var ev=jQuery.extend({},e);var ob=this;if(ob.hoverIntent_t){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t)}if(e.type=="mouseenter"){pX=ev.pageX;pY=ev.pageY;$(ob).bind("mousemove",track);if(ob.hoverIntent_s!=1){ob.hoverIntent_t=setTimeout(function(){compare(ev,ob)},cfg.interval)}}else{$(ob).unbind("mousemove",track);if(ob.hoverIntent_s==1){ob.hoverIntent_t=setTimeout(function(){delay(ev,ob)},cfg.timeout)}}};return this.bind('mouseenter',handleHover).bind('mouseleave',handleHover)}})(jQuery);
5149;/*})'"*/;/*})'"*/

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.