PageSourceSearch

https://www.grandline.ru/js/app.js?v=1784620204

js grandline.ru collected 2026-10-03 19:49:07 UTC 50,218 bytes, 1,227 lines download raw bytes

vendor: 13,604 bytes, lines 1-410
1/*!
2 * app.js
3 */
4
5    /**
6    * jquery-match-height master by @liabru
7    * http://brm.io/jquery-match-height/
8    * License: MIT
9    */
10
11    (function(factory) { // eslint-disable-line no-extra-semi
12        'use strict';
13        if (typeof define === 'function' && define.amd) {
14            // AMD
15            define(['jquery'], factory);
16        } else if (typeof module !== 'undefined' && module.exports) {
17            // CommonJS
18            module.exports = factory(require('jquery'));
19        } else {
20            // Global
21            factory(jQuery);
22        }
23    })(function($) {
24        /*
25        *  internal
26        */
27
28        var _previousResizeWidth = -1,
29            _updateTimeout = -1;
30
31        /*
32        *  _parse
33        *  value parse utility function
34        */
35
36        var _parse = function(value) {
37            // parse value and convert NaN to 0
38            return parseFloat(value) || 0;
39        };
40
41        /*
42        *  _rows
43        *  utility function returns array of jQuery selections representing each row
44        *  (as displayed after float wrapping applied by browser)
45        */
46
47        var _rows = function(elements) {
48            var tolerance = 1,
49                $elements = $(elements),
50                lastTop = null,
51                rows = [];
52
53            // group elements by their top position
54            $elements.each(function(){
55                var $that = $(this),
56                    top = $that.offset().top - _parse($that.css('margin-top')),
57                    lastRow = rows.length > 0 ? rows[rows.length - 1] : null;
58
59                if (lastRow === null) {
60                    // first item on the row, so just push it
61                    rows.push($that);
62                } else {
63                    // if the row top is the same, add to the row group
64                    if (Math.floor(Math.abs(lastTop - top)) <= tolerance) {
65                        rows[rows.length - 1] = lastRow.add($that);
66                    } else {
67                        // otherwise start a new row group
68                        rows.push($that);
69                    }
70                }
71
72                // keep track of the last row top
73                lastTop = top;
74            });
75
76            return rows;
77        };
78
79        /*
80        *  _parseOptions
81        *  handle plugin options
82        */
83
84        var _parseOptions = function(options) {
85            var opts = {
86                byRow: true,
87                property: 'height',
88                target: null,
89                remove: false
90            };
91
92            if (typeof options === 'object') {
93                return $.extend(opts, options);
94            }
95
96            if (typeof options === 'boolean') {
97                opts.byRow = options;
98            } else if (options === 'remove') {
99                opts.remove = true;
100            }
101
102            return opts;
103        };
104
105        /*
106        *  matchHeight
107        *  plugin definition
108        */
109
110        var matchHeight = $.fn.matchHeight = function(options) {
111            var opts = _parseOptions(options);
112
113            // handle remove
114            if (opts.remove) {
115                var that = this;
116
117                // remove fixed height from all selected elements
118                this.css(opts.property, '');
119
120                // remove selected elements from all groups
121                $.each(matchHeight._groups, function(key, group) {
122                    group.elements = group.elements.not(that);
123                });
124
125                // TODO: cleanup empty groups
126
127                return this;
128            }
129
130            if (this.length <= 1 && !opts.target) {
131                return this;
132            }
133
134            // keep track of this group so we can re-apply later on load and resize events
135            matchHeight._groups.push({
136                elements: this,
137                options: opts
138            });
139
140            // match each element's height to the tallest element in the selection
141            matchHeight._apply(this, opts);
142
143            return this;
144        };
145
146        /*
147        *  plugin global options
148        */
149
150        matchHeight.version = 'master';
151        matchHeight._groups = [];
152        matchHeight._throttle = 80;
153        matchHeight._maintainScroll = false;
154        matchHeight._beforeUpdate = null;
155        matchHeight._afterUpdate = null;
156        matchHeight._rows = _rows;
157        matchHeight._parse = _parse;
158        matchHeight._parseOptions = _parseOptions;
159
160        /*
161        *  matchHeight._apply
162        *  apply matchHeight to given elements
163        */
164
165        matchHeight._apply = function(elements, options) {
166            var opts = _parseOptions(options),
167                $elements = $(elements),
168                rows = [$elements];
169
170            // take note of scroll position
171            var scrollTop = $(window).scrollTop(),
172                htmlHeight = $('html').outerHeight(true);
173
174            // get hidden parents
175            var $hiddenParents = $elements.parents().filter(':hidden');
176
177            // cache the original inline style
178            $hiddenParents.each(function() {
179                var $that = $(this);
180                $that.data('style-cache', $that.attr('style'));
181            });
182
183            // temporarily must force hidden parents visible
184            $hiddenParents.css('display', 'block');
185
186            // get rows if using byRow, otherwise assume one row
187            if (opts.byRow && !opts.target) {
188
189                // must first force an arbitrary equal height so floating elements break evenly
190                $elements.each(function() {
191                    var $that = $(this),
192                        display = $that.css('display');
193
194                    // temporarily force a usable display value
195                    if (display !== 'inline-block' && display !== 'flex' && display !== 'inline-flex') {
196                        display = 'block';
197                    }
198
199                    // cache the original inline style
200                    $that.data('style-cache', $that.attr('style'));
201
202                    $that.css({
203                        'display': display,
204                        'padding-top': '0',
205                        'padding-bottom': '0',
206                        'margin-top': '0',
207                        'margin-bottom': '0',
208                        'border-top-width': '0',
209                        'border-bottom-width': '0',
210                        'height': '100px',
211                        'overflow': 'hidden'
212                    });
213                });
214
215                // get the array of rows (based on element top position)
216                rows = _rows($elements);
217
218                // revert original inline styles
219                $elements.each(function() {
220                    var $that = $(this);
221                    $that.attr('style', $that.data('style-cache') || '');
222                });
223            }
224
225            $.each(rows, function(key, row) {
226                var $row = $(row),
227                    targetHeight = 0;
228
229                if (!opts.target) {
230                    // skip apply to rows with only one item
231                    if (opts.byRow && $row.length <= 1) {
232                        $row.css(opts.property, '');
233                        return;
234                    }
235
236                    // iterate the row and find the max height
237                    $row.each(function(){
238                        var $that = $(this),
239                            style = $that.attr('style'),
240                            display = $that.css('display');
241
242                        // temporarily force a usable display value
243                        if (display !== 'inline-block' && display !== 'flex' && display !== 'inline-flex') {
244                            display = 'block';
245                        }
246
247                        // ensure we get the correct actual height (and not a previously set height value)
248                        var css = { 'display': display };
249                        css[opts.property] = '';
250                        $that.css(css);
251
252                        // find the max height (including padding, but not margin)
253                        if ($that.outerHeight(false) > targetHeight) {
254                            targetHeight = $that.outerHeight(false);
255                        }
256
257                        // revert styles
258                        if (style) {
259                            $that.attr('style', style);
260                        } else {
261                            $that.css('display', '');
262                        }
263                    });
264                } else {
265                    // if target set, use the height of the target element
266                    targetHeight = opts.target.outerHeight(false);
267                }
268
269                // iterate the row and apply the height to all elements
270                $row.each(function(){
271                    var $that = $(this),
272                        verticalPadding = 0;
273
274                    // don't apply to a target
275                    if (opts.target && $that.is(opts.target)) {
276                        return;
277                    }
278
279                    // handle padding and border correctly (required when not using border-box)
280                    if ($that.css('box-sizing') !== 'border-box') {
281                        verticalPadding += _parse($that.css('border-top-width')) + _parse($that.css('border-bottom-width'));
282                        verticalPadding += _parse($that.css('padding-top')) + _parse($that.css('padding-bottom'));
283                    }
284
285                    // set the height (accounting for padding and border)
286                    $that.css(opts.property, (targetHeight - verticalPadding) + 'px');
287                });
288            });
289
290            // revert hidden parents
291            $hiddenParents.each(function() {
292                var $that = $(this);
293                $that.attr('style', $that.data('style-cache') || null);
294            });
295
296            // restore scroll position if enabled
297            if (matchHeight._maintainScroll) {
298                $(window).scrollTop((scrollTop / htmlHeight) * $('html').outerHeight(true));
299            }
300
301            return this;
302        };
303
304        /*
305        *  matchHeight._applyDataApi
306        *  applies matchHeight to all elements with a data-match-height attribute
307        */
308
309        matchHeight._applyDataApi = function() {
310            var groups = {};
311
312            // generate groups by their groupId set by elements using data-match-height
313            $('[data-match-height], [data-mh]').each(function() {
314                var $this = $(this),
315                    groupId = $this.attr('data-mh') || $this.attr('data-match-height');
316
317                if (groupId in groups) {
318                    groups[groupId] = groups[groupId].add($this);
319                } else {
320                    groups[groupId] = $this;
321                }
322            });
323
324            // apply matchHeight to each group
325            $.each(groups, function() {
326                this.matchHeight(true);
327            });
328        };
329
330        /*
331        *  matchHeight._update
332        *  updates matchHeight on all current groups with their correct options
333        */
334
335        var _update = function(event) {
336            if (matchHeight._beforeUpdate) {
337                matchHeight._beforeUpdate(event, matchHeight._groups);
338            }
339
340            $.each(matchHeight._groups, function() {
341                matchHeight._apply(this.elements, this.options);
342            });
343
344            if (matchHeight._afterUpdate) {
345                matchHeight._afterUpdate(event, matchHeight._groups);
346            }
347        };
348
349        matchHeight._update = function(throttle, event) {
350            // prevent update if fired from a resize event
351            // where the viewport width hasn't actually changed
352            // fixes an event looping bug in IE8
353            if (event && event.type === 'resize') {
354                var windowWidth = $(window).width();
355                if (windowWidth === _previousResizeWidth) {
356                    return;
357                }
358                _previousResizeWidth = windowWidth;
359            }
360
361            // throttle updates
362            if (!throttle) {
363                _update(event);
364            } else if (_updateTimeout === -1) {
365                _updateTimeout = setTimeout(function() {
366                    _update(event);
367                    _updateTimeout = -1;
368                }, matchHeight._throttle);
369            }
370        };
371
372        /*
373        *  bind events
374        */
375
376        // apply on DOM ready event
377        $(matchHeight._applyDataApi);
378
379        // update heights on load and resize events
380        $(window).bind('load', function(event) {
381            matchHeight._update(false, event);
382        });
383
384        // throttled update heights on resize events
385        $(window).bind('resize orientationchange', function(event) {
386            matchHeight._update(true, event);
387        });
388
389    });
390
391
392
393    function initTooltips() {
394        $('.tooltip').on('open', function() {
395            var tooltip = this;
396            $('.tooltip.open').each(function() {
397                if(this !== tooltip){
398                    $(this).removeClass('open');
399                    $('.tooltip-popover').removeClass('at-right');
400                }
401            });
402
403            var content = $(this).closest('.tooltip').find('.tooltip__body').clone(true);
404
405            $('.tooltip-popover')
406                .addClass('open')
407                .find('.tooltip-popover__content')
408                .html(content);
409
410            calcPopoverPosition();
411            //window.lazyLoadInstance.update();
412        });
413
414        var mobile = $('.resolution-mobile').is(':visible');
415        $(window).on('resize orientationChange', function() {
416            calcPopoverPosition();
417            if(mobile !== $('.resolution-mobile').is(':visible')){
418                $('.tooltip-popover.open').trigger('close');
419                $('[data-id="tooltip-modal"]').trigger('close');
420                mobile = $('.resolution-mobile').is(':visible');
421            }
422        });
423
424        function calcPopoverPosition() {
425            $('.tooltip-popover.open').each(function() {
426                var position = {
427                    top: $('.tooltip.open').offset().top + 8,
428                    left: $('.tooltip.open').offset().left + ($('.tooltip.open .tooltip__icon').innerWidth() / 2)
429                }
430
431                $(this).css(position);
432
433                if($(this).position().left + $(this).innerWidth() > $(window).innerWidth()){
434                    $(this).addClass('at-right');
435                } else {
436                    $(this).removeClass('at-right');
437                }
438            });
439        }
440
441        $('.tooltip__icon').on('click', function(e) {
442            e.preventDefault();
443            $(this).closest('.tooltip').toggleClass('open');
444            if ($(this).closest('.tooltip').hasClass('open')) {
445                if($('.resolution-mobile').is(':hidden') && !$(this).data('modal')){
446                    $(this).closest('.tooltip').trigger('open');
447                } else {
448                    var tolltipContent = $(this).closest('.tooltip').find('.tooltip__body').html(),
449                        title = $(this).closest('.tooltip').find('.tooltip__title').html();
450
451                    $('[data-id="tooltip-modal"]').find('.modal-body').html(tolltipContent);
452                    $('[data-id="tooltip-modal"]')
453                        .trigger('open')
454                        .find('.header__popup-title')
455                        .html(title);
456                    $('[data-id="tooltip-modal"]').addClass('active');
457                    $('[data-id="tooltip-modal"]').on('close', function() {
458                        $('.tooltip.open').removeClass('open');
459                    });
460
461                }
462            } else {
463                $('.tooltip-popover.open').trigger('close');
464            }
465        });
466
467        initHoverTooltip();
468        function initHoverTooltip() {
469            var hovered = false;
470            $('.tooltip__icon--hover').on('mouseenter', function() {
471                var self = this;
472                hovered = true;
473                setTimeout(function() {
474                    if(hovered){
475                        $(self).closest('.tooltip').toggleClass('open');
476                        if($(self).closest('.tooltip').hasClass('open')){
477                            if($('.resolution-mobile').is(':hidden') && !$(self).data('modal')){
478                                $(self).closest('.tooltip').trigger('open');
479                            } else {
480                                var tolltipContent = $(self).closest('.tooltip').find('.tooltip__body').html(),
481                                    title = $(self).closest('.tooltip').find('.tooltip__title').html();
482                                $('[data-id="tooltip-modal"]').find('.modal-body').html(tolltipContent);
483                                $('[data-id="tooltip-modal"]')
484                                    .trigger('open')
485                                    .find('.header__popup-title')
486                                    .html(title);
487                                $('[data-id="tooltip-modal"]').addClass('active');
488                                $('[data-id="tooltip-modal"]').on('close', function() {
489                                    $('.tooltip.open').removeClass('open');
490                                });
491                            }
492                        } else {
493                            $('.tooltip-popover.open').trigger('close');
494                        }
495                    }
496                }, 1 * 1000);
497            });
498            $('.tooltip__icon--hover').on('mouseleave', function() {
499                hovered = false;
500                $('.tooltip-popover.open').trigger('close');
501            });
502        }
503
504        $(document).on('click', function(e) {
505            if($(e.target).closest('.tooltip-popover').length || $(e.target).closest('.tooltip').length) return;
506            $('.tooltip-popover.open').trigger('close');
507        });
508
509        $('.tooltip-popover__close').on('click', function() {
510            $('.tooltip-popover.open').trigger('close');
511        });
512
513        $('.tooltip-popover').on('close', function() {
514            $('.tooltip-popover').removeClass('open');
515            $('.tooltip.open').removeClass('open');
516            setTimeout(function() {
517                $('.tooltip-popover').removeClass('at-right');
518            }, 350);
519        });
520    }
521
522
523    $(document).ready(function(){
524
525        initTooltips();
526
527        if (location.search.indexOf('feedback') !== -1) {
528            document.querySelector('[data-popup="#header__feedback"]').click();
529        }
530
531        var domain = (window.location.hostname.indexOf('grandline.by') !== -1) ? 'by' : 'ru';
532
533        var masks = {
534            "by" : {
535                "mask" : "+375 00 000-00-00",
536                "match" : /\+375 [0-9]{2} [0-9]{3}-[0-9]{2}-[0-9]{2}/i,
537                "format" : '+375 99 999-11-22'
538
539            },
540            "ru" : {
541                "mask" : "+7 (000) 000-00-00",
542                "match" : /\+7 \([0-9]{3}\) [0-9]{3}-[0-9]{2}-[0-9]{2}/i,
543                "format" : '+7 (999) 999-11-22'
544           },
545        }
546
547        // Валидация формы стать дилером
548        if ($("form#become_dealer-form input[name=dealer_phone]").length > 0) {
549            $("form#become_dealer-form input[name=dealer_phone]").mask(masks[domain]['mask'], {
550                onChange: function(cep){
551                    if(cep.match(masks[domain]['match'])) {
552                        $("form#become_dealer-form .btn").removeClass('disabled').removeAttr('disabled');
553                    } else {
554                        $("form#become_dealer-form .btn").addClass('disabled').attr('disabled', 'disabled');
555                    }
556                },
557            });
558        }
559
560        // Валидация формы стать дилером (FULL)
561        $("form#become_dealer_full-form input[name=dealerf_phone]").mask(masks[domain]['mask']);
562        $(document).on('submit', "form#become_dealer_full-form", function () {
563            var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
564            var errors = false;
565            var company_selector = "form#become_dealer_full-form input[name=dealerf_company]";
566            var city_selector = "form#become_dealer_full-form input[name=dealerf_city]";
567            var name_selector = "form#become_dealer_full-form input[name=dealerf_name]";
568            var phone_selector = "form#become_dealer_full-form input[name=dealerf_phone]";
569            var email_selector = "form#become_dealer_full-form input[name=dealerf_email]";
570            var inn_selector = "form#become_dealer_full-form input[name=dealerf_inn]";
571            var dealerf_agree = "form#become_dealer_full-form input[name=dealerf_agree]";
572
573            $("form#become_dealer_full-form").find('.input-text-holder.front_form_has_error').removeClass('front_form_has_error');
574            $("form#become_dealer_full-form").find(".input-note").empty();
575            $(dealerf_agree).parent().children('span').css('color', '');
576
577            if($(company_selector).val() == '') {
578                errors = true;
579                add_error_to_field(company_selector, 'Поле обязательно для заполнения.');
580            }
581            if($(city_selector).val() == '') {
582                errors = true;
583                add_error_to_field(city_selector, 'Поле обязательно для заполнения.');
584            }
585            if($(name_selector).val() == '') {
586                errors = true;
587                add_error_to_field(name_selector, 'Поле обязательно для заполнения.');
588            }
589            if($(phone_selector).val() == '') {
590                errors = true;
591                add_error_to_field(phone_selector, 'Поле обязательно для заполнения.');
592            } else if(!$(phone_selector).val().match(masks[domain]['match'])) {
593                errors = true;
594                add_error_to_field(phone_selector, 'Формат не верен: '+masks[domain]['format']+'.');
595            }
596            if($(email_selector).val() == '') {
597                errors = true;
598                add_error_to_field(email_selector, 'Поле обязательно для заполнения.');
599            } else if(!$(email_selector).val().match(re)) {
600                add_error_to_field(email_selector, 'Формат почты не верен.');
601            }
602            if($(inn_selector).val() == '') {
603                errors = true;
604                add_error_to_field(inn_selector, 'Поле обязательно для заполнения.');
605            }
606            if(!$(dealerf_agree).is(':checked')) {
607                errors = true;
608                add_error_to_field(dealerf_agree, '', true);
609            }
610
611            if(errors == true)
612                return false;
613            else
614                return true;
615        });
616
617        //Валидация формы Скидка 500 руб
618        $("form#discount-form input[name=discount_phone]").mask(masks[domain]['mask']);
619        $(document).on('submit', "form#discount-form", function () {
620            var errors = false;
621            var phone_selector = "form#discount-form input[name=discount_phone]";
622            var agree_selector = "form#discount-form input[name=discount_agree]";
623            reset_error_from_field(phone_selector);
624            reset_error_from_field(agree_selector, true);
625            if($(phone_selector).val() == '') {
626                errors = true;
627                add_error_to_field(phone_selector, 'Поле обязательно для заполнения.');
628            } else if(!$(phone_selector).val().match(masks[domain]['match'])) {
629                errors = true;
630                add_error_to_field(phone_selector, 'Формат не верен: '+masks[domain]['format']+'.');
631            }
632            if(!$(agree_selector).is(':checked')) {
633                errors = true;
634                add_error_to_field(agree_selector, '', true);
635            }
636            if(errors == true)
637                return false;
638            else
639                return true;
640        });
641
642        //Валидация формы обратного звонка
643        $("form#header__popup-tel input[name=callback_phone]").mask(masks[domain]['mask']);
644
645        // Валидация поля телефона при потере фокуса
646        $(document).on('blur', "form#header__popup-tel input[name=callback_phone]", function () {
647            var phone_selector = "form#header__popup-tel input[name=callback_phone]";
648            reset_error_from_field(phone_selector);
649            if($(phone_selector).val() == '') {
650                add_error_to_field(phone_selector, 'Поле обязательно для заполнения.');
651            } else if(!$(phone_selector).val().match(masks[domain]['match'])) {
652                add_error_to_field(phone_selector, 'Формат не верен: '+masks[domain]['format']+'.');
653            }
654        });
655
656        $(document).on('submit', "form#header__popup-tel", function () {
657            var errors = false;
658            var phone_selector = "form#header__popup-tel input[name=callback_phone]";
659            var agree_selector = "form#header__popup-tel input[name=pd_agreement]";
660
661            reset_error_from_field(phone_selector);
662            reset_error_from_field(agree_selector, true);
663            if($(phone_selector).val() == '') {
664                errors = true;
665                add_error_to_field(phone_selector, 'Поле обязательно для заполнения.');
666            } else if(!$(phone_selector).val().match(masks[domain]['match'])) {
667                errors = true;
668                add_error_to_field(phone_selector, 'Формат не верен: '+masks[domain]['format']+'.');
669            }
670            if(!$(agree_selector).is(':checked')) {
671                errors = true;
672                add_error_to_field(agree_selector, '', true);
673            }
674
675            if(errors == true) {
676                return false;
677            } else {
678                return true;
679            }
680        });
681
682        //Валидация формы пожаловаться/похвалить
683        $("form#report-form input[name=report_phone]").mask(masks[domain]['mask']);
684        $(document).on('submit', "form#report-form", function () {
685            var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
686            var errors = false;
687            var name_selector = "form#report-form input[name=report_name]";
688            var phone_selector = "form#report-form input[name=report_phone]";
689            var email_selector = "form#report-form input[name=report_email]";
690            var message_selector = "form#report-form textarea[name=report_message]";
691            var agree_selector = "form#report-form input[name=report_agree]";
692            reset_error_from_field(name_selector);
693            reset_error_from_field(phone_selector);
694            reset_error_from_field(email_selector);
695            reset_error_from_field(message_selector);
696            reset_error_from_field(agree_selector, true);
697            if($(name_selector).val() == '') {
698                errors = true;
699                add_error_to_field(name_selector, 'Поле обязательно для заполнения.');
700            }
701            if($(phone_selector).val() == '') {
702                errors = true;
703                add_error_to_field(phone_selector, 'Поле обязательно для заполнения.');
704            } else if(!$(phone_selector).val().match(masks[domain]['match'])) {
705                errors = true;
706                add_error_to_field(phone_selector, 'Формат не верен: '+masks[domain]['format']+'.');
707            }
708            if($(email_selector).val() != '' && !$(email_selector).val().match(re)) {
709                errors = true;
710                add_error_to_field(email_selector, 'Формат почты не верен.');
711            }
712            if($(message_selector).val() == '') {
713                errors = true;
714                add_error_to_field(message_selector, 'Поле обязательно для заполнения.');
715            }
716            if(!$(agree_selector).is(':checked')) {
717                errors = true;
718                add_error_to_field(agree_selector, '', true);
719            }
720            if(errors == true)
721                return false;
722            else
723                return true;
724        });
725
726        //Валидация формы расчет вентиляции
727        $("form#ventil-form input[name=ventil_phone]").mask(masks[domain]['mask']);
728        $(document).on('submit', "form#ventil-form", function () {
729            var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
730            var errors = false;
731            var square_selector = "form#ventil-form input[name=ventil_square]";
732            var region_selector = "form#ventil-form input[name=ventil_region]";
733            var name_selector = "form#ventil-form input[name=ventil_name]";
734            var phone_selector = "form#ventil-form input[name=ventil_phone]";
735            var email_selector = "form#ventil-form input[name=ventil_email]";
736            var agree_selector = "form#ventil-form input[name=ventil_agree]";
737            reset_error_from_field(square_selector);
738            reset_error_from_field(region_selector);
739            reset_error_from_field(name_selector);
740            reset_error_from_field(phone_selector);
741            reset_error_from_field(email_selector);
742            reset_error_from_field(agree_selector, true);
743
744            if($(square_selector).val() == '') {
745                errors = true;
746                add_error_to_field(square_selector, 'Поле обязательно для заполнения.');
747            }
748            if($(region_selector).val() == '') {
749                errors = true;
750                add_error_to_field(region_selector, 'Поле обязательно для заполнения.');
751            }
752            if($(name_selector).val() == '') {
753                errors = true;
754                add_error_to_field(name_selector, 'Поле обязательно для заполнения.');
755            }
756            if($(phone_selector).val() == '') {
757                errors = true;
758                add_error_to_field(phone_selector, 'Поле обязательно для заполнения.');
759            } else if(!$(phone_selector).val().match(masks[domain]['match'])) {
760                errors = true;
761                add_error_to_field(phone_selector, 'Формат не верен: '+masks[domain]['format']+'.');
762            }
763            if($(email_selector).val() != '' && !$(email_selector).val().match(re)) {
764                errors = true;
765                add_error_to_field(email_selector, 'Формат почты не верен.');
766            }
767            if(!$(agree_selector).is(':checked')) {
768                errors = true;
769                add_error_to_field(agree_selector, '', true);
770            }
771            if(errors == true)
772                return false;
773            else
774                return true;
775        });
776
777        // АЯКС кнопки новостей показать еще
778        $(".show-more-news").bind('click', function(e){
779            e.preventDefault();
780            var offset = $(this).attr('data-offset');
781            var id = $(this).attr('data-id');
782            $.ajax({
783                url: '/forms/news-pager/?limit=9&offset='+offset+"&id="+id,
784                dataType: 'html',
785                success: function(data) {
786                    $(".article-list").append(data);
787                    $(".show-more-news").attr('data-offset', parseInt(offset)+9);
788                    if ($('#news-all-count').length) {
789                        var newsAllCount = $('#news-all-count').val();
790                        if (parseInt(offset)+9 > newsAllCount) {
791                            $(".show-more-news").hide();
792                        }
793                    }
794                },
795                complete: function(){
796                    var ll = new LazyLoad({
797                        elements_selector: ".lazy",
798                        callback_set: function(el) {
799                            $(el).addClass('hidden');
800                            setTimeout(function () {
801                                $(el).removeClass('hidden');
802                            },400);
803                        }
804                    });
805                }
806            });
807        });
808
809        // сохраняем utm метку
810        var query_string = window.location.search;
811        if (query_string.indexOf('utm_') !== -1) {
812            var tdomain;
813            tdomain = window.location.hostname;
814            var shortDomain;
815            domainArr = tdomain.split('.');
816            domainArr.shift();
817            shortDomain = domainArr.join('.');
818            // php setcookie($cookieName, $query_string, $expire, '/', $_SERVER['HTTP_HOST'], false);
819            $.cookie('info_grandline_utm', query_string, {expires: 30, path: '/', domain: shortDomain});
820        }
821
822        // В меню каталога основного сайта разместить прямые ссылки на лендинг по забору https://zabor.grandline.ru/
823        // /shop/zabory-i-ograzhdeniya/panelnye-ograzhdeniya/
824
825        const ogr = {
826            'ograzhdeniya-3d-dlya-promyshlennyh-obektov' : 'katalog/panelnye-ograzhdeniya/profi',
827            'ograzhdeniya-2d-dlya-promyshlennyh-obektov-bastion' : 'katalog/panelnye-ograzhdeniya/bastion',
828            'ograzhdeniya-dlya-sportploshadok' : 'katalog/panelnye-ograzhdeniya/dlya-sportploschadok',
829            'protect' : 'katalog/panelnye-ograzhdeniya/protect',
830            'ograzdenie-x-line' : 'katalog/panelnye-ograzhdeniya/x-line',
831
832        }
833
834        for (let key in ogr) {
835            els = document.querySelectorAll('[href="/shop/zabory-i-ograzhdeniya/panelnye-ograzhdeniya/'+key+'/"]');
836            els.forEach(link => {
837                let href = 'https://zabor.grandline.ru/'+ogr[key];
838                if ($.cookie('info_grandline_utm')) href += $.cookie('info_grandline_utm');
839                // Замена ссылки
840                link.href = href;
841            });
842        }
843
844        function initFileLoader() {
845            $('.attach').on('click', '.attach__list-item .icon-close', function() {
846                if($(this).closest('.attach').find('.js_files_not').length > 0)
847                {
848                      $(this).closest('.attach').find('.js_files_not').val( $(this).closest('.attach').find('.js_files_not').val() + $(this).closest('.attach__list-item')[0].innerText + ",");
849                }
850                $(this).closest('.attach__list-item').remove();
851            });
852        }
853
854        initFileLoader();
855
856        Fancybox.bind("[data-fancybox]", {
857            // Your custom options
858            groupAll: true,
859            padding: 2,
860            margin: 0,
861            width: 1170,
862            loop: false,
863            helpers: {
864                title : {
865                    type : 'inside'
866                }
867            },
868            openEffect  : 'fade',
869            closeEffect : 'fade',
870            nextEffect  : 'fade',
871            prevEffect  : 'fade',
872            Thumbs: false,
873            beforeShow: function() {
874                if($('.fancybox-skin').length && this.group.length>1){
875                    $('.fancybox-wrap').addClass('fancybox-wrap__has-num');
876                }
877            },
878            afterShow: function() {
879                if($('.fancybox-skin').length && this.group.length>1){
880                    $('.fancybox-skin').append('<div class="fancybox-wrap__num"><span>'+(this.index + 1)+'</span>/<span>'+this.group.length+'</span></div>');
881                }
882                if($(this.element).is('[data-btn-title]') && $(this.element).is('[data-btn-link]')){
883                    $('.fancybox-skin').append('<a class="btn btn-arrow" href="'+$(this.element).attr('data-btn-link')+'"><span>'+$(this.element).attr('data-btn-title')+'</span><span class="icon-left-light2"></span></a>');
884                }
885            },
886            beforeLoad: function() {
887                console.log('beforeLoad')
888                console.log('$(this.element)', $(this.element))
889
890                if($(this.element).is('[data-link]') && $(this.element).is('[data-title]')){
891                    this.title = '<a target="_blank" href="' + $(this.element).attr('data-link') + '">'+$(this.element).attr('data-title')+'</a> ';
892                }else if($(this.element).is('[data-title]')){
893                    this.title = $(this.element).attr('data-title');
894                }
895            },
896        });
897
898        const phoneInputs = [
899            "#complaintrequest-phone_number",
900            "#yiiorderrequest-phone_number",
901            "#feedbackpotolokform-phone_number",
902            "#materialform-phone_number",
903            "#feedbackcladdingform-phonenumber",
904            "#dealership_request_phone_number"
905        ];
906
907        phoneInputs.forEach(function(selector) {
908            $(selector).mask(masks[domain]['mask']);
909        });
910    });
911
912    function append_attach_input(el) {
913        parent_div = document.getElementById("attach__parent");
914        el.style.display = "none";
915        var input = document.createElement("input");
916        input.type = "file";
917        input.name = el.name;
918        input.setAttribute("multiple", "multiple");
919        input.setAttribute("onchange", "append_attach_input(this)");
920        input.setAttribute("accept", el.accept);
921        parent_div.appendChild(input);
922        var attach__list = $(el).closest('.attach').find('.attach__list');
923        var k = attach__list.childElementCount;
924        $(el).closest('.attach').find('.attach__list')
925        for (var i = 0; i < $(el)[0].files.length; i++){
926            var item = '<li class="attach__list-item" data-id="' + k + i + '">' + $(el)[0].files[i].name + '<i class="icon-close"></i></li>';
927            attach__list.append(item);
928        }
929    }
930
931
932    function bind_close_btn_click() {
933        $('.header__popup-wrap').on('click','.overlay, [data-close]',function (e) {
934            $('.header__popup-wrap').removeClass('active');
935            e.preventDefault();
936        });
937    }
938
939    // Добавляем ошибку к полю в форме
940    function add_error_to_field(name_selector, error_text = '', checkbox = false) {
941        if(checkbox == false) {
942            $(name_selector).parent().addClass('front_form_has_error');
943            $(name_selector).parent().parent().addClass('error');
944            $(name_selector).parent().parent().children(".input-note").empty().append(error_text);
945        } else {
946            $(name_selector).parent().children('span').css('color', 'red');
947        }
948    }
949
950    function reset_error_from_field(name_selector, checkbox = false) {
951        if(checkbox == false) {
952            // Убираем классы ошибок
953            $(name_selector).parent().removeClass('front_form_has_error');
954            $(name_selector).parent().parent().removeClass('error');
955            // Очищаем текст ошибки
956            $(name_selector).parent().parent().children(".input-note").empty();
957        } else {
958            // Возвращаем стандартный цвет для чекбокса
959            $(name_selector).parent().children('span').css('color', '');
960        }
961    }
962
963    function initModalInfo() {
964        $(document).on('click', '[data-modal]', function(e) {
965            e.preventDefault();
966            var modalId = $(this).data('modal'),
967                video = $(this).data('video'),
968                modalTitle = $(this).data('modal-title'),
969                modalSubTitle = $(this).data('modal-subtitle'),
970                modalDescription = $(this).data('modal-description'),
971                modalImg = $(this).data('modal-img'),
972                marker = $(this).data('marker'),
973                modalActiveTab = $(this).data('tab');
974            $('#' + modalId).trigger('open');
975            $('[data-id="' + modalId + '"]').each(function() {
976                var params = {};
977                if($(this).hasClass('video-modal')){
978                    params.video = video;
979                }
980                if(modalTitle){
981                    params.title = modalTitle;
982                }
983                if(modalSubTitle){
984                    params.subtitle = modalSubTitle;
985                }
986                if(modalImg){
987                    params.img = modalImg;
988                }
989                if(modalDescription){
990                    params.description = modalDescription;
991                }
992                if(marker){
993                    params.marker = marker;
994                }
995                if(modalActiveTab){
996                    params.modalActiveTab = modalActiveTab;
997                }
998                $(this).trigger('open', params);
999            });
1000        });
1001
1002        $(document).on('click', '.close-modal', function(e) {
1003            e.preventDefault();
1004            $(this).closest('.modal-holder').trigger('close');
1005        });
1006
1007        $(document).on('click', '.close-icon', function() {
1008            $('body').removeClass('main-menu-open');
1009        });
1010
1011        if($('.modal-holder:not(.static-modal).open').length){
1012            $('body').addClass('modal-open');
1013        }
1014
1015        $(document).on('click', '.modal-holder', function(e) {
1016            if(!$(e.target).closest('.modal').length){
1017                $(this).trigger('close');
1018            }
1019        });
1020
1021        $('.modal-holder').on('open', function(e, params) {
1022            $(this).removeClass('success').css('margin-left', 0);
1023            $('body').css('margin-right', windowSizes().width - $('body').innerWidth());
1024            $('.btn-to-top').css('margin-right', (windowSizes().width - $('body').innerWidth()) / 2);
1025            $('body').addClass('modal-open');
1026            $(this).addClass('open').find('.modal-inner').scrollTop(0);
1027            if($(this).hasClass('video-modal') && !$(this).find('.video iframe').length){
1028                $(this).find('.video').append('<iframe frameborder="0">');
1029            }
1030            if(params && 'video' in params){
1031                $(this).find('iframe').attr('src', params.video);
1032            }
1033            $(this).find('[data-modal-title]').each(function() {
1034                if(params && 'title' in params){
1035                    $(this).html(params.title);
1036                } else {
1037                    $(this).html($(this).data('default-text'));
1038                }
1039            });
1040            $(this).find('[data-modal-subtitle]').each(function() {
1041                if(params && 'title' in params){
1042                    $(this).html(params.subtitle);
1043                } else {
1044                    $(this).html($(this).data('default-text'));
1045                }
1046            });
1047            $(this).find('[data-modal-description]').each(function() {
1048                if(params && 'title' in params){
1049                    $(this).html(params.description);
1050                } else {
1051                    $(this).html('');
1052                }
1053            });
1054            $(this).find('[data-modal-img]').each(function() {
1055                if(params && 'img' in params){
1056                    $(this).show();
1057                    $(this).attr('src', params.img);
1058                } else {
1059                    $(this).hide();
1060                }
1061            });
1062            if(params && 'marker' in params){
1063                $(this).find('.map-holder').each(function() {
1064                    $(this).append('<div id="modal-map" class="map" data-center="' + params.marker + '" data-marker="' + params.marker + '">');
1065                    initSingleMap($(this).find('.map'));
1066                });
1067            }
1068            if(params && 'modalActiveTab' in params){
1069                $(this).find('[data-id="' + params.modalActiveTab + '"]').click();
1070            }
1071            // window.lazyLoadInstance.update();
1072        });
1073
1074        $('.modal-holder').on('close', function(e, params) {
1075            $(this).removeClass('open');
1076            $('.video-modal.open iframe').removeAttr('src');
1077            $('.modal-holder').css('margin-left', (windowSizes().width - $('body').innerWidth()) / 2);
1078            if(!$('.modal-holder.open').length){
1079                $('body').removeClass('modal-open');
1080                $('body').css('margin-right', 0);
1081                $('.btn-to-top').css('margin-right', 0);
1082            }
1083            $('.callback-modal').removeClass('success');
1084            if($(this).hasClass('video-modal')){
1085                $(this).find('iframe').removeAttr('src');
1086            }
1087        });
1088
1089        $(window).on('load resize orientationChange', function() {
1090            if($('.modal-holder.open').length){
1091                $('.modal-holder.open .modal-inner').css('max-height', $(window).innerHeight());
1092            }
1093        });
1094    }
1095
1096    function windowSizes(){
1097        var e = window,
1098            a = 'inner';
1099        if (!('innerWidth' in window)) {
1100            a = 'client';
1101            e = document.documentElement || document.body;
1102        }
1103        return {
1104            width: e[a + 'Width'],
1105            height: e[a + 'Height']
1106        };
1107    }
1108
1109    $(document).ready(function(){
1110        initModalInfo();
1111    });
1112
1113
1114document.addEventListener('DOMContentLoaded', function () {
1115    let currentOpenBlock = null;
1116
1117    document.addEventListener('click', function (e) {
1118        // Обработка клика на .reklama-title
1119        if (e.target.closest('.reklama-title')) {
1120            const title = e.target.closest('.reklama-title');
1121            const block = title.closest('.reklama-block');
1122            const desc = block.querySelector('.reklama-description');
1123
1124            if (desc) {
1125                toggleDescription(block, desc);
1126            }
1127        }
1128
1129        // Обработка клика на .reklama-description (закрытие)
1130        if (e.target.closest('.reklama-description')) {
1131            const desc = e.target.closest('.reklama-description');
1132            const block = desc.closest('.reklama-block');
1133
1134            if (desc && currentOpenBlock === block) {
1135                toggleDescription(block, desc);
1136            }
1137        }
1138
1139        // Обработка копирования
1140        if (e.target.closest('.reklama-description_copy')) {
1141            const copyElement = e.target.closest('.reklama-description_copy');
1142            const block = copyElement.closest('.reklama-block');
1143            const eridElement = block.querySelector('.reklama-description_erid');
1144
1145            if (eridElement) {
1146                e.stopPropagation();
1147                copyTokenToClipboard(eridElement.textContent, copyElement);
1148            }
1149        }
1150
1151        // Закрытие по клику вне блоков
1152        if (!e.target.closest('.reklama-block')) {
1153            closeAllDescriptions();
1154        }
1155    });
1156
1157    function toggleDescription(block, desc) {
1158        const isOpening = desc.style.opacity !== '1';
1159
1160        if (isOpening) {
1161            // Закрываем предыдущий открытый блок
1162            if (currentOpenBlock && currentOpenBlock !== block) {
1163                const prevDesc = currentOpenBlock.querySelector('.reklama-description');
1164                if (prevDesc) {
1165                    prevDesc.style.opacity = '0';
1166                    prevDesc.style.visibility = 'hidden';
1167                }
1168            }
1169
1170            // Открываем текущий блок
1171            desc.style.opacity = '1';
1172            desc.style.visibility = 'visible';
1173            currentOpenBlock = block;
1174        } else {
1175            // Закрываем текущий блок
1176            desc.style.opacity = '0';
1177            desc.style.visibility = 'hidden';
1178            currentOpenBlock = null;
1179        }
1180    }
1181
1182    function closeAllDescriptions() {
1183        const allDescs = document.querySelectorAll('.reklama-description');
1184        allDescs.forEach(desc => {
1185            if (desc.style.opacity === '1') {
1186                desc.style.opacity = '0';
1187                desc.style.visibility = 'hidden';
1188            }
1189        });
1190        currentOpenBlock = null;
1191    }
1192
1193    function copyTokenToClipboard(token, element) {
1194        const originalText = element.textContent;
1195        navigator.clipboard.writeText(token).then(() => {
1196            element.textContent = "токен скопирован";
1197            element.classList.add('copied');
1198            setTimeout(() => {
1199                element.textContent = originalText;
1200                element.classList.remove('copied');
1201            }, 2000);
1202            console.log('✅ Токен скопирован:', token);
1203        }).catch(err => {
1204            console.error('❌ Ошибка копирования:', err);
1205            fallbackCopyTokenToClipboard(token, element, originalText);
1206        });
1207    }
1208
1209    function fallbackCopyTokenToClipboard(token, element, originalText) {
1210        const textArea = document.createElement('textarea');
1211        textArea.value = token;
1212        document.body.appendChild(textArea);
1213        textArea.select();
1214        try {
1215            document.execCommand('copy');
1216            element.textContent = "токен скопирован";
1217            element.classList.add('copied');
1218            setTimeout(() => {
1219                element.textContent = originalText;
1220                element.classList.remove('copied');
1221            }, 2000);
1222        } catch (err) {
1223            console.error('Fallback copy failed:', err);
1224        }
1225        document.body.removeChild(textArea);
1226    }
1227});

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.