PageSourceSearch

https://www.italiarail.com/themes/custom/italiarail-v2/js/italiarail.js?v=1

js italiarail.com collected 2026-10-02 04:08:36 UTC 46,662 bytes, 1,306 lines download raw bytes

1(function(f){f.fn.jqm=function(n){var p=f.extend({},f.jqm.params,n);return this.each(function(){var q=f(this),o=f(this).data("jqm");if(!o){o={ID:j++}}q.data("jqm",f.extend(p,o)).addClass("jqm-init");p.trigger&&f(this).jqmAddTrigger(p.trigger)})};f.fn.jqmAddTrigger=function(n){return this.each(function(){if(!a(f(this),"jqmShow",n)){d("jqmAddTrigger must be called on initialized modals")}})};f.fn.jqmAddClose=function(n){return this.each(function(){if(!a(f(this),"jqmHide",n)){d("jqmAddClose must be called on initialized modals")}})};f.fn.jqmShow=function(n){return this.each(function(){!this._jqmShown&&k(f(this),n)})};f.fn.jqmHide=function(n){return this.each(function(){this._jqmShown&&h(f(this),n)})};var d=function(n){if(window.console&&window.console.error){window.console.error(n)}},k=function(u,q){var x=u.data("jqm"),q=q||window.event,w=(parseInt(u.css("z-index"))),w=(w>0)?w:3000,n=f("<div></div>").addClass(x.overlayClass).css({height:"100%",width:"100%",position:"fixed",left:0,top:0,"z-index":w-1,opacity:x.overlay/100}),r={w:u,c:x,o:n,t:q};u.css("z-index",w);if(x.ajax){var s=x.target||u,p=x.ajax;s=(typeof s=="string")?f(s,u):f(s);if(p.substr(0,1)=="@"){p=f(q).attr(p.substring(1))}s.html(x.ajaxText).load(p,function(){x.onLoad&&x.onLoad.call(this,r);g(r)})}else{g(r)}},h=function(q,n){var r=q.data("jqm"),n=n||window.event,p={w:q,c:r,o:q.data("jqmv"),t:n};m(p)},i=function(n){if(n.c.overlay>0){n.o.prependTo("body")}n.w.show();f.jqm.focusFunc(n.w);return true},e=function(n){n.w.hide()&&n.o&&n.o.remove();return true},a=function(p,o,n){if(!p.data("jqm")){return false}return f(n).each(function(){this[o]=this[o]||[];this[o].push(p)}).click(function(){var q=this;f.each(this[o],function(r,s){s[o](q)});return false})},g=function(p){var q=p.w,n=p.o,r=p.c;if(r.onShow(p)!==false){q[0]._jqmShown=true;if(r.modal){!b[0]&&l("bind");b.push(q)}else{q.jqmAddClose(n)}r.closeClass&&q.jqmAddClose(f("."+r.closeClass,q));r.toTop&&n&&q.before('<span id="jqmP'+r.ID+'"></span>').insertAfter(n);q.data("jqmv",n)}},m=function(p){var q=p.w,n=p.o,r=p.c;if(r.onHide(p)!==false){q[0]._jqmShown=false;if(r.modal){b.pop();!b[0]&&l("unbind")}r.toTop&&n&&f("#jqmP"+r.ID).after(q).remove()}},l=function(n){f(document)[n]("keypress keydown mousedown",c)},c=function(p){var o=f(p.target).data("jqm")||f(p.target).parents(".jqm-init:first").data("jqm"),n=b[b.length-1].data("jqm");if(o&&o.ID==n.ID){return true}return f.jqm.focusFunc(n)},j=0,b=[];f.jqm={params:{overlay:50,overlayClass:"jqmOverlay",closeClass:"jqmClose",trigger:".jqModal",ajax:false,target:false,ajaxText:"",modal:false,toTop:false,onShow:i,onHide:e,onLoad:false},focusFunc:function(n){f(":input:visible:first",n).focus();return false}}})(jQuery);
2
3
4$(function () {
5
6  // Check Source
7  checkAffSource();
8
9  $('.banner_itinerary_section_right .itinerary_text').attr("href", getItineraryPage());
10  $('li.searchstatus a').attr("href", getItineraryPage());
11  // Change cart status and add tickets.
12  if (isRerail()) {
13    checkRerailCartStatus();
14  } else {
15    checkCartStatus();
16  }
17
18  setTimeout(function(){
19    $('.ui-dialog.webform-confirmation-modal .ui-dialog-content').html( getThankYouMessageDesktop() );
20    $('.ui-dialog.webform-confirmation-modal').css('width', '722px');
21  }, 10);
22
23  if (!docCookies.hasItem('ioc')) {
24      $('.jsIOC').slideDown();
25      $('.jsIOCFooterPadding').slideDown();
26  }
27
28  $('.jsCloseIOC').on('click', function () {
29      $('.jsIOC').slideUp();
30      $('.jsIOCFooterPadding').slideUp();
31      docCookies.setItem('ioc', 'viewed', Infinity);
32  });
33});
34
35$(document).ready(function() {
36
37    var pathname = window.location.pathname;
38    if (pathname != '/') {
39        setTimeout(function() {
40            if (!docCookies.hasItem('italiapass_user')) {
41                // Cookie is not set. Let's open the modal.
42
43                if ($('article.popular-routes-new.full').length > 0) {
44                    $('#italia-pass-search-popup .cityFrom').replaceWith('<span class="green cityFrom">' + $('article.popular-routes-new.full').attr('data-departure-station-name') + '</span>');
45                    $('#italia-pass-search-popup .cityTo').replaceWith('<span class="green cityTo">' + $('article.popular-routes-new.full').attr('data-destination-station-name') + '</span>');
46                    $('#italia-pass-search-popup').dialog('open');
47                }
48                else {
49                    $('#italia-pass-search-popup .cities').replaceWith('<span class="cities">Italy</span>');
50                    $('#italia-pass-search-popup').dialog('open');
51                }
52            }
53            }, 2000);
54    }
55    var firstStationId = drupalSettings.italiarail.articleCity;
56
57    if (firstStationId) {
58        $('#faresearch_route-destination').val(firstStationId);
59    }
60});
61
62
63function getThankYouMessageDesktop() {
64
65    return '<div class="ir-thank-you-modal">'
66    + '<div class="image"></div>'
67    + '<div class="text">'
68    + '<div class="message-title">Thank You</div>'
69    + '<div class="message">We received your request. A customer service agent will be contacting you shortly.</div>'
70    + '<a href="/" class="btn btn-primary back-home">Return to Homepage</a>'
71    + '</div>'
72    + '</div>';
73}
74
75function searchLower(){
76    document.sitesearch.keys.value = document.sitesearch.keys.value.toLowerCase();
77    return true;
78}
79
80/**
81* Aff_Source cookie create before any call to portal
82*/
83function checkAffSource(){
84  if (utm_source = getQueryString('utm_source')) {
85    let source = utm_source;
86    let medium = getQueryString('utm_medium');
87    docCookies.setItem('aff_source', source+'-'+medium,'','/','.italiarail.com');
88  }
89}
90
91
92// Check and update cart status on the current app Environment.
93function checkCartStatus() {
94    var app_env = '';
95    if (drupalSettings.irenv) {
96        if (drupalSettings.irenv == 'stage') {
97            app_env = '-stage';
98        }
99        if (drupalSettings.irenv == 'dev') {
100            app_env = '-dev';
101        }
102    }
103    var app_url = 'https://app' + app_env + '.italiarail.com';
104
105    $.ajax({
106        url: app_url + '/api/itinerary?aff='+docCookies.getItem('aff'),
107        type: 'GET',
108        xhrFields: {
109            withCredentials: true
110        },
111        success: function (response) {
112            var itinerary = response.itinerary.length;
113            var products = response.products.length;
114            var tickets = itinerary+products;
115            updateCartIcon(tickets);
116        }
117    });
118}
119
120function updateCartIcon(count) {
121  if (count) {
122    console.log(count);
123    $('.banner_itinerary_section_right.searchstatus').addClass('withTickets');
124    $('.banner_itinerary_section_right .itinerary_text').text(Drupal.t('Cart') + ' (' + count + ')');
125    $('.searchstatus a').text('Cart (' + count + ')');
126  } else {
127    $('.banner_itinerary_section_right.searchstatus').removeClass('withTickets');
128    $('.banner_itinerary_section_right .itinerary_text').text(Drupal.t('Cart'));
129    $('li.searchstatus a').text('View Cart');
130  }
131}
132
133$(function () {
134    $('.breadcrumb').addClass('container');
135});
136
137
138function setTrip(from, fromId, to, toId, show_date) {
139    if(show_date === undefined)
140        show_date = true;
141    $("#faresearch_route-origin").val(from);
142    $("#faresearch_route-origin_id").val(fromId);
143
144    $("#faresearch_route-destination").val(to);
145    $("#faresearch_route-destination_id").val(toId);
146
147    // then show the calendar
148    if(show_date){
149        $('#faresearch_route-departure_date').datepicker('show');
150    }
151}
152
153function switchOriginDestination() {
154    var from = $("#faresearch_route-origin").val();
155    var fromId = $("#faresearch_route-origin_id").val();
156    var to = $("#faresearch_route-destination").val();
157    var toId = $("#faresearch_route-destination_id").val();
158
159    $("#faresearch_route-origin").val(to);
160    $("#faresearch_route-origin_id").val(toId);
161
162    $("#faresearch_route-destination").val(from);
163    $("#faresearch_route-destination_id").val(fromId);
164}
165
166var currencySymbol = {
167    'TRE': 'US$', 'ITC': 'C$', 'ITE': '€', 'ITR': '£', 'TRA': 'A$'
168};
169
170var affCurrency = {
171  TRE: 'USD',
172  ITC: 'CAD',
173  ITR: 'GBP',
174  ITE: 'EUR',
175  TRA: 'AUD'
176};
177
178var cityToCity = {
179    'rome_to_florence_price': {'TRE': '35', 'ITC': '28', 'ITE': '19', 'ITR': '16', 'TRA': '28'},
180    'rome_to_venice_price': {'TRE': '18', 'ITC': '43', 'ITE': '29', 'ITR': '25', 'TRA': '43'},
181    'rome_to_naples_price': {'TRE': '26', 'ITC': '28', 'ITE': '19', 'ITR': '16', 'TRA': '28'},
182    'rome_to_milan_price': {'TRE': '40', 'ITC': '43', 'ITE': '29', 'ITR': '25', 'TRA': '43'},
183    'florence_to_rome_price': {'TRE': '26', 'ITC': '28', 'ITE': '19', 'ITR': '16', 'TRA': '28'},
184    'florence_to_venice_price': {'TRE': '26', 'ITC': '28', 'ITE': '19', 'ITR': '16', 'TRA': '28'},
185    'florence_to_milan_price': {'TRE': '26', 'ITC': '28', 'ITE': '19', 'ITR': '16', 'TRA': '28'},
186    'florence_to_pisa_price': {'TRE': '11', 'ITC': '12', 'ITE': '8', 'ITR': '7', 'TRA': '12'},
187    'venice_to_rome_price': {'TRE': '11', 'ITC': '43', 'ITE': '29', 'ITR': '25', 'TRA': '43'},
188    'venice_to_florence_price': {'TRE': '26', 'ITC': '28', 'ITE': '19', 'ITR': '16', 'TRA': '28'},
189    'venice_to_milan_price': {'TRE': '13', 'ITC': '14', 'ITE': '9', 'ITR': '14', 'TRA': '14'},
190    'venice_to_pisa_price': {'TRE': '37', 'ITC': '40', 'ITE': '27', 'ITR': '40', 'TRA': '40'},
191    'milan_to_venice_price': {'TRE': '13', 'ITC': '14', 'ITE': '9', 'ITR': '14', 'TRA': '14'},
192    'milan_to_rome_price': {'TRE': '40', 'ITC': '43', 'ITE': '29', 'ITR': '43', 'TRA': '43'},
193    'milan_to_florence_price': {'TRE': '26', 'ITC': '28', 'ITE': '19', 'ITR': '28', 'TRA': '28'},
194    'milan_to_genoa_price': {'TRE': '13', 'ITC': '14', 'ITE': '9', 'ITR': '14', 'TRA': '14'}
195};
196
197var affiliateMapping = {
198    'S6': {'TRE': 'S61', 'ITC': '', 'ITE': 'S61U', 'ITR': 'S61P', 'TRA': ''},
199    'IP': {'TRE': 'IPTRE', 'ITC': 'IPITC', 'ITE': 'IPITE', 'ITR': 'IPITR', 'TRA': 'IPTRA'},
200    'EM': {'TRE': '', 'ITC': '', 'ITE': 'EMI', 'ITR': '', 'TRA': ''},
201    'FA': {'TRE': 'FAN', 'ITC': '', 'ITE': '', 'ITR': '', 'TRA': ''},
202    'BN': {'TRE': 'BNUS', 'ITC': '', 'ITE': '', 'ITR': '', 'TRA': ''}
203};
204
205function getPrices(country, cities) {
206    "use strict";
207
208    if (currencySymbol.hasOwnProperty(country) && cityToCity.hasOwnProperty(cities)) {
209        return currencySymbol[country] + cityToCity[cities][country];
210    } else {
211        return '--';
212    }
213}
214
215function getPassengersTotal() {
216    var tmp_cnt = parseInt($('#faresearch_passengers-child').val()) +
217        parseInt($('#faresearch_passengers-adult').val());
218    return tmp_cnt;
219}
220
221function getTravelersTitle($passengers) {
222    if ($passengers == 1) return  Drupal.t('Traveler');
223
224    else return  Drupal.t('Travelers');
225}
226
227function openGroupsModal() {
228    if (!$('#groupsmodal').hasClass('popped')) {
229        $('#groupsmodal').addClass('popped');
230        $('.ir-bookingwidget').addClass('groups-modal-popped');
231        popup('groupsmodal',!blanket());
232    }
233}
234
235
236var COMPANY_FOR_AFFILIATE = {
237    'TRE': 'International Rail, LLC',
238    'ITC': 'ItaliaRail.ca Travel Services LTD',
239    'ITR': 'ItaliaRail Limited',
240    'ITE': 'ItaliaRail Limited',
241    'TRA': 'International Rail, LLC'
242};
243function setCompany(affiliate) {
244    if (affiliate === undefined) {
245        return;
246    }
247    var companyElem = $('#footer').find('div.footer_copy span.company');
248    var name = COMPANY_FOR_AFFILIATE[affiliate];
249    if (name === undefined) {
250        return;
251    }
252    companyElem.text(name);
253}
254function setPassholderPref() {
255    if (docCookies.hasItem('include_passholder') && docCookies.getItem('include_passholder') 
255=== 'true') {
256        $('#faresearch_passholder').click();
257        $('#passholder_option').removeClass('hidden');
258        $('#passholder_toggle').remove();
259    }
260}
261function saveAffiliateInfo() {
262    var curr = getParameterByName('aff');
263    var affiliate_id;
264    // TODO: If we get a new affiliate we get the associated currency and the affiliate code.
265
266    // If we are coming from an affiliate we persist it for future link we also set the currency for this
267    // Affiliate. We are from an affiliate when not from a new currency
268    if ((curr) &&
269     (curr !== "") &&
270     (curr !== "TRE") &&
271     (curr !== "ITC") &&
272     (curr !== "ITR") &&
273     (curr !== "ITE") &&
274     (curr !== "TRA")  ) {
275        // We set it for one month
276        var mytime = new Date();
277        mytime.setTime(mytime.getTime()+(30*24*60*60*1000));
278        docCookies.setItem('affid', curr, mytime.toUTCString(),"/","italiarail.com");
279    }
280}
281
282function getAffiliate() {
283    // Retrieve currency from our cookies, if they come from elsewhere the cookie was already set b
284    var currency_aff;
285    curr = docCookies.getItem('aff');
286    affiliate = docCookies.getItem('affid');
287
288    if (affiliate) {
289        aff = affiliateMapping[affiliate.substring(0, 2)];
290        if (aff !== undefined) {
291            // We have this listed in our possible affiliates, let's get the corresponding value.
292            currency_aff = aff[curr];
293        }
294    }
295
296    // No match revert to currency mapping for aff
297    if ((aff === undefined) || (currency_aff === "")) {
298        return curr;
299    }
300
301    // Return affiliate ID mapped to the currency
302    return currency_aff;
303}
304
305function setCurrencySelectionFromPullDown() {
306    var curr = $("#currency_select").val();
307    if (isValidAffiliate(curr)) {
308        $("#currency_select").val(curr);
309    }
310    var affiliate = $("#currency_select").val();
311
312    setCompany(affiliate);
313    docCookies.setItem('aff', affiliate);
314}
315
316function isValidAffiliate(affCode) {
317    return contains(['TRE', 'ITC', 'ITE', 'ITR', 'TRA'], affCode);
318}
319
320function isRerailDate(depart) {
321
322    if (!depart) {
323        depart = document.getElementById('faresearch_route-departure_date').value;
324    }
325    // Build ISO using LOCAL date parts (no UTC conversion)
326    const d = new Date();
327    if (!depart) {
328        depart = (d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'));
329        document.getElementById('faresearch_route-departure_date').value = depart;
330    }
331    else {
332        console.log(depart);
333        const s = String(depart).trim();
334
335        const monthMap = {
336            'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06',
337            'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'
338        };
339
340        let year, month, day;
341        let m;
342
343        // Format A: "Mon, Mar 03"
344        m = s.match(/^[A-Za-z]{3},\s*([A-Za-z]{3})\s+(\d{2})$/);
345        if (m) {
346            month = monthMap[m[1]];
347            console.log(month);
348            day = parseInt(m[2], 10);
349
350            // Choose year intelligently (travel-safe)
351            const today = new Date();
352            year = today.getFullYear();
353            console.log(year);
354        }
355
356        // Format B: "9 Feb 2026" or "09 Feb 2026"
357        else if (!m) {
358            m = s.match(/^(\d{1,2})\s+([A-Za-z]{3})\s+(\d{4})$/);
359            if (m) {
360                day = parseInt(m[0], 10);
361                month = monthMap[m[1]];
362                year = parseInt(m[2], 10);
363            }
364        }
365        if (month == null || !Number.isFinite(day) || !Number.isFinite(year)) {
366            depart = d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
367        }
368        else {
369            depart = String(year) + '-' + String(month) + '-' + String(day);
370        }
371
372        depart = String(year) + '-' + String(month) + '-' + String(day);
373        console.log(depart);
374    }
375
376    var departure_date = depart;
377
378    return departure_date;
379}
380
381function initializeUI() {
382
383    //The page was accessed by navigating into the history.
384    //We reload to be sure form status is correct
385    if (performance.navigation.type == 2){
386        if( $("input[value='round_trip']").is(':checked')) {
387          selectRoundTrip()
388        }
389        else {
390         $("input[value='one_way']").prop('checked', true);
391        }
392    }
393    //  The page was accessed by clicking the Reload button (1)
394    if ( (performance.navigation.type == 1) ){
395        //Uncomment when CMS-866 ends
396        if( $("input[value='round_trip']").is(':checked')) {
397          selectRoundTrip();
398        }
399    }
400
401
402    $('#waiting').dialog({
403        autoOpen: false,
404        width: 350,
405        draggable: false,
406        resizable: false,
407        dialogClass: "waiting"
408    });
409
410    /*** Booking widget submit ***/
411    $('#faresearch #submitButton, #faresearch2 #submitButton, #faresearch2 #submit').click(function () {
412        docCookies.setItem('include_passholder', '' + $('#faresearch_passholder').prop('checked'))
413        var form = $(this).form();
414        var has_empty = false;
415        var org_value = false;
416
417
418        // GET STATION NAMES
419        var STATION_NAMES = [];
420        $.ajax({
421            dataType: "json",
422            async: false,
423            url: "/stations.txt",
424            success: function(jsondata){
425                STATION_NAMES = jsondata;
426            },
427            error: function(){
428                /* TBD */
429            }
430        });
431
432        // To avoid issue with IOS out of focus issue, at least blank when not resolved.
433        if ($("#faresearch_route-origin").val() !== '') {
434            var origin_check = $("#faresearch_route-origin").val().toLowerCase().trim();
435            $.each(STATION_NAMES, function(index, value) {
436                if (value.label.toLowerCase() === origin_check) {
437                    $("#faresearch_route-origin").val(value.label);
438                    $("#faresearch_route-origin_id").val(value.value);
439                    org_value = true;
440                }
441            });
442        }
443        if (org_value === false) {
444            $("#faresearch_route-origin_id").val('');
445        }
446        if ($("#faresearch_route-origin_id").val() === '') {
447            $("#faresearch_route-origin").val('');
448        }
449
450        var des_value = false;
451
452        if ($("#faresearch_route-destination").val() !== '') {
453            var dest_check = $("#faresearch_route-destination").val().toLowerCase().trim();
454            $.each(STATION_NAMES, function(index, value) {
455                if (value.label.toLowerCase() === dest_check) {
456                    $("#faresearch_route-destination").val(value.label);
457                    $("#faresearch_route-destination_id").val(value.value);
458                    des_value = true;
459                }
460            });
461        }
462        if (des_value === false) {
463            $("#faresearch_route-destination_id").val('');
464        }
465        if ($("#faresearch_route-destination_id").val() === '') {
466            $("#faresearch_route-destination").val('');
467        }
468
469
470        var return_date = Date.parse($('#faresearch_route-return_date').val());
471
472
473        $(form).find(':input').each(function(idx, item) {
474            if ((item.type === 'text') && (item.value === '') && item.style.display != 'none' )
475            {
476            	$(item).css('border', "solid 1px red");
477                has_empty = true;
478            }
479            else
480            {
481                $(item).css('border', "solid 1px lightgrey");
482            }
483
484        });
485
486        var num_passengers = $('#num_passengers').html();
487        if (parseInt(num_passengers) < 1) {
488        	has_empty = true;
489        	$('.passengers_toggle_slide a').css('border', "solid 1px red");
490        }
491
492        if (has_empty) {
493            return false;
494        }
495
496        if ($('#faresearch :input[name="aff"]').val() == 'DISABLED') {
497            $('#faresearch').attr("action", "https://app2.italiarail.com")
498        }
499
500        // Disable submit button to avoid several click.
501        form.find('.submit').attr('disabled', true).addClass('disabled');
502        if (!docCookies.hasItem('italiapass_user')) {
503            // Cookie is not set. Let's open the modal.
504            // Submitting of closing the modal will submit the search form.
505            if( $('#italia-pass-search-popup').length && searchToTrack() ) {
506                $('#italia-pass-search-popup .cityFrom').replaceWith('<span class="green cityFrom">' + form.find('#faresearch_route-origin').val() + '</span>');
507                $('#italia-pass-search-popup .cityTo').replaceWith('<span class="green cityTo">' + form.find('#faresearch_route-destination').val() + '</span>');
508                $('#italia-pass-search-popup').dialog('open');
509            }
510            else {
511                if (isRerail()) {
512                  isRerailSubmit(form);
513                  return false;
514                }
515                form.submit();
516            }
517        }
518        else {
519            //we should track search here.
520            $('#waiting').dialog('open');
521            if (searchToTrack()) {
522                trackUserSearch();
523            }
524            else {
525              if (isRerail()) {
526                isRerailSubmit(form);
527                return false;
528              }
529              form.submit();
530            }
531        }
532    });
533    /*** Booking widget submit  - END ***/
534
535    $('#rountrip-confirm-modal').on('click', '.btn-primary', function() {
536      var form = $('#faresearch');
537      if (! form.is('form')) {
538        form = $('#faresearch2');
539      }
540      form.find('.submit').attr('disabled', true).addClass('disabled');
541      $('#waiting').dialog('open');
542      resetRerailCart(function() {
543        disableRerail();
544        form.submit();
545      });
546    });
547
548    $(".fn-cart-link").click(function (event) {
549        event.preventDefault();
550        var aff_query;
551        var current_currency = $("#currency_select").val();
552        // We need to make sure that query parameters are sent in the proper order by environment.
553        if ($(event.currentTarget).attr('href').indexOf('?') >= 0) {
554            aff_query = "&aff=" + current_currency;
555        }
556        else {
557            aff_query = "?aff=" + current_currency;
558        }
559        // Temp for transition
560        if (current_currency == 'DISABLED') {
561            window.location = "https://app2.italiarail.com" + aff_query;
562        }
563        else {
564            window.location = $(event.currentTarget).attr('href') + aff_query;
565        }
566    });
567
568
569    //###    TRAVELERS SELECTOR
570    var passengersAdultOpts = {
571        change: function (event, ui) {
572            $('#num_passengers').text(getPassengersTotal());
573            $('.passengers_toggle_slide .passenger_label_title').text(getTravelersTitle($('#num_passengers').text()));
574        },
575        spin: function (event, ui) {
576            $('#num_passengers').text(getPassengersTotal());
577            $('.passengers_toggle_slide .passenger_label_title').text(getTravelersTitle($('#num_passengers').text()));
578        },
579        start: function (event, ui) {
580            $('#num_passengers').text(getPassengersTotal());
581            $('.passengers_toggle_slide .passenger_label_title').text(getTravelersTitle($('#num_passengers').text()));
582        },
583        stop: function (event, ui) {
584            $('#num_passengers').text(getPassengersTotal());
585            $('.passengers_toggle_slide .passenger_label_title').text(getTravelersTitle($('#num_passengers').text()));
586        },
587        min: 1,
588        max: 9
589    };
590    $('#faresearch_passengers-adult').spinner(passengersAdultOpts);
591    if ($('#productsearch_passengers-adult').length > 0) {
592        $('#productsearch_passengers-adult').spinner();
593    }
594
595    var childPassengerOps = {
596        change: function (event, ui) {
597            $('#num_passengers').text(getPassengersTotal());
598            $('.passengers_toggle_slide .passenger_label_title').text(getTravelersTitle($('#num_passengers').text()));
599
600        },
601        spin: function (event, ui) {
602            $('#num_passengers').text(getPassengersTotal());
603            $('.passengers_toggle_slide .passenger_label_title').text(getTravelersTitle($('#num_passengers').text()));
604        },
605        start: function (event, ui) {
606            $('#num_passengers').text(getPassengersTotal());
607            $('.passengers_toggle_slide .passenger_label_title').text(getTravelersTitle($('#num_passengers').text()));
608        },
609        stop: function (event, ui) {
610            $('#num_passengers').text(getPassengersTotal());
611            $('.passengers_toggle_slide .passenger_label_title').text(getTravelersTitle($('#num_passengers').text()));
612        },
613        min: 0,
614        max: 9
615    };
616    $('#faresearch_passengers-child').spinner(childPassengerOps);
617    if ($('#productsearch_passengers-child').length > 0) {
618        $('#productsearch_passengers-child').spinner();
619    }
620    $(".passenger_toggle").click(function () {
621        var curr_arr = $("#date_icon_arrow").attr("src");
622        $("#date_icon_arrow").toggleClass("active");
623
624        if ( $("#date_icon_arrow").hasClass('active') ) {
625            $("#date_icon_arrow").attr("src", "/themes/custom/italiarail-v2/img/icon-up-arr.png");
626            $("#passengers_toggle_panel").show("fast");
627        } else {
628            $("#date_icon_arrow").attr("src", "/themes/custom/italiarail-v2/img/icon-down-arr.png");
629            $("#passengers_toggle_panel").hide("fast");
630        }
631
632        $(this).toggleClass("passengers_toggle_active");
633
634        if ($(this).hasClass("passengers_toggle_active")) {
635            $("#passengers_toggle_panel").css("display", "block");
636        }
637        return false;
638    });
639
640
641    var numberOfMonths = getNumberOfMonths();
642    //console.log(numberOfMonths);
643
644
645    //###    DATE PICKER
646
647    var rtto = $('#faresearch_route-return_date').datepicker({
648            minDate: 1,
649            dateFormat: 'd M yy',
650            buttonImage: '/themes/custom/italiarail-v2/img/icon-calendar.png',
651            showOn: 'both',
652            hideIfNoPrevNext: true,
653            numberOfMonths: numberOfMonths,
654            onSelect: function() {
655                var datefrom = $(this).datepicker("getDate");
656                rtfrom.datepicker("option", "maxDate", datefrom || 0);
657            },
658        });
659
660    var rtfrom = $('#faresearch_route-departure_date').datepicker({
661        minDate: 0,
662        dateFormat: 'D, M dd',
663        buttonImage: '/themes/custom/italiarail-v2/img/icon-calendar.png',
664        showOn: 'both',
665        hideIfNoPrevNext: true,
666        numberOfMonths: numberOfMonths,
667        onSelect: function() {
668            if ( $("input[value='round_trip']:checked").val() ){
669                var date = $(this).datepicker("getDate");
670                if (date) date.setDate(date.getDate()); // Add one day
671                rtto.datepicker("option","setDate", date);
672                rtto.datepicker("option","minDate", date || 1);
673                //rtto.focus();
674            };
675        },
676        beforeShow: function () {
677            $('#passengers_toggle_panel').hide();
678        }
679    });
680
681    rtfrom.datepicker();
682    rtto.datepicker();
683
684
685    function getNumberOfMonths() {
686        var n = $(window).width() < 600 ? 1 : 2;
687        return n;
688    }
689
690    //###    AUTOCOMPLETE
691    var station_ac_options = (function () {
692        "use strict";
693        var stations_cache_data,
694            start_stations,
695            mid_stations;
696
697        $.ajax({
698            url: "/stations.txt",
699            dataType: "json",
700            cache: true,
701            success: function (data) {
702                stations_cache_data = data;
703            }
704        });
705
706        // The includes() method determines whether an array includes a certain element, returning true or false as appropriate.
707        // https://tc39.github.io/ecma262/#sec-array.prototype.includes
708        if (!Array.prototype.includes) {
709            Object.defineProperty(Array.prototype, 'includes', {
710                value: function(searchElement, fromIndex) {
711                    if (this == null) {
712                        throw new TypeError('"this" is null or not defined');
713                    }
714
715                    var o = Object(this);
716                    var len = o.length >>> 0;
717
718                    if (len === 0) {
719                        return false;
720                    }
721
722                    var n = fromIndex | 0;
723                    var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
724
725                    function sameValueZero(x, y) {
726                        return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
727                    }
728
729                    while (k < len) {
730                        if (sameValueZero(o[k], searchElement)) {
731                            return true;
732                        }
733                        k++;
734                    }
735
736                    return false;
737                }
738            });
739        }
740
741        function _filterStations(request, response, stations_cache_data) {
742            var start_matcher = new RegExp("^" + $.ui.autocomplete.escapeRegex(request.term), "i"),
743                mid_matcher = new RegExp(".+\\b" + $.ui.autocomplete.escapeRegex(request.term), "i");
744
745            start_stations = $.grep(stations_cache_data, function (item) {
746                return start_matcher.test(item.label);
747            });
748
749            mid_stations = $.grep(stations_cache_data, function (item) {
750                return mid_matcher.test(item.label) && !start_stations.includes(item);
751            });
752
753            start_stations.sort(compareIgnoreCase);
754            mid_stations.sort(compareIgnoreCase);
755            response(start_stations.concat(mid_stations));
756        }
757
758        return {
759            filterStations: _filterStations,
760            source: function (request, response) {_filterStations(request, response, stations_cache_data);},
761            focus: function (event, ui) {
762                if (event.which !== undefined) {
763                    $(this).val(ui.item.label);
764                }
765                return false;
766            },
767            select: function (event, ui) {
768                $(this).val(ui.item.label);
769                $("#" + $(this)[0].id + "_id").val(ui.item.value);
770                if (typeof updateSubmit === 'function') {
771                    updateSubmit();
772                }
773                return false;
774            },
775            autoFocus: true
776        };
777    }());
778
779    $('#faresearch_route-origin').autocomplete(station_ac_options);
780    $('#faresearch_route-destination').autocomplete(station_ac_options);
781
782    $('#swap_input').click(function (event, ui) {
783        switchOriginDestination();
784    });
785
786    $('#swap_input').mousedown(function () {
787        $(this).addClass('submit_active');
788    });
789
790    $('#swap_input').mouseup(function () {
791        $(this).css('background-color', 'white');
792        $(this).removeClass('submit_active');
793    });
794
795    //### GET FROM
796    var fromgood = false;
797    var togood = false;
798    var depgood = false;
799
800    var from = getParameterByName('from');
801    /*$.grep(STATION_NAMES, function (item, idx) {
802        if (item.value == from || item.label == from) {
803            label = item.label;
804            $("#faresearch_route-origin").val(item.label);
805            $("#faresearch_route-origin_id").val(item.value);
806            fromgood = true;
807        }
808    });*/
809
810    //### GET TO
811    var to = getParameterByName('to');
812    /*$.grep(STATION_NAMES, function (item, idx) {
813
814        if (item.value == to || item.label == to) {
815            label = item.label;
816            $("#faresearch_route-destination").val(item.label);
817            $("#faresearch_route-destination_id").val(item.value);
818            togood = true;
819        }
820    });*/
821
822    if (fromgood && togood) {
823        // then show the calendar
824        //$('#faresearch_route-departure_date').datepicker('show');
825    }
826
827    //###    CURRENCY SELECTOR
828        // Add modal for pass
829    /*    $('#railpass-help2').dialog({
830            autoOpen: false,
831            trigger: '.railpass-help2-open',
832            overlayClass: 'railpass-help2Overlay',
833            closeClass: 'railpass-help2-close'
834        });*/
835
836    $("#faresearch_passholder").change(
837        function (event) {
838            if ($(event.currentTarget).val() > 0) {
839                //var background = blanket();
840                popup('railpass-help1',!blanket());
841                $("input[value='round_trip']").prop("disabled", true);
842            }
843
844            if ($(event.currentTarget).val() == 0) {
845                $("input[value='round_trip']").prop("disabled", false);
846            }
847        }
848    );
849
850    $(".railpassconfirm").click(function(){
851      if (get_background_context){
852        popup('railpass-help1', false);
853      } else {
854        popup('railpass-help1', true);
855      }
856    });
857
858    $("input[value='one_way']").click(function() {
859        if( $(this).is(':checked')) {
860            $("#faresearch_route-return_date").hide();
861            $("#faresearch_route-departure_date").removeClass("wide");
862            $("#faresearch_route-return_date").removeClass("wide");
863            $("#faresearch_route-return_date").disabled = true;
864            $(".btn.dropdown-toggle").prop("disabled", false);
865            rtfrom.datepicker("option", "maxDate", null);
866        }
867    });
868
869    $("input[value='round_trip']").click(function() {
870        if( $(this).is(':checked')) {
871            $("#faresearch_route-return_date").show();
872            $("#faresearch_route-departure_date").addClass("wide");
873            $("#faresearch_route-return_date").addClass("wide");
874            $("#faresearch_route-return_date").disabled = false;
875            $(".btn.dropdown-toggle").prop("disabled", true);
876            var date = rtfrom.datepicker("getDate");
877            if (date){
878                rtto.datepicker("option","setDate", date);
879                rtto.datepicker("option","minDate", date || 1);
880                //rtto.datepicker();
881            }
882            var dateto = rtto.datepicker("getDate");
883            if (dateto){
884                rtfrom.datepicker("option", "maxDate", dateto);
885            }
886
887        }
888    });
889
890    $('.railpass-help2-close').on('click', function(){
891        popup('railpass-help1', !get_background_context());
892    });
893    $('.railpass-help2-close').on('click', function(){
894      //$('#faresearch_passholder').val('0');
895      $('.btn-group ul.selectpicker li').each(function(){
896          $(this).removeClass('selected');
897          $('.btn-group ul.selectpicker li:first-child').addClass('selected');
898      });
899    });
900
901    saveAffiliateInfo();
902    setPassholderPref();
903}
904
905function toggle(div_id) {
906    var el = document.getElementById(div_id);
907    if (el.style.display == 'none') {
908        el.style.display = 'block';
909    }
910    else {
911        el.style.display = 'none';
912    }
913}
914
915function blanket_size(popUpDivVar) {
916    if (typeof window.innerWidth != 'undefined') {
917        viewportheight = window.innerHeight;
918    } else {
919        viewportheight = document.documentElement.clientHeight;
920    }
921    if ((viewportheight > document.body.parentNode.scrollHeight) && (viewportheight > document.body.parentNode.clientHeight)) {
922        blanket_height = viewportheight;
923    } else {
924        if (document.body.parentNode.clientHeight > document.body.parentNode.scrollHeight) {
925            blanket_height = document.body.parentNode.clientHeight;
926        } else {
927            blanket_height = document.body.parentNode.scrollHeight;
928        }
929    }
930    var blanket = document.getElementById('blanket');
931    blanket.style.height = blanket_height + 'px';
932    var popUpDiv = document.getElementById(popUpDivVar);
933    popUpDiv_height = blanket_height / 2 - 400;//150 is half popup's height
934    popUpDiv.style.top = (popUpDiv_height - 200) + 'px';
935}
936
937function window_pos(popUpDivVar) {
938    if (typeof window.innerWidth != 'undefined') {
939        viewportwidth = window.innerHeight;
940    } else {
941        viewportwidth = document.documentElement.clientHeight;
942    }
943    if ((viewportwidth > document.body.parentNode.scrollWidth) && (viewportwidth > document.body.parentNode.clientWidth)) {
944        window_width = viewportwidth;
945    } else {
946        if (document.body.parentNode.clientWidth > document.body.parentNode.scrollWidth) {
947            window_width = document.body.parentNode.clientWidth;
948        } else {
949            window_width = document.body.parentNode.scrollWidth;
950        }
951    }
952    var popUpDiv = document.getElementById(popUpDivVar);
953    window_width = (window_width / 2 - 150) - 50;//150 is half popup's width
954    popUpDiv.style.left = window_width + 'px';
955}
956
957/**
958 * Pops up the window indicated as parameter
959 *
960 */
961function popup(windowname, blanketbackground) {
962    if (undefined === blanketbackground) {
963        blanketbackground = true;
964    }
965
966    var context = get_background_context();
967    blanket_size(windowname);
968    window_pos(windowname);
969    if (!context) {
970          toggle('blanket');
971    } else if (blanketbackground){
972        toggle('blanket');
973    }
974    toggle(windowname);
975    ga('send', 'event', 'popup', windowname, getAffiliate());
976}
977
978// Return true if we have the booking widget popup
979function get_background_context(){
980  return $('#bookingwidgetpopup').length;
981}
982
983// Check if blanket background is active
984function blanket() {
985    if ( $('#blanket').css('display') == 'block' || $('#blanket').css("visibility") == "show"){
986      // Background is visible
987      return true;
988    } else{
989      // Background is NOT visible
990      return false;
991    }
992}
993
994/**
995* Wait for element to be visible
996*/
997var waitForEl = function(selector, callback) {
998  if (jQuery(selector).length) {
999    callback();
1000  } else {
1001    setTimeout(function() {
1002      waitForEl(selector, callback);
1003    }, 100);
1004  }
1005};
1006
1007/**
1008* Choose either to track or not the search based on stations.
1009*/
1010function searchToTrack() {
1011  if (drupalSettings.italiaPassSettings.stations) {
1012    if (((drupalSettings.italiaPassSettings.stations).indexOf($('#faresearch_route-origin_id').val() ) >= 0) || ((drupalSettings.italiaPassSettings.stations).indexOf($('#faresearch_route-destination_id').val() ) >= 0)) {
1013        // We should show the popup
1014        return true;
1015    }
1016  }
1017  return false;
1018};
1019
1020function selectRoundTrip(){
1021  $("input[value='round_trip']").prop('checked', true);
1022  $("#faresearch_route-return_date").show();
1023  $("#faresearch_route-departure_date").addClass("wide");
1024  $("#faresearch_route-return_date").addClass("wide");
1025  //$("#faresearch_route-return_date").disabled = false;
1026  waitForEl(".btn.dropdown-toggle", function() {
1027    $(".btn.dropdown-toggle").prop("disabled", true);
1028  });
1029
1030}
1031
1032/**
1033 * Get parameter from URL query(search) string
1034 *
1035 * Example:
1036 * var id = getQueryString('id')
1037 *
1038 * @param {string} name
1039 * @returns {string}
1040 */
1041function getQueryString(name) {
1042  var result = location.href.match(new RegExp("[\?\&]" + name + "=([^\&]+)", "i"));
1043  if (result == null || result.length < 1) {
1044    return "";
1045  }
1046  return decodeURIComponent(result[1]);
1047};
1048
1049function trackUserSearch(submitForm = true) {
1050    var hash = docCookies.getItem('italiapass_user');
1051    var email = $('.italy-pass-search-email-form .form-item-email input').val();
1052    var from = '';
1053    var to = '';
1054    var depart = '';
1055    var roundtrip = 0;
1056    var return_date = '';
1057
1058
1059    var form = false;
1060    if ( $('#faresearch #submitButton, #faresearch2 #submitButton').length > 0 ) {
1061        var form = $('#faresearch #submitButton, #faresearch2 #submitButton').form();
1062        var from = form.find('#faresearch_route-origin_id').val();
1063        var to = form.find('#faresearch_route-destination_id').val();
1064        var depart = form.find('#faresearch_route-departure_date').val();
1065        var roundtrip = 0;
1066        var return_date = form.find('#faresearch_route-return_date').val();
1067    }
1068
1069    var pathname = window.location.pathname;
1070
1071    if (return_date != 'undefined' && return_date != '') {
1072      roundtrip = 1;
1073      return_date = Math.floor(Date.parse(return_date) / 1000);
1074    }
1075    else {
1076      return_date = 0;
1077    }
1078
1079    var depart_date = 0;
1080
1081    if (depart != '') {
1082        depart_date = Math.floor(Date.parse(depart) / 1000);
1083    }
1084
1085    // Change values for popular routes.
1086    if ($('article.popular-routes-new.full').length > 0) {
1087        isPopularRoute = true;
1088        from = $('article.popular-routes-new.full').attr('data-departure-station-id');
1089        to = $('article.popular-routes-new.full').attr('data-destination-station-id')
1090    }
1091
1092    $.ajax({
1093      url: '/v1/italiapass/research/add',
1094      type: 'POST',
1095      data: {
1096        hash: hash,
1097        from: from,
1098        to: to,
1099        roundtrip: roundtrip,
1100        depart: depart_date,
1101        return: return_date,
1102        path: pathname,
1103      },
1104      success: function (response) {
1105        if (submitForm) {
1106            if (isRerail()){
1107              isRerailSubmit(form);
1108            } else {
1109              form.submit();
1110            }
1111        }
1112      },
1113      // We should always submit.
1114      error: function (response) {
1115        if (submitForm) {
1116            if (isRerail()){
1117              isRerailSubmit(form);
1118            } else {
1119              form.submit();
1120            }
1121        }
1122      }
1123    });
1124}
1125
1126
1127function isRerailSubmit(form){
1128
1129  if (drupalSettings.show_roundtrip != 'undefined' && drupalSettings.show_roundtrip == 0) {
1130      var tripType = 'one_way';
1131  }
1132  else {
1133      var tripType = $("input[name='faresearch_route-trip_type']:checked").val();
1134  }
1135
1136  if (tripType.toLowerCase() === 'round_trip') {
1137    getRerailCartCount(function(response) {
1138      if (response.total) { // show confirmation section when cart is not empty
1139        $('#rountrip-confirm-modal').modal();
1140        return;
1141      }
1142      // Disable submit button to avoid several click.
1143      form.find('.submit').attr('disabled', true).addClass('disabled');
1144      $('#waiting').dialog('open');
1145      disableRerail();
1146      form.submit();
1147    });
1148  } else {
1149    var depart = document.getElementById('faresearch_route-departure_date').value;
1150    var departure_date = isRerailDate(depart);
1151    searchRerailTrains(
1152      $('#faresearch_route-origin_id').val(),
1153      $('#faresearch_route-destination_id').val(),
1154      departure_date,
1155      $('#faresearch_route-return_date').val(),
1156      $('#faresearch_passengers-adult').val(),
1157      $('#faresearch_passengers-child').val(),
1158      $('#faresearch_passholder').val(),
1159      $('#aff').val(),
1160      tripType
1161    );
1162  }
1163  return;
1164
1165}
1166
1167document.addEventListener('DOMContentLoaded', function() {
1168    const searchIcon = document.querySelector('.search-icon');
1169    const searchContainer = document.querySelector('.search-container');
1170
1171    searchIcon.addEventListener('click', function() {
1172        searchContainer.classList.toggle('active');
1173        if (searchContainer.classList.contains('active')) {
1174            document.querySelector('#nav-keys').focus();
1175        }
1176    });
1177});
1178
1179// Function to initialize the countdown on a given wrapper
1180function initializeCountdown(wrapperId) {
1181  const wrapper = document.getElementById(wrapperId);
1182  const countdownEl = wrapper.querySelector(".countdown");
1183  const timerContainer = wrapper.querySelector(".expiration-timer");
1184  const chronoIcon = wrapper.querySelector(".cart-trip-chrono");
1185  const helpBtn = wrapper.querySelector(".help-button");
1186  const tooltip = wrapper.querySelector(".tooltip");
1187
1188  // Determine the fetch URL based on the current host
1189  const currentHost = window.location.hostname;
1190  let fetchUrl;
1191  let fetchHost;
1192
1193  // Check if the current host is cms-origin.stage.italiarail.com
1194  if (currentHost === "cms-origin.stage.italiarail.com") {
1195    fetchHost = "https://newshop.stage.italiarail.com";
1196  } else {
1197    fetchHost = "https://shop.italiarail.com";
1198  }
1199
1200  // If we have expiration then show the cart icon too with proper env link 
1201  let cartLink = document.getElementById("cart");
1202
1203  if (cartLink) {
1204    // Get the current href
1205    let currentHref = cartLink.getAttribute("href");
1206
1207    // Update the href attribute
1208    if (!currentHref.startsWith(fetchHost)) {
1209      cartLink.setAttribute("href", fetchHost + currentHref);
1210    }
1211  }
1212  
1213  // Construct the endpoint URL based on environment 
1214  fetchUrl = fetchHost += '/api/cms/cart/count';
1215
1216  function startCountdown(expirationTimeStr) {
1217    const expirationTime = new Date(expirationTimeStr);
1218
1219    function updateCountdown() {
1220      const now = new Date();
1221      const diffMs = expirationTime - now;
1222
1223      if (diffMs <= 0) {
1224        clearInterval(countdownInterval);
1225        wrapper.classList.remove("d-flex");
1226        return;
1227      } else {
1228        wrapper.classList.add("d-flex");
1229      }
1230
1231      const totalSeconds = Math.floor(diffMs / 1000);
1232      const minutes = Math.floor(totalSeconds / 60);
1233      const seconds = totalSeconds % 60;
1234      countdownEl.textContent = `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
1235
1236      if (totalSeconds <= 180) {
1237        timerContainer.classList.add("trip-chrono-warning");
1238        chronoIcon.classList.add("icon-chrono-warning");
1239      }
1240    }
1241
1242    updateCountdown();
1243    var countdownInterval = setInterval(updateCountdown, 1000);
1244  }
1245
1246  // Determine the expiration time before fetching the data
1247  const urlParams = new URLSearchParams(window.location.search);
1248  const useFakeExpiration = urlParams.get('useFakeExpiration') === 'true';
1249
1250  let expirationTime;
1251
1252  if (useFakeExpiration) {
1253    // Set expiration time to 5 minutes ahead
1254    const now = new Date();
1255    now.setMinutes(now.getMinutes() + 5); // Add 5 minutes
1256    expirationTime = now.toISOString(); // Convert to ISO 8601 string
1257  } else {
1258    expirationTime = null; // No fake expiration, we will fetch it from the API
1259  }
1260
1261  // Fetch the JSON from API only if no fake expiration is used
1262  if (!useFakeExpiration) {
1263    fetch(fetchUrl, {
1264      credentials: 'include'
1265    })
1266    .then(response => {
1267      if (!response.ok) throw new Error("Failed to fetch cart data");
1268      return response.json();
1269    })
1270    .then(data => {
1271      if (data.earliestExpirationTime) {
1272        expirationTime = data.earliestExpirationTime;
1273      } else {
1274        console.warn("No expiration time in response");
1275        timerContainer.style.display = "none";
1276        if (cartLink) { cartLink.style.display = "none"; }
1277        return; // Exit early if no valid expiration time
1278      }
1279
1280      startCountdown(expirationTime);
1281    })
1282    .catch(error => {
1283      console.error("Error fetching countdown data:", error);
1284      if (cartLink) { cartLink.style.display = "none"; }
1285      timerContainer.style.display = "none";
1286    });
1287  } else {
1288    // Start the countdown with the manually set expiration time (5 minutes ahead)
1289    startCountdown(expirationTime);
1290  }
1291
1292  // Tooltip toggle
1293  helpBtn.addEventListener("click", (e) => {
1294    e.stopPropagation();
1295    tooltip.classList.toggle("show");
1296  });
1297
1298  document.addEventListener("click", () => {
1299    tooltip.classList.remove("show");
1300  });
1301
1302}
1303
1304// Initialize countdown for both mobile and desktop wrappers
1305initializeCountdown("mobile-exp-wrapper");
1306initializeCountdown("desktop-exp-wrapper");

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.