PageSourceSearch

https://www.ugmk-clinic.ru/static/mobile_app/www/js/other/custom_bindings.js

js ugmk-clinic.ru collected 2026-10-02 05:11:44 UTC 158,400 bytes, 5,598 lines download raw bytes

1var phone_codes = [{
2  mask: '+247-####',
3  cc: 'AC',
4  name_en: 'Ascension',
5  desc_en: '',
6  name_ru: 'Остров Вознесения',
7  desc_ru: ''
8}, {
9  mask: '+376-###-###',
10  cc: 'AD',
11  name_en: 'Andorra',
12  desc_en: '',
13  name_ru: 'Андорра',
14  desc_ru: ''
15}, {
16  mask: '+971-5#-###-####',
17  cc: 'AE',
18  name_en: 'United Arab Emirates',
19  desc_en: 'mobile',
20  name_ru: 'Объединенные Арабские Эмираты',
21  desc_ru: 'мобильные'
22}, {
23  mask: '+971-#-###-####',
24  cc: 'AE',
25  name_en: 'United Arab Emirates',
26  desc_en: '',
27  name_ru: 'Объединенные Арабские Эмираты',
28  desc_ru: ''
29}, {
30  mask: '+93-##-###-####',
31  cc: 'AF',
32  name_en: 'Afghanistan',
33  desc_en: '',
34  name_ru: 'Афганистан',
35  desc_ru: ''
36}, {
37  mask: '+1(268)###-####',
38  cc: 'AG',
39  name_en: 'Antigua & Barbuda',
40  desc_en: '',
41  name_ru: 'Антигуа и Барбуда',
42  desc_ru: ''
43}, {
44  mask: '+1(264)###-####',
45  cc: 'AI',
46  name_en: 'Anguilla',
47  desc_en: '',
48  name_ru: 'Ангилья',
49  desc_ru: ''
50}, {
51  mask: '+355(###)###-###',
52  cc: 'AL',
53  name_en: 'Albania',
54  desc_en: '',
55  name_ru: 'Албания',
56  desc_ru: ''
57}, {
58  mask: '+374-##-###-###',
59  cc: 'AM',
60  name_en: 'Armenia',
61  desc_en: '',
62  name_ru: 'Армения',
63  desc_ru: ''
64}, {
65  mask: '+599-###-####',
66  cc: 'AN',
67  name_en: 'Caribbean Netherlands',
68  desc_en: '',
69  name_ru: 'Карибские Нидерланды',
70  desc_ru: ''
71}, {
72  mask: '+599-###-####',
73  cc: 'AN',
74  name_en: 'Netherlands Antilles',
75  desc_en: '',
76  name_ru: 'Нидерландские Антильские острова',
77  desc_ru: ''
78}, {
79  mask: '+599-9###-####',
80  cc: 'AN',
81  name_en: 'Netherlands Antilles',
82  desc_en: 'Curacao',
83  name_ru: 'Нидерландские Антильские острова',
84  desc_ru: 'Кюрасао'
85}, {
86  mask: '+244(###)###-###',
87  cc: 'AO',
88  name_en: 'Angola',
89  desc_en: '',
90  name_ru: 'Ангола',
91  desc_ru: ''
92}, {
93  mask: '+672-1##-###',
94  cc: 'AQ',
95  name_en: 'Australian bases in Antarctica',
96  desc_en: '',
97  name_ru: 'Австралийская антарктическая база',
98  desc_ru: ''
99}, {
100  mask: '+54(###)###-####',
101  cc: 'AR',
102  name_en: 'Argentina',
103  desc_en: '',
104  name_ru: 'Аргентина',
105  desc_ru: ''
106}, {
107  mask: '+1(684)###-####',
108  cc: 'AS',
109  name_en: 'American Samoa',
110  desc_en: '',
111  name_ru: 'Американское Самоа',
112  desc_ru: ''
113}, {
114  mask: '+43(###)###-####',
115  cc: 'AT',
116  name_en: 'Austria',
117  desc_en: '',
118  name_ru: 'Австрия',
119  desc_ru: ''
120}, {
121  mask: '+61-#-####-####',
122  cc: 'AU',
123  name_en: 'Australia',
124  desc_en: '',
125  name_ru: 'Австралия',
126  desc_ru: ''
127}, {
128  mask: '+297-###-####',
129  cc: 'AW',
130  name_en: 'Aruba',
131  desc_en: '',
132  name_ru: 'Аруба',
133  desc_ru: ''
134}, {
135  mask: '+994-##-###-##-##',
136  cc: 'AZ',
137  name_en: 'Azerbaijan',
138  desc_en: '',
139  name_ru: 'Азербайджан',
140  desc_ru: ''
141}, {
142  mask: '+387-##-#####',
143  cc: 'BA',
144  name_en: 'Bosnia and Herzegovina',
145  desc_en: '',
146  name_ru: 'Босния и Герцеговина',
147  desc_ru: ''
148}, {
149  mask: '+387-##-####',
150  cc: 'BA',
151  name_en: 'Bosnia and Herzegovina',
152  desc_en: '',
153  name_ru: 'Босния и Герцеговина',
154  desc_ru: ''
155}, {
156  mask: '+1(246)###-####',
157  cc: 'BB',
158  name_en: 'Barbados',
159  desc_en: '',
160  name_ru: 'Барбадос',
161  desc_ru: ''
162}, {
163  mask: '+880-##-###-###',
164  cc: 'BD',
165  name_en: 'Bangladesh',
166  desc_en: '',
167  name_ru: 'Бангладеш',
168  desc_ru: ''
169}, {
170  mask: '+32(###)###-###',
171  cc: 'BE',
172  name_en: 'Belgium',
173  desc_en: '',
174  name_ru: 'Бельгия',
175  desc_ru: ''
176}, {
177  mask: '+226-##-##-####',
178  cc: 'BF',
179  name_en: 'Burkina Faso',
180  desc_en: '',
181  name_ru: 'Буркина Фасо',
182  desc_ru: ''
183}, {
184  mask: '+359(###)###-###',
185  cc: 'BG',
186  name_en: 'Bulgaria',
187  desc_en: '',
188  name_ru: 'Болгария',
189  desc_ru: ''
190}, {
191  mask: '+973-####-####',
192  cc: 'BH',
193  name_en: 'Bahrain',
194  desc_en: '',
195  name_ru: 'Бахрейн',
196  desc_ru: ''
197}, {
198  mask: '+257-##-##-####',
199  cc: 'BI',
200  name_en: 'Burundi',
201  desc_en: '',
202  name_ru: 'Бурунди',
203  desc_ru: ''
204}, {
205  mask: '+229-##-##-####',
206  cc: 'BJ',
207  name_en: 'Benin',
208  desc_en: '',
209  name_ru: 'Бенин',
210  desc_ru: ''
211}, {
212  mask: '+1(441)###-####',
213  cc: 'BM',
214  name_en: 'Bermuda',
215  desc_en: '',
216  name_ru: 'Бермудские острова',
217  desc_ru: ''
218}, {
219  mask: '+673-###-####',
220  cc: 'BN',
221  name_en: 'Brunei Darussalam',
222  desc_en: '',
223  name_ru: 'Бруней-Даруссалам',
224  desc_ru: ''
225}, {
226  mask: '+591-#-###-####',
227  cc: 'BO',
228  name_en: 'Bolivia',
229  desc_en: '',
230  name_ru: 'Боливия',
231  desc_ru: ''
232}, {
233  mask: '+55(##)####-####',
234  cc: 'BR',
235  name_en: 'Brazil',
236  desc_en: '',
237  name_ru: 'Бразилия',
238  desc_ru: ''
239}, {
240  mask: '+55(##)7###-####',
241  cc: 'BR',
242  name_en: 'Brazil',
243  desc_en: 'mobile',
244  name_ru: 'Бразилия',
245  desc_ru: 'мобильные'
246}, {
247  mask: '+55(##)9####-####',
248  cc: 'BR',
249  name_en: 'Brazil',
250  desc_en: 'mobile',
251  name_ru: 'Бразилия',
252  desc_ru: 'мобильные'
253}, {
254  mask: '+1(242)###-####',
255  cc: 'BS',
256  name_en: 'Bahamas',
257  desc_en: '',
258  name_ru: 'Багамские Острова',
259  desc_ru: ''
260}, {
261  mask: '+975-17-###-###',
262  cc: 'BT',
263  name_en: 'Bhutan',
264  desc_en: '',
265  name_ru: 'Бутан',
266  desc_ru: ''
267}, {
268  mask: '+975-#-###-###',
269  cc: 'BT',
270  name_en: 'Bhutan',
271  desc_en: '',
272  name_ru: 'Бутан',
273  desc_ru: ''
274}, {
275  mask: '+267-##-###-###',
276  cc: 'BW',
277  name_en: 'Botswana',
278  desc_en: '',
279  name_ru: 'Ботсвана',
280  desc_ru: ''
281}, {
282  mask: '+375(##)###-##-##',
283  cc: 'BY',
284  name_en: 'Belarus',
285  desc_en: '',
286  name_ru: 'Беларусь (Белоруссия)',
287  desc_ru: ''
288}, {
289  mask: '+501-###-####',
290  cc: 'BZ',
291  name_en: 'Belize',
292  desc_en: '',
293  name_ru: 'Белиз',
294  desc_ru: ''
295}, {
296  mask: '+243(###)###-###',
297  cc: 'CD',
298  name_en: 'Dem. Rep. Congo',
299  desc_en: '',
300  name_ru: 'Дем. Респ. Конго (Киншаса)',
301  desc_ru: ''
302}, {
303  mask: '+236-##-##-####',
304  cc: 'CF',
305  name_en: 'Central African Republic',
306  desc_en: '',
307  name_ru: 'Центральноафриканская Республика',
308  desc_ru: ''
309}, {
310  mask: '+242-##-###-####',
311  cc: 'CG',
312  name_en: 'Congo (Brazzaville)',
313  desc_en: '',
314  name_ru: 'Конго (Браззавиль)',
315  desc_ru: ''
316}, {
317  mask: '+41-##-###-####',
318  cc: 'CH',
319  name_en: 'Switzerland',
320  desc_en: '',
321  name_ru: 'Швейцария',
322  desc_ru: ''
323}, {
324  mask: '+225-##-###-###',
325  cc: 'CI',
326  name_en: 'Cote d’Ivoire (Ivory Coast)',
327  desc_en: '',
328  name_ru: 'Кот-д’Ивуар',
329  desc_ru: ''
330}, {
331  mask: '+682-##-###',
332  cc: 'CK',
333  name_en: 'Cook Islands',
334  desc_en: '',
335  name_ru: 'Острова Кука',
336  desc_ru: ''
337}, {
338  mask: '+56-#-####-####',
339  cc: 'CL',
340  name_en: 'Chile',
341  desc_en: '',
342  name_ru: 'Чили',
343  desc_ru: ''
344}, {
345  mask: '+237-####-####',
346  cc: 'CM',
347  name_en: 'Cameroon',
348  desc_en: '',
349  name_ru: 'Камерун',
350  desc_ru: ''
351}, {
352  mask: '+86(###)####-####',
353  cc: 'CN',
354  name_en: 'China (PRC)',
355  desc_en: '',
356  name_ru: 'Китайская Н.Р.',
357  desc_ru: ''
358}, {
359  mask: '+86(###)####-###',
360  cc: 'CN',
361  name_en: 'China (PRC)',
362  desc_en: '',
363  name_ru: 'Китайская Н.Р.',
364  desc_ru: ''
365}, {
366  mask: '+86-##-#####-#####',
367  cc: 'CN',
368  name_en: 'China (PRC)',
369  desc_en: '',
370  name_ru: 'Китайская Н.Р.',
371  desc_ru: ''
372}, {
373  mask: '+57(###)###-####',
374  cc: 'CO',
375  name_en: 'Colombia',
376  desc_en: '',
377  name_ru: 'Колумбия',
378  desc_ru: ''
379}, {
380  mask: '+506-####-####',
381  cc: 'CR',
382  name_en: 'Costa Rica',
383  desc_en: '',
384  name_ru: 'Коста-Рика',
385  desc_ru: ''
386}, {
387  mask: '+53-#-###-####',
388  cc: 'CU',
389  name_en: 'Cuba',
390  desc_en: '',
391  name_ru: 'Куба',
392  desc_ru: ''
393}, {
394  mask: '+238(###)##-##',
395  cc: 'CV',
396  name_en: 'Cape Verde',
397  desc_en: '',
398  name_ru: 'Кабо-Верде',
399  desc_ru: ''
400}, {
401  mask: '+599-###-####',
402  cc: 'CW',
403  name_en: 'Curacao',
404  desc_en: '',
405  name_ru: 'Кюрасао',
406  desc_ru: ''
407}, {
408  mask: '+357-##-###-###',
409  cc: 'CY',
410  name_en: 'Cyprus',
411  desc_en: '',
412  name_ru: 'Кипр',
413  desc_ru: ''
414}, {
415  mask: '+420(###)###-###',
416  cc: 'CZ',
417  name_en: 'Czech Republic',
418  desc_en: '',
419  name_ru: 'Чехия',
420  desc_ru: ''
421}, {
422  mask: '+49(####)###-####',
423  cc: 'DE',
424  name_en: 'Germany',
425  desc_en: '',
426  name_ru: 'Германия',
427  desc_ru: ''
428}, {
429  mask: '+49(###)###-####',
430  cc: 'DE',
431  name_en: 'Germany',
432  desc_en: '',
433  name_ru: 'Германия',
434  desc_ru: ''
435}, {
436  mask: '+49(###)##-####',
437  cc: 'DE',
438  name_en: 'Germany',
439  desc_en: '',
440  name_ru: 'Германия',
441  desc_ru: ''
442}, {
443  mask: '+49(###)##-###',
444  cc: 'DE',
445  name_en: 'Germany',
446  desc_en: '',
447  name_ru: 'Германия',
448  desc_ru: ''
449}, {
450  mask: '+49(###)##-##',
451  cc: 'DE',
452  name_en: 'Germany',
453  desc_en: '',
454  name_ru: 'Германия',
455  desc_ru: ''
456}, {
457  mask: '+49-###-###',
458  cc: 'DE',
459  name_en: 'Germany',
460  desc_en: '',
461  name_ru: 'Германия',
462  desc_ru: ''
463}, {
464  mask: '+253-##-##-##-##',
465  cc: 'DJ',
466  name_en: 'Djibouti',
467  desc_en: '',
468  name_ru: 'Джибути',
469  desc_ru: ''
470}, {
471  mask: '+45-##-##-##-##',
472  cc: 'DK',
473  name_en: 'Denmark',
474  desc_en: '',
475  name_ru: 'Дания',
476  desc_ru: ''
477}, {
478  mask: '+1(767)###-####',
479  cc: 'DM',
480  name_en: 'Dominica',
481  desc_en: '',
482  name_ru: 'Доминика',
483  desc_ru: ''
484}, {
485  mask: '+1(809)###-####',
486  cc: 'DO',
487  name_en: 'Dominican Republic',
488  desc_en: '',
489  name_ru: 'Доминиканская Республика',
490  desc_ru: ''
491}, {
492  mask: '+1(829)###-####',
493  cc: 'DO',
494  name_en: 'Dominican Republic',
495  desc_en: '',
496  name_ru: 'Доминиканская Республика',
497  desc_ru: ''
498}, {
499  mask: '+1(849)###-####',
500  cc: 'DO',
501  name_en: 'Dominican Republic',
502  desc_en: '',
503  name_ru: 'Доминиканская Республика',
504  desc_ru: ''
505}, {
506  mask: '+213-##-###-####',
507  cc: 'DZ',
508  name_en: 'Algeria',
509  desc_en: '',
510  name_ru: 'Алжир',
511  desc_ru: ''
512}, {
513  mask: '+593-##-###-####',
514  cc: 'EC',
515  name_en: 'Ecuador ',
516  desc_en: 'mobile',
517  name_ru: 'Эквадор ',
518  desc_ru: 'мобильные'
519}, {
520  mask: '+593-#-###-####',
521  cc: 'EC',
522  name_en: 'Ecuador',
523  desc_en: '',
524  name_ru: 'Эквадор',
525  desc_ru: ''
526}, {
527  mask: '+372-####-####',
528  cc: 'EE',
529  name_en: 'Estonia ',
530  desc_en: 'mobile',
531  name_ru: 'Эстония ',
532  desc_ru: 'мобильные'
533}, {
534  mask: '+372-###-####',
535  cc: 'EE',
536  name_en: 'Estonia',
537  desc_en: '',
538  name_ru: 'Эстония',
539  desc_ru: ''
540}, {
541  mask: '+20(###)###-####',
542  cc: 'EG',
543  name_en: 'Egypt',
544  desc_en: '',
545  name_ru: 'Египет',
546  desc_ru: ''
547}, {
548  mask: '+291-#-###-###',
549  cc: 'ER',
550  name_en: 'Eritrea',
551  desc_en: '',
552  name_ru: 'Эритрея',
553  desc_ru: ''
554}, {
555  mask: '+34(###)###-###',
556  cc: 'ES',
557  name_en: 'Spain',
558  desc_en: '',
559  name_ru: 'Испания',
560  desc_ru: ''
561}, {
562  mask: '+251-##-###-####',
563  cc: 'ET',
564  name_en: 'Ethiopia',
565  desc_en: '',
566  name_ru: 'Эфиопия',
567  desc_ru: ''
568}, {
569  mask: '+358(###)###-##-##',
570  cc: 'FI',
571  name_en: 'Finland',
572  desc_en: '',
573  name_ru: 'Финляндия',
574  desc_ru: ''
575}, {
576  mask: '+679-##-#####',
577  cc: 'FJ',
578  name_en: 'Fiji',
579  desc_en: '',
580  name_ru: 'Фиджи',
581  desc_ru: ''
582}, {
583  mask: '+500-#####',
584  cc: 'FK',
585  name_en: 'Falkland Islands',
586  desc_en: '',
587  name_ru: 'Фолклендские острова',
588  desc_ru: ''
589}, {
590  mask: '+691-###-####',
591  cc: 'FM',
592  name_en: 'F.S. Micronesia',
593  desc_en: '',
594  name_ru: 'Ф.Ш. Микронезии',
595  desc_ru: ''
596}, {
597  mask: '+298-###-###',
598  cc: 'FO',
599  name_en: 'Faroe Islands',
600  desc_en: '',
601  name_ru: 'Фарерские острова',
602  desc_ru: ''
603}, {
604  mask: '+262-#####-####',
605  cc: 'FR',
606  name_en: 'Mayotte',
607  desc_en: '',
608  name_ru: 'Майотта',
609  desc_ru: ''
610}, {
611  mask: '+33(###)###-###',
612  cc: 'FR',
613  name_en: 'France',
614  desc_en: '',
615  name_ru: 'Франция',
616  desc_ru: ''
617}, {
618  mask: '+508-##-####',
619  cc: 'FR',
620  name_en: 'St Pierre & Miquelon',
621  desc_en: '',
622  name_ru: 'Сен-Пьер и Микелон',
623  desc_ru: ''
624}, {
625  mask: '+590(###)###-###',
626  cc: 'FR',
627  name_en: 'Guadeloupe',
628  desc_en: '',
629  name_ru: 'Гваделупа',
630  desc_ru: ''
631}, {
632  mask: '+241-#-##-##-##',
633  cc: 'GA',
634  name_en: 'Gabon',
635  desc_en: '',
636  name_ru: 'Габон',
637  desc_ru: ''
638}, {
639  mask: '+1(473)###-####',
640  cc: 'GD',
641  name_en: 'Grenada',
642  desc_en: '',
643  name_ru: 'Гренада',
644  desc_ru: ''
645}, {
646  mask: '+995(###)###-###',
647  cc: 'GE',
648  name_en: 'Rep. of Georgia',
649  desc_en: '',
650  name_ru: 'Грузия',
651  desc_ru: ''
652}, {
653  mask: '+594-#####-####',
654  cc: 'GF',
655  name_en: 'Guiana (French)',
656  desc_en: '',
657  name_ru: 'Фр. Гвиана',
658  desc_ru: ''
659}, {
660  mask: '+233(###)###-###',
661  cc: 'GH',
662  name_en: 'Ghana',
663  desc_en: '',
664  name_ru: 'Гана',
665  desc_ru: ''
666}, {
667  mask: '+350-###-#####',
668  cc: 'GI',
669  name_en: 'Gibraltar',
670  desc_en: '',
671  name_ru: 'Гибралтар',
672  desc_ru: ''
673}, {
674  mask: '+299-##-##-##',
675  cc: 'GL',
676  name_en: 'Greenland',
677  desc_en: '',
678  name_ru: 'Гренландия',
679  desc_ru: ''
680}, {
681  mask: '+220(###)##-##',
682  cc: 'GM',
683  name_en: 'Gambia',
684  desc_en: '',
685  name_ru: 'Гамбия',
686  desc_ru: ''
687}, {
688  mask: '+224-##-###-###',
689  cc: 'GN',
690  name_en: 'Guinea',
691  desc_en: '',
692  name_ru: 'Гвинея',
693  desc_ru: ''
694}, {
695  mask: '+240-##-###-####',
696  cc: 'GQ',
697  name_en: 'Equatorial Guinea',
698  desc_en: '',
699  name_ru: 'Экваториальная Гвинея',
700  desc_ru: ''
701}, {
702  mask: '+30(###)###-####',
703  cc: 'GR',
704  name_en: 'Greece',
705  desc_en: '',
706  name_ru: 'Греция',
707  desc_ru: ''
708}, {
709  mask: '+502-#-###-####',
710  cc: 'GT',
711  name_en: 'Guatemala',
712  desc_en: '',
713  name_ru: 'Гватемала',
714  desc_ru: ''
715}, {
716  mask: '+1(671)###-####',
717  cc: 'GU',
718  name_en: 'Guam',
719  desc_en: '',
720  name_ru: 'Гуам',
721  desc_ru: ''
722}, {
723  mask: '+245-#-######',
724  cc: 'GW',
725  name_en: 'Guinea-Bissau',
726  desc_en: '',
727  name_ru: 'Гвинея-Бисау',
728  desc_ru: ''
729}, {
730  mask: '+592-###-####',
731  cc: 'GY',
732  name_en: 'Guyana',
733  desc_en: '',
734  name_ru: 'Гайана',
735  desc_ru: ''
736}, {
737  mask: '+852-####-####',
738  cc: 'HK',
739  name_en: 'Hong Kong',
740  desc_en: '',
741  name_ru: 'Гонконг',
742  desc_ru: ''
743}, {
744  mask: '+504-####-####',
745  cc: 'HN',
746  name_en: 'Honduras',
747  desc_en: '',
748  name_ru: 'Гондурас',
749  desc_ru: ''
750}, {
751  mask: '+385-##-###-###',
752  cc: 'HR',
753  name_en: 'Croatia',
754  desc_en: '',
755  name_ru: 'Хорватия',
756  desc_ru: ''
757}, {
758  mask: '+509-##-##-####',
759  cc: 'HT',
760  name_en: 'Haiti',
761  desc_en: '',
762  name_ru: 'Гаити',
763  desc_ru: ''
764}, {
765  mask: '+36(###)###-###',
766  cc: 'HU',
767  name_en: 'Hungary',
768  desc_en: '',
769  name_ru: 'Венгрия',
770  desc_ru: ''
771}, {
772  mask: '+62(8##)###-####',
773  cc: 'ID',
774  name_en: 'Indonesia ',
775  desc_en: 'mobile',
776  name_ru: 'Индонезия ',
777  desc_ru: 'мобильные'
778}, {
779  mask: '+62-##-###-##',
780  cc: 'ID',
781  name_en: 'Indonesia',
782  desc_en: '',
783  name_ru: 'Индонезия',
784  desc_ru: ''
785}, {
786  mask: '+62-##-###-###',
787  cc: 'ID',
788  name_en: 'Indonesia',
789  desc_en: '',
790  name_ru: 'Индонезия',
791  desc_ru: ''
792}, {
793  mask: '+62-##-###-####',
794  cc: 'ID',
795  name_en: 'Indonesia',
796  desc_en: '',
797  name_ru: 'Индонезия',
798  desc_ru: ''
799}, {
800  mask: '+62(8##)###-###',
801  cc: 'ID',
802  name_en: 'Indonesia ',
803  desc_en: 'mobile',
804  name_ru: 'Индонезия ',
805  desc_ru: 'мобильные'
806}, {
807  mask: '+62(8##)###-##-###',
808  cc: 'ID',
809  name_en: 'Indonesia ',
810  desc_en: 'mobile',
811  name_ru: 'Индонезия ',
812  desc_ru: 'мобильные'
813}, {
814  mask: '+353(###)###-###',
815  cc: 'IE',
816  name_en: 'Ireland',
817  desc_en: '',
818  name_ru: 'Ирландия',
819  desc_ru: ''
820}, {
821  mask: '+972-5#-###-####',
822  cc: 'IL',
823  name_en: 'Israel ',
824  desc_en: 'mobile',
825  name_ru: 'Израиль ',
826  desc_ru: 'мобильные'
827}, {
828  mask: '+972-#-###-####',
829  cc: 'IL',
830  name_en: 'Israel',
831  desc_en: '',
832  name_ru: 'Израиль',
833  desc_ru: ''
834}, {
835  mask: '+91(####)###-###',
836  cc: 'IN',
837  name_en: 'India',
838  desc_en: '',
839  name_ru: 'Индия',
840  desc_ru: ''
841}, {
842  mask: '+246-###-####',
843  cc: 'IO',
844  name_en: 'Diego Garcia',
845  desc_en: '',
846  name_ru: 'Диего-Гарсия',
847  desc_ru: ''
848}, {
849  mask: '+964(###)###-####',
850  cc: 'IQ',
851  name_en: 'Iraq',
852  desc_en: '',
853  name_ru: 'Ирак',
854  desc_ru: ''
855}, {
856  mask: '+98(###)###-####',
857  cc: 'IR',
858  name_en: 'Iran',
859  desc_en: '',
860  name_ru: 'Иран',
861  desc_ru: ''
862}, {
863  mask: '+354-###-####',
864  cc: 'IS',
865  name_en: 'Iceland',
866  desc_en: '',
867  name_ru: 'Исландия',
868  desc_ru: ''
869}, {
870  mask: '+39(###)####-###',
871  cc: 'IT',
872  name_en: 'Italy',
873  desc_en: '',
874  name_ru: 'Италия',
875  desc_ru: ''
876}, {
877  mask: '+1(876)###-####',
878  cc: 'JM',
879  name_en: 'Jamaica',
880  desc_en: '',
881  name_ru: 'Ямайка',
882  desc_ru: ''
883}, {
884  mask: '+962-#-####-####',
885  cc: 'JO',
886  name_en: 'Jordan',
887  desc_en: '',
888  name_ru: 'Иордания',
889  desc_ru: ''
890}, {
891  mask: '+81-##-####-####',
892  cc: 'JP',
893  name_en: 'Japan ',
894  desc_en: 'mobile',
895  name_ru: 'Япония ',
896  desc_ru: 'мобильные'
897}, {
898  mask: '+81(###)###-###',
899  cc: 'JP',
900  name_en: 'Japan',
901  desc_en: '',
902  name_ru: 'Япония',
903  desc_ru: ''
904}, {
905  mask: '+254-###-######',
906  cc: 'KE',
907  name_en: 'Kenya',
908  desc_en: '',
909  name_ru: 'Кения',
910  desc_ru: ''
911}, {
912  mask: '+996(###)###-###',
913  cc: 'KG',
914  name_en: 'Kyrgyzstan',
915  desc_en: '',
916  name_ru: 'Киргизия',
917  desc_ru: ''
918}, {
919  mask: '+855-##-###-###',
920  cc: 'KH',
921  name_en: 'Cambodia',
922  desc_en: '',
923  name_ru: 'Камбоджа',
924  desc_ru: ''
925}, {
926  mask: '+686-##-###',
927  cc: 'KI',
928  name_en: 'Kiribati',
929  desc_en: '',
930  name_ru: 'Кирибати',
931  desc_ru: ''
932}, {
933  mask: '+269-##-#####',
934  cc: 'KM',
935  name_en: 'Comoros',
936  desc_en: '',
937  name_ru: 'Коморы',
938  desc_ru: ''
939}, {
940  mask: '+1(869)###-####',
941  cc: 'KN',
942  name_en: 'Saint Kitts & Nevis',
943  desc_en: '',
944  name_ru: 'Сент-Китс и Невис',
945  desc_ru: ''
946}, {
947  mask: '+850-191-###-####',
948  cc: 'KP',
949  name_en: 'DPR Korea (North) ',
950  desc_en: 'mobile',
951  name_ru: 'Корейская НДР ',
952  desc_ru: 'мобильные'
953}, {
954  mask: '+850-##-###-###',
955  cc: 'KP',
956  name_en: 'DPR Korea (North)',
957  desc_en: '',
958  name_ru: 'Корейская НДР',
959  desc_ru: ''
960}, {
961  mask: '+850-###-####-###',
962  cc: 'KP',
963  name_en: 'DPR Korea (North)',
964  desc_en: '',
965  name_ru: 'Корейская НДР',
966  desc_ru: ''
967}, {
968  mask: '+850-###-###',
969  cc: 'KP',
970  name_en: 'DPR Korea (North)',
971  desc_en: '',
972  name_ru: 'Корейская НДР',
973  desc_ru: ''
974}, {
975  mask: '+850-####-####',
976  cc: 'KP',
977  name_en: 'DPR Korea (North)',
978  desc_en: '',
979  name_ru: 'Корейская НДР',
980  desc_ru: ''
981}, {
982  mask: '+850-####-#############',
983  cc: 'KP',
984  name_en: 'DPR Korea (North)',
985  desc_en: '',
986  name_ru: 'Корейская НДР',
987  desc_ru: ''
988}, {
989  mask: '+82-##-###-####',
990  cc: 'KR',
991  name_en: 'Korea (South)',
992  desc_en: '',
993  name_ru: 'Респ. Корея',
994  desc_ru: ''
995}, {
996  mask: '+965-####-####',
997  cc: 'KW',
998  name_en: 'Kuwait',
999  desc_en: '',
1000  name_ru: 'Кувейт',
1001  desc_ru: ''
1002}, {
1003  mask: '+1(345)###-####',
1004  cc: 'KY',
1005  name_en: 'Cayman Islands',
1006  desc_en: '',
1007  name_ru: 'Каймановы острова',
1008  desc_ru: ''
1009}, {
1010  mask: '+7(6##)###-##-##',
1011  cc: 'KZ',
1012  name_en: 'Kazakhstan',
1013  desc_en: '',
1014  name_ru: 'Казахстан',
1015  desc_ru: ''
1016}, {
1017  mask: '+7(7##)###-##-##',
1018  cc: 'KZ',
1019  name_en: 'Kazakhstan',
1020  desc_en: '',
1021  name_ru: 'Казахстан',
1022  desc_ru: ''
1023}, {
1024  mask: '+856(20##)###-###',
1025  cc: 'LA',
1026  name_en: 'Laos ',
1027  desc_en: 'mobile',
1028  name_ru: 'Лаос ',
1029  desc_ru: 'мобильные'
1030}, {
1031  mask: '+856-##-###-###',
1032  cc: 'LA',
1033  name_en: 'Laos',
1034  desc_en: '',
1035  name_ru: 'Лаос',
1036  desc_ru: ''
1037}, {
1038  mask: '+961-##-###-###',
1039  cc: 'LB',
1040  name_en: 'Lebanon ',
1041  desc_en: 'mobile',
1042  name_ru: 'Ливан ',
1043  desc_ru: 'мобильные'
1044}, {
1045  mask: '+961-#-###-###',
1046  cc: 'LB',
1047  name_en: 'Lebanon',
1048  desc_en: '',
1049  name_ru: 'Ливан',
1050  desc_ru: ''
1051}, {
1052  mask: '+1(758)###-####',
1053  cc: 'LC',
1054  name_en: 'Saint Lucia',
1055  desc_en: '',
1056  name_ru: 'Сент-Люсия',
1057  desc_ru: ''
1058}, {
1059  mask: '+423(###)###-####',
1060  cc: 'LI',
1061  name_en: 'Liechtenstein',
1062  desc_en: '',
1063  name_ru: 'Лихтенштейн',
1064  desc_ru: ''
1065}, {
1066  mask: '+94-##-###-####',
1067  cc: 'LK',
1068  name_en: 'Sri Lanka',
1069  desc_en: '',
1070  name_ru: 'Шри-Ланка',
1071  desc_ru: ''
1072}, {
1073  mask: '+231-##-###-###',
1074  cc: 'LR',
1075  name_en: 'Liberia',
1076  desc_en: '',
1077  name_ru: 'Либерия',
1078  desc_ru: ''
1079}, {
1080  mask: '+266-#-###-####',
1081  cc: 'LS',
1082  name_en: 'Lesotho',
1083  desc_en: '',
1084  name_ru: 'Лесото',
1085  desc_ru: ''
1086}, {
1087  mask: '+370(###)##-###',
1088  cc: 'LT',
1089  name_en: 'Lithuania',
1090  desc_en: '',
1091  name_ru: 'Литва',
1092  desc_ru: ''
1093}, {
1094  mask: '+352(###)###-###',
1095  cc: 'LU',
1096  name_en: 'Luxembourg',
1097  desc_en: '',
1098  name_ru: 'Люксембург',
1099  desc_ru: ''
1100}, {
1101  mask: '+371-##-###-###',
1102  cc: 'LV',
1103  name_en: 'Latvia',
1104  desc_en: '',
1105  name_ru: 'Латвия',
1106  desc_ru: ''
1107}, {
1108  mask: '+218-##-###-###',
1109  cc: 'LY',
1110  name_en: 'Libya',
1111  desc_en: '',
1112  name_ru: 'Ливия',
1113  desc_ru: ''
1114}, {
1115  mask: '+218-21-###-####',
1116  cc: 'LY',
1117  name_en: 'Libya',
1118  desc_en: 'Tripoli',
1119  name_ru: 'Ливия',
1120  desc_ru: 'Триполи'
1121}, {
1122  mask: '+212-##-####-###',
1123  cc: 'MA',
1124  name_en: 'Morocco',
1125  desc_en: '',
1126  name_ru: 'Марокко',
1127  desc_ru: ''
1128}, {
1129  mask: '+377(###)###-###',
1130  cc: 'MC',
1131  name_en: 'Monaco',
1132  desc_en: '',
1133  name_ru: 'Монако',
1134  desc_ru: ''
1135}, {
1136  mask: '+377-##-###-###',
1137  cc: 'MC',
1138  name_en: 'Monaco',
1139  desc_en: '',
1140  name_ru: 'Монако',
1141  desc_ru: ''
1142}, {
1143  mask: '+373-####-####',
1144  cc: 'MD',
1145  name_en: 'Moldova',
1146  desc_en: '',
1147  name_ru: 'Молдова',
1148  desc_ru: ''
1149}, {
1150  mask: '+382-##-###-###',
1151  cc: 'ME',
1152  name_en: 'Montenegro',
1153  desc_en: '',
1154  name_ru: 'Черногория',
1155  desc_ru: ''
1156}, {
1157  mask: '+261-##-##-#####',
1158  cc: 'MG',
1159  name_en: 'Madagascar',
1160  desc_en: '',
1161  name_ru: 'Мадагаскар',
1162  desc_ru: ''
1163}, {
1164  mask: '+692-###-####',
1165  cc: 'MH',
1166  name_en: 'Marshall Islands',
1167  desc_en: '',
1168  name_ru: 'Маршалловы Острова',
1169  desc_ru: ''
1170}, {
1171  mask: '+389-##-###-###',
1172  cc: 'MK',
1173  name_en: 'Republic of Macedonia',
1174  desc_en: '',
1175  name_ru: 'Респ. Македония',
1176  desc_ru: ''
1177}, {
1178  mask: '+223-##-##-####',
1179  cc: 'ML',
1180  name_en: 'Mali',
1181  desc_en: '',
1182  name_ru: 'Мали',
1183  desc_ru: ''
1184}, {
1185  mask: '+95-##-###-###',
1186  cc: 'MM',
1187  name_en: 'Burma (Myanmar)',
1188  desc_en: '',
1189  name_ru: 'Бирма (Мьянма)',
1190  desc_ru: ''
1191}, {
1192  mask: '+95-#-###-###',
1193  cc: 'MM',
1194  name_en: 'Burma (Myanmar)',
1195  desc_en: '',
1196  n
1196ame_ru: 'Бирма (Мьянма)',
1197  desc_ru: ''
1198}, {
1199  mask: '+95-###-###',
1200  cc: 'MM',
1201  name_en: 'Burma (Myanmar)',
1202  desc_en: '',
1203  name_ru: 'Бирма (Мьянма)',
1204  desc_ru: ''
1205}, {
1206  mask: '+976-##-##-####',
1207  cc: 'MN',
1208  name_en: 'Mongolia',
1209  desc_en: '',
1210  name_ru: 'Монголия',
1211  desc_ru: ''
1212}, {
1213  mask: '+853-####-####',
1214  cc: 'MO',
1215  name_en: 'Macau',
1216  desc_en: '',
1217  name_ru: 'Макао',
1218  desc_ru: ''
1219}, {
1220  mask: '+1(670)###-####',
1221  cc: 'MP',
1222  name_en: 'Northern Mariana Islands',
1223  desc_en: '',
1224  name_ru: 'Северные Марианские острова Сайпан',
1225  desc_ru: ''
1226}, {
1227  mask: '+596(###)##-##-##',
1228  cc: 'MQ',
1229  name_en: 'Martinique',
1230  desc_en: '',
1231  name_ru: 'Мартиника',
1232  desc_ru: ''
1233}, {
1234  mask: '+222-##-##-####',
1235  cc: 'MR',
1236  name_en: 'Mauritania',
1237  desc_en: '',
1238  name_ru: 'Мавритания',
1239  desc_ru: ''
1240}, {
1241  mask: '+1(664)###-####',
1242  cc: 'MS',
1243  name_en: 'Montserrat',
1244  desc_en: '',
1245  name_ru: 'Монтсеррат',
1246  desc_ru: ''
1247}, {
1248  mask: '+356-####-####',
1249  cc: 'MT',
1250  name_en: 'Malta',
1251  desc_en: '',
1252  name_ru: 'Мальта',
1253  desc_ru: ''
1254}, {
1255  mask: '+230-###-####',
1256  cc: 'MU',
1257  name_en: 'Mauritius',
1258  desc_en: '',
1259  name_ru: 'Маврикий',
1260  desc_ru: ''
1261}, {
1262  mask: '+960-###-####',
1263  cc: 'MV',
1264  name_en: 'Maldives',
1265  desc_en: '',
1266  name_ru: 'Мальдивские острова',
1267  desc_ru: ''
1268}, {
1269  mask: '+265-1-###-###',
1270  cc: 'MW',
1271  name_en: 'Malawi',
1272  desc_en: 'Telecom Ltd',
1273  name_ru: 'Малави',
1274  desc_ru: 'Telecom Ltd'
1275}, {
1276  mask: '+265-#-####-####',
1277  cc: 'MW',
1278  name_en: 'Malawi',
1279  desc_en: '',
1280  name_ru: 'Малави',
1281  desc_ru: ''
1282}, {
1283  mask: '+52(###)###-####',
1284  cc: 'MX',
1285  name_en: 'Mexico',
1286  desc_en: '',
1287  name_ru: 'Мексика',
1288  desc_ru: ''
1289}, {
1290  mask: '+52-##-##-####',
1291  cc: 'MX',
1292  name_en: 'Mexico',
1293  desc_en: '',
1294  name_ru: 'Мексика',
1295  desc_ru: ''
1296}, {
1297  mask: '+60-##-###-####',
1298  cc: 'MY',
1299  name_en: 'Malaysia ',
1300  desc_en: 'mobile',
1301  name_ru: 'Малайзия ',
1302  desc_ru: 'мобильные'
1303}, {
1304  mask: '+60(###)###-###',
1305  cc: 'MY',
1306  name_en: 'Malaysia',
1307  desc_en: '',
1308  name_ru: 'Малайзия',
1309  desc_ru: ''
1310}, {
1311  mask: '+60-##-###-###',
1312  cc: 'MY',
1313  name_en: 'Malaysia',
1314  desc_en: '',
1315  name_ru: 'Малайзия',
1316  desc_ru: ''
1317}, {
1318  mask: '+60-#-###-###',
1319  cc: 'MY',
1320  name_en: 'Malaysia',
1321  desc_en: '',
1322  name_ru: 'Малайзия',
1323  desc_ru: ''
1324}, {
1325  mask: '+258-##-###-###',
1326  cc: 'MZ',
1327  name_en: 'Mozambique',
1328  desc_en: '',
1329  name_ru: 'Мозамбик',
1330  desc_ru: ''
1331}, {
1332  mask: '+264-##-###-####',
1333  cc: 'NA',
1334  name_en: 'Namibia',
1335  desc_en: '',
1336  name_ru: 'Намибия',
1337  desc_ru: ''
1338}, {
1339  mask: '+687-##-####',
1340  cc: 'NC',
1341  name_en: 'New Caledonia',
1342  desc_en: '',
1343  name_ru: 'Новая Каледония',
1344  desc_ru: ''
1345}, {
1346  mask: '+227-##-##-####',
1347  cc: 'NE',
1348  name_en: 'Niger',
1349  desc_en: '',
1350  name_ru: 'Нигер',
1351  desc_ru: ''
1352}, {
1353  mask: '+672-3##-###',
1354  cc: 'NF',
1355  name_en: 'Norfolk Island',
1356  desc_en: '',
1357  name_ru: 'Норфолк (остров)',
1358  desc_ru: ''
1359}, {
1360  mask: '+234(###)###-####',
1361  cc: 'NG',
1362  name_en: 'Nigeria',
1363  desc_en: '',
1364  name_ru: 'Нигерия',
1365  desc_ru: ''
1366}, {
1367  mask: '+234-##-###-###',
1368  cc: 'NG',
1369  name_en: 'Nigeria',
1370  desc_en: '',
1371  name_ru: 'Нигерия',
1372  desc_ru: ''
1373}, {
1374  mask: '+234-##-###-##',
1375  cc: 'NG',
1376  name_en: 'Nigeria',
1377  desc_en: '',
1378  name_ru: 'Нигерия',
1379  desc_ru: ''
1380}, {
1381  mask: '+234(###)###-####',
1382  cc: 'NG',
1383  name_en: 'Nigeria ',
1384  desc_en: 'mobile',
1385  name_ru: 'Нигерия ',
1386  desc_ru: 'мобильные'
1387}, {
1388  mask: '+505-####-####',
1389  cc: 'NI',
1390  name_en: 'Nicaragua',
1391  desc_en: '',
1392  name_ru: 'Никарагуа',
1393  desc_ru: ''
1394}, {
1395  mask: '+31-##-###-####',
1396  cc: 'NL',
1397  name_en: 'Netherlands',
1398  desc_en: '',
1399  name_ru: 'Нидерланды',
1400  desc_ru: ''
1401}, {
1402  mask: '+47(###)##-###',
1403  cc: 'NO',
1404  name_en: 'Norway',
1405  desc_en: '',
1406  name_ru: 'Норвегия',
1407  desc_ru: ''
1408}, {
1409  mask: '+977-##-###-###',
1410  cc: 'NP',
1411  name_en: 'Nepal',
1412  desc_en: '',
1413  name_ru: 'Непал',
1414  desc_ru: ''
1415}, {
1416  mask: '+674-###-####',
1417  cc: 'NR',
1418  name_en: 'Nauru',
1419  desc_en: '',
1420  name_ru: 'Науру',
1421  desc_ru: ''
1422}, {
1423  mask: '+683-####',
1424  cc: 'NU',
1425  name_en: 'Niue',
1426  desc_en: '',
1427  name_ru: 'Ниуэ',
1428  desc_ru: ''
1429}, {
1430  mask: '+64(###)###-###',
1431  cc: 'NZ',
1432  name_en: 'New Zealand',
1433  desc_en: '',
1434  name_ru: 'Новая Зеландия',
1435  desc_ru: ''
1436}, {
1437  mask: '+64-##-###-###',
1438  cc: 'NZ',
1439  name_en: 'New Zealand',
1440  desc_en: '',
1441  name_ru: 'Новая Зеландия',
1442  desc_ru: ''
1443}, {
1444  mask: '+64(###)###-####',
1445  cc: 'NZ',
1446  name_en: 'New Zealand',
1447  desc_en: '',
1448  name_ru: 'Новая Зеландия',
1449  desc_ru: ''
1450}, {
1451  mask: '+968-##-###-###',
1452  cc: 'OM',
1453  name_en: 'Oman',
1454  desc_en: '',
1455  name_ru: 'Оман',
1456  desc_ru: ''
1457}, {
1458  mask: '+507-###-####',
1459  cc: 'PA',
1460  name_en: 'Panama',
1461  desc_en: '',
1462  name_ru: 'Панама',
1463  desc_ru: ''
1464}, {
1465  mask: '+51(###)###-###',
1466  cc: 'PE',
1467  name_en: 'Peru',
1468  desc_en: '',
1469  name_ru: 'Перу',
1470  desc_ru: ''
1471}, {
1472  mask: '+689-##-##-##',
1473  cc: 'PF',
1474  name_en: 'French Polynesia',
1475  desc_en: '',
1476  name_ru: 'Французская Полинезия (Таити)',
1477  desc_ru: ''
1478}, {
1479  mask: '+675(###)##-###',
1480  cc: 'PG',
1481  name_en: 'Papua New Guinea',
1482  desc_en: '',
1483  name_ru: 'Папуа-Новая Гвинея',
1484  desc_ru: ''
1485}, {
1486  mask: '+63(###)###-####',
1487  cc: 'PH',
1488  name_en: 'Philippines',
1489  desc_en: '',
1490  name_ru: 'Филиппины',
1491  desc_ru: ''
1492}, {
1493  mask: '+92(###)###-####',
1494  cc: 'PK',
1495  name_en: 'Pakistan',
1496  desc_en: '',
1497  name_ru: 'Пакистан',
1498  desc_ru: ''
1499}, {
1500  mask: '+48(###)###-###',
1501  cc: 'PL',
1502  name_en: 'Poland',
1503  desc_en: '',
1504  name_ru: 'Польша',
1505  desc_ru: ''
1506}, {
1507  mask: '+970-##-###-####',
1508  cc: 'PS',
1509  name_en: 'Palestine',
1510  desc_en: '',
1511  name_ru: 'Палестина',
1512  desc_ru: ''
1513}, {
1514  mask: '+351-##-###-####',
1515  cc: 'PT',
1516  name_en: 'Portugal',
1517  desc_en: '',
1518  name_ru: 'Португалия',
1519  desc_ru: ''
1520}, {
1521  mask: '+680-###-####',
1522  cc: 'PW',
1523  name_en: 'Palau',
1524  desc_en: '',
1525  name_ru: 'Палау',
1526  desc_ru: ''
1527}, {
1528  mask: '+595(###)###-###',
1529  cc: 'PY',
1530  name_en: 'Paraguay',
1531  desc_en: '',
1532  name_ru: 'Парагвай',
1533  desc_ru: ''
1534}, {
1535  mask: '+974-####-####',
1536  cc: 'QA',
1537  name_en: 'Qatar',
1538  desc_en: '',
1539  name_ru: 'Катар',
1540  desc_ru: ''
1541}, {
1542  mask: '+262-#####-####',
1543  cc: 'RE',
1544  name_en: 'Reunion',
1545  desc_en: '',
1546  name_ru: 'Реюньон',
1547  desc_ru: ''
1548}, {
1549  mask: '+40-##-###-####',
1550  cc: 'RO',
1551  name_en: 'Romania',
1552  desc_en: '',
1553  name_ru: 'Румыния',
1554  desc_ru: ''
1555}, {
1556  mask: '+381-##-###-####',
1557  cc: 'RS',
1558  name_en: 'Serbia',
1559  desc_en: '',
1560  name_ru: 'Сербия',
1561  desc_ru: ''
1562}, {
1563  mask: '+7(###)###-##-##',
1564  cc: 'RU',
1565  name_en: 'Russia',
1566  desc_en: '',
1567  name_ru: 'Россия',
1568  desc_ru: ''
1569}, {
1570  mask: '+250(###)###-###',
1571  cc: 'RW',
1572  name_en: 'Rwanda',
1573  desc_en: '',
1574  name_ru: 'Руанда',
1575  desc_ru: ''
1576}, {
1577  mask: '+966-5-####-####',
1578  cc: 'SA',
1579  name_en: 'Saudi Arabia ',
1580  desc_en: 'mobile',
1581  name_ru: 'Саудовская Аравия ',
1582  desc_ru: 'мобильные'
1583}, {
1584  mask: '+966-#-###-####',
1585  cc: 'SA',
1586  name_en: 'Saudi Arabia',
1587  desc_en: '',
1588  name_ru: 'Саудовская Аравия',
1589  desc_ru: ''
1590}, {
1591  mask: '+677-###-####',
1592  cc: 'SB',
1593  name_en: 'Solomon Islands ',
1594  desc_en: 'mobile',
1595  name_ru: 'Соломоновы Острова ',
1596  desc_ru: 'мобильные'
1597}, {
1598  mask: '+677-#####',
1599  cc: 'SB',
1600  name_en: 'Solomon Islands',
1601  desc_en: '',
1602  name_ru: 'Соломоновы Острова',
1603  desc_ru: ''
1604}, {
1605  mask: '+248-#-###-###',
1606  cc: 'SC',
1607  name_en: 'Seychelles',
1608  desc_en: '',
1609  name_ru: 'Сейшелы',
1610  desc_ru: ''
1611}, {
1612  mask: '+249-##-###-####',
1613  cc: 'SD',
1614  name_en: 'Sudan',
1615  desc_en: '',
1616  name_ru: 'Судан',
1617  desc_ru: ''
1618}, {
1619  mask: '+46-##-###-####',
1620  cc: 'SE',
1621  name_en: 'Sweden',
1622  desc_en: '',
1623  name_ru: 'Швеция',
1624  desc_ru: ''
1625}, {
1626  mask: '+65-####-####',
1627  cc: 'SG',
1628  name_en: 'Singapore',
1629  desc_en: '',
1630  name_ru: 'Сингапур',
1631  desc_ru: ''
1632}, {
1633  mask: '+290-####',
1634  cc: 'SH',
1635  name_en: 'Saint Helena',
1636  desc_en: '',
1637  name_ru: 'Остров Святой Елены',
1638  desc_ru: ''
1639}, {
1640  mask: '+290-####',
1641  cc: 'SH',
1642  name_en: 'Tristan da Cunha',
1643  desc_en: '',
1644  name_ru: 'Тристан-да-Кунья',
1645  desc_ru: ''
1646}, {
1647  mask: '+386-##-###-###',
1648  cc: 'SI',
1649  name_en: 'Slovenia',
1650  desc_en: '',
1651  name_ru: 'Словения',
1652  desc_ru: ''
1653}, {
1654  mask: '+421(###)###-###',
1655  cc: 'SK',
1656  name_en: 'Slovakia',
1657  desc_en: '',
1658  name_ru: 'Словакия',
1659  desc_ru: ''
1660}, {
1661  mask: '+232-##-######',
1662  cc: 'SL',
1663  name_en: 'Sierra Leone',
1664  desc_en: '',
1665  name_ru: 'Сьерра-Леоне',
1666  desc_ru: ''
1667}, {
1668  mask: '+378-####-######',
1669  cc: 'SM',
1670  name_en: 'San Marino',
1671  desc_en: '',
1672  name_ru: 'Сан-Марино',
1673  desc_ru: ''
1674}, {
1675  mask: '+221-##-###-####',
1676  cc: 'SN',
1677  name_en: 'Senegal',
1678  desc_en: '',
1679  name_ru: 'Сенегал',
1680  desc_ru: ''
1681}, {
1682  mask: '+252-##-###-###',
1683  cc: 'SO',
1684  name_en: 'Somalia',
1685  desc_en: '',
1686  name_ru: 'Сомали',
1687  desc_ru: ''
1688}, {
1689  mask: '+252-#-###-###',
1690  cc: 'SO',
1691  name_en: 'Somalia',
1692  desc_en: '',
1693  name_ru: 'Сомали',
1694  desc_ru: ''
1695}, {
1696  mask: '+252-#-###-###',
1697  cc: 'SO',
1698  name_en: 'Somalia ',
1699  desc_en: 'mobile',
1700  name_ru: 'Сомали ',
1701  desc_ru: 'мобильные'
1702}, {
1703  mask: '+597-###-####',
1704  cc: 'SR',
1705  name_en: 'Suriname ',
1706  desc_en: 'mobile',
1707  name_ru: 'Суринам ',
1708  desc_ru: 'мобильные'
1709}, {
1710  mask: '+597-###-###',
1711  cc: 'SR',
1712  name_en: 'Suriname',
1713  desc_en: '',
1714  name_ru: 'Суринам',
1715  desc_ru: ''
1716}, {
1717  mask: '+211-##-###-####',
1718  cc: 'SS',
1719  name_en: 'South Sudan',
1720  desc_en: '',
1721  name_ru: 'Южный Судан',
1722  desc_ru: ''
1723}, {
1724  mask: '+239-##-#####',
1725  cc: 'ST',
1726  name_en: 'Sao Tome and Principe',
1727  desc_en: '',
1728  name_ru: 'Сан-Томе и Принсипи',
1729  desc_ru: ''
1730}, {
1731  mask: '+503-##-##-####',
1732  cc: 'SV',
1733  name_en: 'El Salvador',
1734  desc_en: '',
1735  name_ru: 'Сальвадор',
1736  desc_ru: ''
1737}, {
1738  mask: '+1(721)###-####',
1739  cc: 'SX',
1740  name_en: 'Sint Maarten',
1741  desc_en: '',
1742  name_ru: 'Синт-Маартен',
1743  desc_ru: ''
1744}, {
1745  mask: '+963-##-####-###',
1746  cc: 'SY',
1747  name_en: 'Syrian Arab Republic',
1748  desc_en: '',
1749  name_ru: 'Сирийская арабская республика',
1750  desc_ru: ''
1751}, {
1752  mask: '+268-##-##-####',
1753  cc: 'SZ',
1754  name_en: 'Swaziland',
1755  desc_en: '',
1756  name_ru: 'Свазиленд',
1757  desc_ru: ''
1758}, {
1759  mask: '+1(649)###-####',
1760  cc: 'TC',
1761  name_en: 'Turks & Caicos',
1762  desc_en: '',
1763  name_ru: 'Тёркс и Кайкос',
1764  desc_ru: ''
1765}, {
1766  mask: '+235-##-##-##-##',
1767  cc: 'TD',
1768  name_en: 'Chad',
1769  desc_en: '',
1770  name_ru: 'Чад',
1771  desc_ru: ''
1772}, {
1773  mask: '+228-##-###-###',
1774  cc: 'TG',
1775  name_en: 'Togo',
1776  desc_en: '',
1777  name_ru: 'Того',
1778  desc_ru: ''
1779}, {
1780  mask: '+66-##-###-####',
1781  cc: 'TH',
1782  name_en: 'Thailand ',
1783  desc_en: 'mobile',
1784  name_ru: 'Таиланд ',
1785  desc_ru: 'мобильные'
1786}, {
1787  mask: '+66-##-###-###',
1788  cc: 'TH',
1789  name_en: 'Thailand',
1790  desc_en: '',
1791  name_ru: 'Таиланд',
1792  desc_ru: ''
1793}, {
1794  mask: '+992-##-###-####',
1795  cc: 'TJ',
1796  name_en: 'Tajikistan',
1797  desc_en: '',
1798  name_ru: 'Таджикистан',
1799  desc_ru: ''
1800}, {
1801  mask: '+690-####',
1802  cc: 'TK',
1803  name_en: 'Tokelau',
1804  desc_en: '',
1805  name_ru: 'Токелау',
1806  desc_ru: ''
1807}, {
1808  mask: '+670-###-####',
1809  cc: 'TL',
1810  name_en: 'East Timor',
1811  desc_en: '',
1812  name_ru: 'Восточный Тимор',
1813  desc_ru: ''
1814}, {
1815  mask: '+670-77#-#####',
1816  cc: 'TL',
1817  name_en: 'East Timor',
1818  desc_en: 'Timor Telecom',
1819  name_ru: 'Восточный Тимор',
1820  desc_ru: 'Timor Telecom'
1821}, {
1822  mask: '+670-78#-#####',
1823  cc: 'TL',
1824  name_en: 'East Timor',
1825  desc_en: 'Timor Telecom',
1826  name_ru: 'Восточный Тимор',
1827  desc_ru: 'Timor Telecom'
1828}, {
1829  mask: '+993-#-###-####',
1830  cc: 'TM',
1831  name_en: 'Turkmenistan',
1832  desc_en: '',
1833  name_ru: 'Туркменистан',
1834  desc_ru: ''
1835}, {
1836  mask: '+216-##-###-###',
1837  cc: 'TN',
1838  name_en: 'Tunisia',
1839  desc_en: '',
1840  name_ru: 'Тунис',
1841  desc_ru: ''
1842}, {
1843  mask: '+676-#####',
1844  cc: 'TO',
1845  name_en: 'Tonga',
1846  desc_en: '',
1847  name_ru: 'Тонга',
1848  desc_ru: ''
1849}, {
1850  mask: '+90(###)###-####',
1851  cc: 'TR',
1852  name_en: 'Turkey',
1853  desc_en: '',
1854  name_ru: 'Турция',
1855  desc_ru: ''
1856}, {
1857  mask: '+1(868)###-####',
1858  cc: 'TT',
1859  name_en: 'Trinidad & Tobago',
1860  desc_en: '',
1861  name_ru: 'Тринидад и Тобаго',
1862  desc_ru: ''
1863}, {
1864  mask: '+688-90####',
1865  cc: 'TV',
1866  name_en: 'Tuvalu ',
1867  desc_en: 'mobile',
1868  name_ru: 'Тувалу ',
1869  desc_ru: 'мобильные'
1870}, {
1871  mask: '+688-2####',
1872  cc: 'TV',
1873  name_en: 'Tuvalu',
1874  desc_en: '',
1875  name_ru: 'Тувалу',
1876  desc_ru: ''
1877}, {
1878  mask: '+886-#-####-####',
1879  cc: 'TW',
1880  name_en: 'Taiwan',
1881  desc_en: '',
1882  name_ru: 'Тайвань',
1883  desc_ru: ''
1884}, {
1885  mask: '+886-####-####',
1886  cc: 'TW',
1887  name_en: 'Taiwan',
1888  desc_en: '',
1889  name_ru: 'Тайвань',
1890  desc_ru: ''
1891}, {
1892  mask: '+255-##-###-####',
1893  cc: 'TZ',
1894  name_en: 'Tanzania',
1895  desc_en: '',
1896  name_ru: 'Танзания',
1897  desc_ru: ''
1898}, {
1899  mask: '+380(##)###-##-##',
1900  cc: 'UA',
1901  name_en: 'Ukraine',
1902  desc_en: '',
1903  name_ru: 'Украина',
1904  desc_ru: ''
1905}, {
1906  mask: '+256(###)###-###',
1907  cc: 'UG',
1908  name_en: 'Uganda',
1909  desc_en: '',
1910  name_ru: 'Уганда',
1911  desc_ru: ''
1912}, {
1913  mask: '+44-##-####-####',
1914  cc: 'UK',
1915  name_en: 'United Kingdom',
1916  desc_en: '',
1917  name_ru: 'Великобритания',
1918  desc_ru: ''
1919}, {
1920  mask: '+598-#-###-##-##',
1921  cc: 'UY',
1922  name_en: 'Uruguay',
1923  desc_en: '',
1924  name_ru: 'Уругвай',
1925  desc_ru: ''
1926}, {
1927  mask: '+998-##-###-####',
1928  cc: 'UZ',
1929  name_en: 'Uzbekistan',
1930  desc_en: '',
1931  name_ru: 'Узбекистан',
1932  desc_ru: ''
1933}, {
1934  mask: '+39-6-698-#####',
1935  cc: 'VA',
1936  name_en: 'Vatican City',
1937  desc_en: '',
1938  name_ru: 'Ватикан',
1939  desc_ru: ''
1940}, {
1941  mask: '+1(784)###-####',
1942  cc: 'VC',
1943  name_en: 'Saint Vincent & the Grenadines',
1944  desc_en: '',
1945  name_ru: 'Сент-Винсент и Гренадины',
1946  desc_ru: ''
1947}, {
1948  mask: '+58(###)###-####',
1949  cc: 'VE',
1950  name_en: 'Venezuela',
1951  desc_en: '',
1952  name_ru: 'Венесуэла',
1953  desc_ru: ''
1954}, {
1955  mask: '+1(284)###-####',
1956  cc: 'VG',
1957  name_en: 'British Virgin Islands',
1958  desc_en: '',
1959  name_ru: 'Британские Виргинские острова',
1960  desc_ru: ''
1961}, {
1962  mask: '+1(340)###-####',
1963  cc: 'VI',
1964  name_en: 'US Virgin Islands',
1965  desc_en: '',
1966  name_ru: 'Американские Виргинские острова',
1967  desc_ru: ''
1968}, {
1969  mask: '+84-##-####-###',
1970  cc: 'VN',
1971  name_en: 'Vietnam',
1972  desc_en: '',
1973  name_ru: 'Вьетнам',
1974  desc_ru: ''
1975}, {
1976  mask: '+84(###)####-###',
1977  cc: 'VN',
1978  name_en: 'Vietnam',
1979  desc_en: '',
1980  name_ru: 'Вьетнам',
1981  desc_ru: ''
1982}, {
1983  mask: '+678-##-#####',
1984  cc: 'VU',
1985  name_en: 'Vanuatu ',
1986  desc_en: 'mobile',
1987  name_ru: 'Вануату ',
1988  desc_ru: 'мобильные'
1989}, {
1990  mask: '+678-#####',
1991  cc: 'VU',
1992  name_en: 'Vanuatu',
1993  desc_en: '',
1994  name_ru: 'Вануату',
1995  desc_ru: ''
1996}, {
1997  mask: '+681-##-####',
1998  cc: 'WF',
1999  name_en: 'Wallis and Futuna',
2000  desc_en: '',
2001  name_ru: 'Уоллис и Футуна',
2002  desc_ru: ''
2003}, {
2004  mask: '+685-##-####',
2005  cc: 'WS',
2006  name_en: 'Samoa',
2007  desc_en: '',
2008  name_ru: 'Самоа',
2009  desc_ru: ''
2010}, {
2011  mask: '+967-###-###-###',
2012  cc: 'YE',
2013  name_en: 'Yemen ',
2014  desc_en: 'mobile',
2015  name_ru: 'Йемен ',
2016  desc_ru: 'мобильные'
2017}, {
2018  mask: '+967-#-###-###',
2019  cc: 'YE',
2020  name_en: 'Yemen',
2021  desc_en: '',
2022  name_ru: 'Йемен',
2023  desc_ru: ''
2024}, {
2025  mask: '+967-##-###-###',
2026  cc: 'YE',
2027  name_en: 'Yemen',
2028  desc_en: '',
2029  name_ru: 'Йемен',
2030  desc_ru: ''
2031}, {
2032  mask: '+27-##-###-####',
2033  cc: 'ZA',
2034  name_en: 'South Africa',
2035  desc_en: '',
2036  name_ru: 'Южно-Африканская Респ.',
2037  desc_ru: ''
2038}, {
2039  mask: '+260-##-###-####',
2040  cc: 'ZM',
2041  name_en: 'Zambia',
2042  desc_en: '',
2043  name_ru: 'Замбия',
2044  desc_ru: ''
2045}, {
2046  mask: '+263-#-######',
2047  cc: 'ZW',
2048  name_en: 'Zimbabwe',
2049  desc_en: '',
2050  name_ru: 'Зимбабве',
2051  desc_ru: ''
2052}, {
2053  mask: '+1(###)###-####',
2054  cc: ['US', 'CA'],
2055  name_en: 'USA and Canada',
2056  desc_en: '',
2057  name_ru: 'США и Канада',
2058  desc_ru: ''
2059}];
2060const fileDrop_files = [{
2061  file_name: 'Resume_for_freelance1.PDF'
2062}, {
2063  file_name: 'Resume_for_freelance2.PDF'
2064}, {
2065  file_name: 'Resume_for_freelance3.PDF'
2066}, {
2067  file_name: 'Resume_for_freelance4.PDF'
2068}];
2069ko.bindingHandlers.maskedDate = {
2070  init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
2071    $(element).mask('99.99.9999', {
2072      autoclear: false
2073    });
2074  }
2075};
2076ko.bindingHandlers.numericInput = {
2077  init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
2078    $(element).mask('999999999', {
2079      autoclear: false,
2080      placeholder: ''
2081    });
2082  }
2083};
2084ko.bindingHandlers.setSearchStringDefault = {
2085  init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
2086    var val = $('#section_slug').val();
2087    if (val && val != '0') {
2088      m_search.chosenCategorySlug($('#section_slug').val());
2089      m_search.searchPlaceholder(m_search.placeholders[$('#section_slug').val()]);
2090    }
2091  }
2092};
2093ko.bindingHandlers.openSearchCategories = {
2094  init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
2095    ko.utils.registerEventHandler($(document), 'mouseup', function (e) {
2096      if (m_search.showCategories()) {
2097        if (!$(element).is(e.target) && $(element).has(e.target).length === 0) {
2098          valueAccessor()(false);
2099        }
2100      }
2101    });
2102  }
2103};
2104ko.bindingHandlers.validationCss = {
2105  init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
2106    ko.utils.registerEventHandler(element, 'focusout', function () {
2107      // NOTE: timeout добавлен из-за того что есть косяки небольшие с masked input'ом.
2108      setTimeout(function () {
2109        var value = allBindings.get('value');
2110        if (value.isValid()) {
2111          $(element).parent().attr('data-validation', 'success');
2112        } else {
2113          $(element).parent().attr('data-validation', 'error');
2114        }
2115      }, 1);
2116    });
2117  }
2118};
2119ko.bindingHandlers.scrollToBottom = {
2120  updating: true,
2121  init: function (element, valueAccessor, allBindingsAccessor) {
2122    var self = this;
2123    self.updating = true;
2124    ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
2125      $(element).off('scroll.ko.scrollHandler');
2126      self.updating = false;
2127    });
2128  },
2129  update: function (element, valueAccessor, allBindingsAccessor) {
2130    var props = allBindingsAccessor().scrollOptions;
2131    var offset = props.offset ? props.offset : '0';
2132    var loadFunc = props.loadFunc;
2133    var load = ko.utils.unwrapObservable(valueAccessor());
2134    var self = this;
2135    if (load) {
2136      element.style.display = '';
2137      $(element).on('scroll.ko.scrollHandler', function () {
2138        if ($(element).scrollTop() + $(element).innerHeight() >= $(element)[0].scrollHeight) {
2139          if (self.updating) {
2140            loadFunc();
2141            self.updating = false;
2142          }
2143        } else {
2144          self.updating = true;
2145        }
2146      });
2147    } else {
2148      $(element).off('scroll.ko.scrollHandler');
2149      self.updating = false;
2150    }
2151  }
2152};
2153ko.bindingHandlers.customSliderInit = {
2154  init: function (element, valueAccessor) {
2155    function init() {
2156      var value = valueAccessor();
2157      var track = $(element).parent().find('#' + value.class)[0];
2158      var leftBtn = $(element).siblings('.slider-btn_left');
2159      var rightBtn = $(element).siblings('.slider-btn_right');
2160      var currentScroll = 0;
2161      var children = $(element).children();
2162      var fullWidth = $(children[0]).outerWidth(true) + $(children[1]).outerWidth(true) * (children.length - 1);
2163      var cardMargin = 20;
2164      var recordBlockWidth = $(element).outerWidth() + cardMargin;
2165      var disabledClass = 'disabled';
2166      $(element).css('overflow', 'hidden');
2167      var widthWithControls = recordBlockWidth + 130;
2168      leftBtn.css('display', 'block');
2169      rightBtn.css('display', 'block');
2170      $(element).parent().css('overflow', 'visible');
2171      var isMobileStage = widthWithControls > window.innerWidth;
2172      if (recordBlockWidth >= fullWidth || isMobileStage) {
2173        leftBtn.css('display', 'none');
2174        rightBtn.css('display', 'none');
2175        $(element).parent().css('overflow', 'hidden');
2176        $(element).css('overflow', 'auto');
2177        return;
2178      }
2179      leftBtn.addClass(disabledClass);
2180      var currentDay = value.currentDay;
2181      leftBtn.on('click', function (e) {
2182        if (currentScroll > 0) {
2183          currentScroll = currentScroll - recordBlockWidth;
2184          $(track).animate({
2185            scrollLeft: currentScroll
2186          }, 300);
2187          if (currentScroll === 0) {
2188            leftBtn.addClass(disabledClass);
2189          }
2190          if (rightBtn.hasClass(disabledClass)) {
2191            rightBtn.removeClass(disabledClass);
2192          }
2193          currentDay().subtract(7, 'days');
2194          currentDay.valueHasMutated();
2195        }
2196      });
2197      rightBtn.on('click', function (e) {
2198        if (currentScroll + $(track).width() < track.scrollWidth) {
2199          currentScroll = currentScroll + recordBlockWidth;
2200          $(track).animate({
2201            scrollLeft: currentScroll
2202          }, 300);
2203          leftBtn.removeClass(disabledClass);
2204          if (currentScroll === 1170 + cardMargin) {
2205            leftBtn.removeClass(disabledClass);
2206          }
2207          if (currentScroll + $(track).width() >= track.scrollWidth) {
2208            rightBtn.addClass(disabledClass);
2209          }
2210          currentDay().add(7, 'days');
2211          currentDay.valueHasMutated();
2212        }
2213      });
2214    }
2215    init();
2216    window.addEventListener('resize', init);
2217  }
2218};
2219
2220ko.bindingHandlers.confirmedRecordsSlider = {
2221  update: function (element, valueAccessor) {
2222    var items = ko.unwrap(valueAccessor()) || [];
2223    var count = element.length;
2224
2225    if ($(element).hasClass('owl-carousel')) {
2226      $(element).trigger('destroy.owl.carousel');
2227      $(element).removeClass('owl-carousel');
2228    }
2229
2230    var $btns = $(element).parent().find('.slider-btns');
2231    if (count <= 3) {
2232      $btns.hide();
2233      return;
2234    }
2235
2236    $btns.show();
2237
2238    var owlOptions = {
2239      margin: 20,
2240      mouseDrag: true,
2241      dots: false,
2242      nav: true,
2243      navText: ['', ''],
2244      navClass: ['slider-btn slider-btn_left', 'slider-btn slider-btn_right'],
2245      navContainerClass: 'slider-btns',
2246      responsive: {
2247        0: { items: 1 },
2248        768: { items: 2 },
2249        1024: { items: 3 }
2250      }
2251    };
2252
2253    $(element).addClass('owl-carousel');
2254    $(element).owlCarousel(owlOptions);
2255  }
2256};
2257
2258
2259ko.bindingHandlers.clinicSliderInit = {
2260  init: function (element, valueAccessor) {
2261    function init() {
2262      var leftBtn = $(element).siblings('.clinic-slider-btn_left');
2263      var rightBtn = $(element).siblings('.clinic-slider-btn_right');
2264      var disabledClass = 'disabled';
2265
2266      setTimeout(function () {
2267        var children = $(element).children();
2268        var fullWidth = 0;
2269
2270        children.each(function () {
2271          fullWidth += $(this).outerWidth(true);
2272        });
2273
2274        var containerWidth = $(element).outerWidth();
2275        var isMobile = window.innerWidth <= 1175;
2276
2277        leftBtn.css('display', 'none');
2278        rightBtn.css('display', 'none');
2279        $(element).css({
2280          'overflow-x': '',
2281          'overflow-y': 'visible',
2282        });
2283
2284        if (isMobile) {
2285          $(element).css({
2286            'overflow-x': 'auto',
2287            'overflow-y': 'visible',
2288          });
2289          return;
2290        }
2291
2292        if (fullWidth <= containerWidth) {
2293          $(element).css({
2294            'overflow-x': 'hidden',
2295            'overflow-y': 'visible',
2296          });
2297          return;
2298        }
2299
2300        $(element).css({
2301          'overflow-x': 'hidden',
2302          'overflow-y': 'visible',
2303        });
2304        leftBtn.css('display', 'flex');
2305        rightBtn.css('display', 'flex');
2306
2307        function updateButtonsState() {
2308          var currentScroll = $(element).scrollLeft();
2309          var maxScroll = Math.max(0, element.scrollWidth - containerWidth);
2310
2311          var tolerance = 2;
2312
2313          if (currentScroll <= tolerance) {
2314            leftBtn.addClass(disabledClass);
2315          } else {
2316            leftBtn.removeClass(disabledClass);
2317          }
2318
2319          if (currentScroll >= maxScroll - tolerance) {
2320            rightBtn.addClass(disabledClass);
2321          } else {
2322            rightBtn.removeClass(disabledClass);
2323          }
2324        }
2325
2326        updateButtonsState();
2327
2328        var avgItemWidth = children.length > 0 ? fullWidth / children.length : containerWidth;
2329        var itemsToScroll = Math.max(2, Math.floor((containerWidth / avgItemWidth) * 0.7));
2330        var scrollStep = Math.min(avgItemWidth * itemsToScroll, containerWidth * 0.9);
2331
2332        leftBtn.off('click').on('click', function (e) {
2333          e.preventDefault();
2334          var currentScroll = $(element).scrollLeft();
2335          if (currentScroll > 0) {
2336            var newScroll = Math.max(0, currentScroll - scrollStep);
2337            $(element).animate(
2338              {
2339                scrollLeft: newScroll,
2340              },
2341              300,
2342              function () {
2343                updateButtonsState();
2344              },
2345            );
2346          }
2347        });
2348
2349        rightBtn.off('click').on('click', function (e) {
2350          e.preventDefault();
2351          var currentScroll = $(element).scrollLeft();
2352          var maxScroll = Math.max(0, element.scrollWidth - containerWidth);
2353          if (currentScroll < maxScroll) {
2354            var newScroll = Math.min(maxScroll, currentScroll + scrollStep);
2355            $(element).animate(
2356              {
2357                scrollLeft: newScroll,
2358              },
2359              300,
2360              function () {
2361                updateButtonsState();
2362              },
2363            );
2364          }
2365        });
2366
2367        $(element).on('scroll', function () {
2368          updateButtonsState();
2369        });
2370      }, 100);
2371    }
2372
2373    init();
2374    window.addEventListener('resize', function () {
2375      setTimeout(init, 100);
2376    });
2377  },
2378};
2379ko.bindingHandlers.styler = {
2380  //    init: function(element, valueAccessor) {
2381  //        // unwrapping to get updates
2382  //        if (Array.isArray(valueAccessor()) > 0) {
2383  //            valueAccessor().forEach(function(el) {
2384  //                ko.utils.unwrapObservable(el);
2385  //            })
2386  //        } else {
2387  //            ko.utils.unwrapObservable(valueAccessor());
2388  //        }
2389  //
2390  //        // resetting styled select
2391  //        $(element).styler('destroy').styler();
2392  //
2393  //    },
2394  update: function (element, valueAccessor) {
2395    // unwrapping to get updates
2396    if (Array.isArray(valueAccessor()) > 0) {
2397      valueAccessor().forEach(function (el) {
2398        ko.utils.unwrapObservable(el);
2399      });
2400    } else {
2401      ko.utils.unwrapObservable(valueAccessor());
2402    }
2403    // resetting styled select
2404    $(element).styler('destroy').styler();
2405  }
2406};
2407ko.bindingHandlers.disableMonth = {
2408  init: function (element, year) {
2409    if (year() == 'Всё время') {
2410      m_article.chosenMonth(0);
2411    }
2412    $(element).styler('destroy').styler();
2413  },
2414  update: function (element, year) {
2415    if (year() == 'Всё время') {
2416      m_article.chosenMonth(0);
2417    }
2418    $(element).styler('destroy').styler();
2419  }
2420};
2421// Carousel initialisation
2422ko.bindingHandlers.yaReachGoal = {
2423  init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
2424    var key = valueAccessor();
2425    $(element).on('click', function () {
2426      if (typeof yaCounter !== 'undefined') {
2427        yaCounter.reachGoal(key);
2428        console.log('yaReachGoal', key);
2429      } else {
2430        console.log('yaCounter not defined');
2431      }
2432    });
2433  }
2434};
2435ko.bindingHandlers.multiSelect = {
2436  update: function (element, valueAccessor) {
2437    ko.utils.unwrapObservable(valueAccessor());
2438    $(element).select2MultiCheckboxes({
2439      placeholder: 'Выберите клинику',
2440      width: 'auto',
2441      wrapClass: 'select-multiple-checkboxed',
2442      templateSelection: function (selected, total) {
2443        var label = '';
2444        if (Array.isArray(selected)) {
2445          if (selected.indexOf('Во всех филиалах') != -1) {
2446            label = 'Во всех филиалах';
2447          } else if (!selected.length) {
2448            label = 'Выберите клинику';
2449          } else {
2450            label = 'Выбрано ' + selected.length + ' из ' + total;
2451          }
2452        }
2453        return label;
2454      }
2455    });
2456  }
2457};
2458ko.bindingHandlers.optionsBind = {
2459  preprocess: function (value, key, addBinding) {
2460    addBinding('optionsAfterRender', 'function(option, item) { ko.bindingHandlers.optionsBind.applyBindings(option, item, ' + value + ') }');
2461  },
2462  applyBindings: function (option, item, bindings) {
2463    if (item !== undefined) {
2464      option.setAttribute('data-bind', bindings);
2465      ko.applyBindings(ko.contextFor(option).createChildContext(item), option);
2466    }
2467  }
2468};
2469ko.bindingHandlers.mobileSelectOpen = {
2470  init: function (element, valueAccessor) {
2471    $(element).on('click', function () {
2472      $(element).parent().css('z-index', 11);
2473      $(element).addClass('active');
2474      var activeItem = $(element).find('.item.active')[0];
2475      var oleft = activeItem.offsetLeft;
2476      $($(element).find('.items-wrapper')[0]).scrollLeft(oleft - 20);
2477    });
2478  }
2479};
2480ko.bindingHandlers.mobileSelectClose = {
2481  init: function (element, valueAccessor) {
2482    $(element).on('click', function () {
2483      event.stopPropagation();
2484      $(element).parent().find('.item.active').removeClass('active');
2485      $(element).addClass('active');
2486      $($(element).parent().parent()[0]).removeClass('active');
2487      $($(element).parent().parent().parent()[0]).css('z-index', 10);
2488    });
2489  }
2490};
2491ko.bindingHandlers.enterHandle = {
2492  init: function (element, value_accessor) {
2493    var fields = $(element).find("textarea, input[type!='checkbox'], input[type!='radio']");
2494    var button = $(element).find("button[type='submit']");
2495    for (var i = 0; i < fields.length; i++) {
2496      $(fields[i]).on('keydown', function (e) {
2497        if (e.keyCode === 13 && fields.index(this) + 1 >= fields.length) {
2498          button.click();
2499        } else if (e.keyCode === 13) {
2500          $(fields[fields.index(this) + 1]).focus();
2501        }
2502      });
2503    }
2504  }
2505};
2506ko.bindingHandlers.enterCallbackHandle = {
2507  init: function (element, valueAccessor, allBindings, viewModel) {
2508    const callback = valueAccessor();
2509    $(element).on('keydown', function (e) {
2510      if (e.keyCode === 13) {
2511        callback.call(viewModel);
2512        return false;
2513      }
2514      return true;
2515    });
2516  }
2517};
2518ko.bindingHandlers.showPasswordBtn = {
2519  init: function (element) {
2520    $(element).parent('.popup__row').addClass('popup__row-password-eye');
2521    $(element).on('click', function () {
2522      const $inputShow = $(element).siblings('input');
2523      if ($inputShow[0].type === 'password') {
2524        $inputShow[0].type = 'text';
2525      } else {
2526        $inputShow[0].type = 'password';
2527      }
2528    });
2529  }
2530};
2531ko.bindingHandlers.scrollIfFocus = {
2532  init: function (element, valueAccessor) {
2533    const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; // ios? [boolean]
2534    const params = ko.utils.unwrapObservable(valueAccessor());
2535    let elementStateType = 'focus';
2536    const $modalSelector = $(params.modalSelector); // какой блок скролим
2537    const hightScrollBonus = params.hightScrollBonus || 0; // на сколько px скролить
2538    const hightOffsetTop = element.offsetTop;
2539    switch (element.tagName) {
2540      case 'INPUT':
2541      case 'TEXTAREA':
2542      case 'SELECT':
2543        elementStateType = 'focus';
2544        break;
2545      default:
2546        elementStateType = 'click';
2547    }
2548    $(element).on(elementStateType, function () {
2549      if (iOS) {
2550        $modalSelector.scrollTop(hightOffsetTop);
2551      } else {
2552        $modalSelector.animate({
2553          scrollTop: hightOffsetTop + hightScrollBonus
2554        }, 300);
2555      }
2556    });
2557  }
2558};
2559ko.bindingHandlers.inputScrollTop = {
2560  init: function (element, value_accessor) {
2561    $(element).on('focus', function () {
2562      m_cabinet.keyboard.subscribe(function (value) {
2563        if (value.visible) {
2564          if (value.height > 0) {
2565            var h = element.offsetTop - 10;
2566            $($(element).parents(value_accessor())[0]).scrollTop(h);
2567          }
2568        }
2569      });
2570      if (m_cabinet.keyboard().visible) {
2571        var h = element.offsetTop - 10;
2572        $($(element).parents(value_accessor())[0]).scrollTop(h);
2573      }
2574    });
2575  }
2576};
2577ko.bindingHandlers.scrollCalendar = {
2578  init: function (element, value_accessor) {
2579    $(element).on('click', function () {
2580      if ($(element).hasClass('active')) {
2581        let parent = $(element).parents('.popup-appointment__inner')[0];
2582        if (m_cabinet.is_app()) {
2583          parent = $(element).parents('#recordModalNew')[0];
2584        }
2585        var h = $(parent).find(value_accessor())[0].offsetTop - 10;
2586        $(parent).animate({
2587          scrollTop: h
2588        }, 400);
2589      }
2590    });
2591  }
2592};
2593ko.bindingHandlers.searchTyping = {
2594  init: function (element, value_accessor) {
2595    $(element).on('focus', function () {
2596      m_record.search().results.subscribe(function (value) {
2597        var h = element.offsetTop - 10;
2598        $($(element).parents(value_accessor())[0]).scrollTop(h);
2599      });
2600    });
2601  }
2602};
2603ko.bindingHandlers.openLink = {
2604  init: function (element, value_accessor) {
2605    $(element).on('click', function () {
2606      window.open(value_accessor(), '_system');
2607    });
2608  }
2609};
2610ko.bindingHandlers.openSearch = {
2611  init: function (element, value_accessor) {
2612    var input = $(element).prev();
2613    $(element).on('click', function () {
2614      $(element).parents(value_accessor()).toggleClass('active');
2615      if ($(element).parents(value_accessor()).hasClass('active')) {
2616        input.focus();
2617        m_cabinet.keyboard.subscribe(function (value) {
2618          if (value.visible) {
2619            if (value.height > 0) {
2620              var h = $($(input).parents('.content')[0]).scrollTop() + input.offset().top - 70;
2621              $($(input).parents('.content')[0]).scrollTop(h);
2622            }
2623          }
2624        });
2625      }
2626    });
2627  }
2628};
2629ko.bindingHandlers.fadeVisible = {
2630  init: function (element, valueAccessor) {
2631    // Initially set the element to be instantly visible/hidden depen
2631ding on the value
2632    var value = valueAccessor();
2633    $(element).toggle(ko.unwrap(value)); // Use "unwrapObservable" so we can handle values that may or may not be observable
2634  },
2635  update: function (element, valueAccessor) {
2636    // Whenever the value subsequently changes, slowly fade the element in or out
2637    var value = valueAccessor();
2638    ko.unwrap(value) ? $(element).fadeIn() : $(element).fadeOut();
2639  }
2640};
2641ko.bindingHandlers.dropdown = {
2642  init: function (element, valueAccessor) {
2643    $(element).dropdown();
2644  }
2645};
2646ko.bindingHandlers.changingHeightBinding = {
2647  update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
2648    if ($(window).scrollTop() > 210) {
2649      $('.header_fixed').animate({
2650        top: 0
2651      }, 100);
2652    }
2653    $(window).scroll(function () {
2654      if ($(window).scrollTop() > 210) {
2655        $('.header_fixed').stop(true, false).animate({
2656          top: 0
2657        }, 100);
2658      } else {
2659        $('.header_fixed').stop(true, false).animate({
2660          top: -80
2661        }, 50);
2662      }
2663    });
2664    var hiddenList = '';
2665    var hiddenListLength = 0;
2666    var fixWidth = parseInt($('body').css('width')) - 370; // 160 размер дропдауна  + 210 размер кнопки ЛК
2667    var indexLastItem = $('.header_fixed .departments li').length - 1;
2668    $('.header_fixed .departments li').each(function (index) {
2669      var rightEdge = $(this).offset().left + parseInt($(this).css('width'));
2670      if (index === indexLastItem && hiddenListLength === 0) {
2671        fixWidth = fixWidth + 160;
2672      }
2673      if (rightEdge > fixWidth) {
2674        hiddenList += this.outerHTML;
2675        $(this).remove();
2676        hiddenListLength++;
2677      }
2678    });
2679    if (hiddenListLength > 0) {
2680      $('.header_fixed .departments').append($('<li class="header_fixed__dropdown"><a>+ ' + hiddenListLength + ' ' + helper.pluralize(hiddenListLength, ['направлений', 'направление', 'направления']) + '</a><ul>' + hiddenList + '</ul></li>'));
2681    }
2682  }
2683};
2684ko.bindingHandlers.stopPropagation = {
2685  init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
2686    $(element).click(function (event) {
2687      event.stopPropagation();
2688    });
2689  }
2690};
2691ko.bindingHandlers.pincodeValidator = {
2692  init: function (element, valueAccessor) {
2693    var input = '';
2694    var pinCode = -1;
2695    var checker = valueAccessor(); // pin checker function
2696    var dots = $(element).find('.dot');
2697    var numbers = $(element).find('.number');
2698    var deletBtn = $(element).find('.btn.delete-icon');
2699    $(numbers).each(function (index, number) {
2700      $(number).on('touchend', function () {
2701        if (input.length < 4) {
2702          input += $(number).data('number');
2703          $(dots[input.length - 1]).addClass('active');
2704        }
2705        if (input.length == 4) {
2706          function correct() {
2707            $(dots).each(function (index, dot) {
2708              $(dot).addClass('correct');
2709            });
2710          }
2711
2712          function wrong() {
2713            $(dots).each(function (index, dot) {
2714              $(dot).addClass('wrong');
2715            });
2716          }
2717
2718          function begin() {
2719            $(dots).each(function (index, dot) {
2720              $(dot).removeClass('wrong').removeClass('correct').removeClass('active');
2721            });
2722            input = '';
2723          }
2724
2725          function beginDelayed() {
2726            setTimeout(begin, 800);
2727          }
2728          // TODO should be configurable through value accessor
2729          var inputVal = parseInt(input);
2730          input = 'disabled';
2731          if (m_device_auth.pagePINMode() === 'createLocalAuth') {
2732            pinCode = inputVal;
2733            m_device_auth.pagePINMode('createLocalAuth2');
2734            beginDelayed();
2735          } else if (m_device_auth.pagePINMode() === 'createLocalAuth2') {
2736            if (pinCode === inputVal) {
2737              m_device_auth.pagePINNextStep() && m_device_auth.pagePINNextStep()(pinCode);
2738              beginDelayed();
2739            } else {
2740              wrong();
2741              m_device_auth.pagePINMode('pinIsNotMatched');
2742              setTimeout(function () {
2743                m_device_auth.pagePINMode('createLocalAuth');
2744                beginDelayed();
2745              }, 2000);
2746            }
2747          } else {
2748            checker(inputVal, function (state, remainingTrys) {
2749              if (state === 'correct') {
2750                correct();
2751                window.location.hash = 'home';
2752              } else if (state === 'incorrect') {
2753                wrong();
2754              } else if (state === 'auth_fail') {
2755                wrong();
2756                $('#popup__auth-fail').show();
2757              } else if (state === 'not_set') {
2758                wrong();
2759                setTimeout(m_device_auth.navigateIfTokenIsValid.bind(m_device_auth, 'auth'), 600);
2760              }
2761              beginDelayed();
2762            });
2763          }
2764        }
2765      });
2766    });
2767    $(deletBtn).on('touchend', function () {
2768      $(dots[input.length - 1]).removeClass('active');
2769      input = input.slice(0, -1);
2770    });
2771  }
2772};
2773ko.bindingHandlers.accordion = {
2774  update: function (element, valueAccessor, allBindings) {
2775    const isDynamic = allBindings().foreach;
2776    const { openFirst, autoCollapse, scroll, stopPropagation } = valueAccessor();
2777
2778    if (openFirst) {
2779      $(element).children('li').first().addClass('active');
2780      $(element).children('li').first().children('.accordion-body').slideToggle(0, 'swing', {});
2781    }
2782
2783    $(element).children('li').each(function () {
2784      const item = $(this);
2785      if (isDynamic) item.off('click');
2786      item.on('click', function (e) {
2787        if (!autoCollapse) {
2788          const openElem = $(element).children('li.active')[0];
2789          if (openElem && openElem !== item[0]) {
2790            $(openElem).removeClass('active');
2791            $(openElem).children('.accordion-body').slideUp(300, 'swing', {});
2792          }
2793        }
2794        item.toggleClass('active');
2795        item.children('.accordion-body').slideToggle(300, 'swing', () => {
2796          if (scroll) helper.scrollToElement(item);
2797        });
2798        stopPropagation && e.stopPropagation();
2799      });
2800    });
2801  }
2802};
2803
2804ko.bindingHandlers.focusOnAppResume = {
2805  elements: [],
2806  onresume: function () {
2807    var elements = this.elements;
2808    var removeNulls = false;
2809    for (var i = 0; i < elements.length; i++) {
2810      var el = elements[i];
2811      if (el.parentNode) {
2812        el.focus();
2813      } else {
2814        elements[i] = null;
2815        removeNulls = true;
2816      }
2817    }
2818    if (removeNulls) {
2819      this.elements = elements.filter(function (el) {
2820        return el != null;
2821      });
2822    }
2823  },
2824  started: false,
2825  add: function (element) {
2826    this.elements.push(element);
2827    if (!this.started) {
2828      document.addEventListener('resume', this.onresume.bind(this), false);
2829      this.started = true;
2830    }
2831  },
2832  init: function (element, valueAccessor) {
2833    ko.bindingHandlers.focusOnAppResume.add(element);
2834  }
2835};
2836ko.validation.init({
2837  registerExtenders: true,
2838  messagesOnModified: true,
2839  insertMessages: true,
2840  decorateInputElement: true,
2841  decorateElementOnModified: true,
2842  errorElementClass: 'validation-error',
2843  errorMessageClass: 'validationMessage',
2844  grouping: { deep: true, observable: true }
2845});
2846ko.validation.rules.checked = {
2847  validator: function (value, otherVal) {
2848    if (!value) {
2849      return false;
2850    }
2851    return true;
2852  }
2853};
2854ko.validation.rules.phoneMinLength = {
2855  validator: function (val, otherVal) {
2856    return val.replace(/\D+/g, '').length > 10;
2857  }
2858};
2859ko.validation.rules.phoneMinLengthOrNone = {
2860  validator: function (val, otherVal) {
2861    var phone = val.replace(/\D+/g, '');
2862    return phone.length > 10 || phone.length === 0;
2863  }
2864};
2865ko.validation.rules.phoneEqualsHack = {
2866  validator: function (val, otherVal) {
2867    if (typeof otherVal == 'function') {
2868      otherVal = otherVal();
2869    }
2870    val = val && val.match && val.match(/[0-9]/g).join('');
2871    otherVal = otherVal && otherVal.match && otherVal.match(/[0-9]/g).join('');
2872    return val == otherVal;
2873  }
2874};
2875ko.validation.rules.passwordEqualsHack = {
2876  validator: function (val, otherVal) {
2877    if (typeof otherVal == 'function') {
2878      otherVal = otherVal();
2879    }
2880    return val == otherVal;
2881  }
2882};
2883ko.validation.rules.passwordSymbols = {
2884  validator: function (val) {
2885    return /^(?=.*[0-9])(?=.*[a-zA-Z])([a-zA-Z0-9!@#$%^&*()_\-+]+)*$/.test('' + val + '');
2886  },
2887  message: 'В пароле могут использоваться только цифры, английские буквы и символы: !@#$%^&*()_-+'
2888};
2889ko.validation.rules.passwordCase = {
2890  validator: function (val) {
2891    return /^.*?[A-Z].*?$/.test('' + val + '');
2892  },
2893  message: 'Одна из букв должна быть заглавной'
2894};
2895ko.validation.rules.passwordLength = {
2896  validator: function (val) {
2897    return val.length > 5;
2898  },
2899  message: 'Пароль должен содержать минимум 6 символов'
2900};
2901ko.validation.rules.passwordCompareWithLogin = {
2902  validator: function (val, otherVal) {
2903    if (typeof otherVal == 'function') {
2904      otherVal = otherVal();
2905    }
2906    if (val.toLowerCase().indexOf(otherVal.toLowerCase()) === -1) {
2907      return true;
2908    } else {
2909      return false;
2910    }
2911  }
2912};
2913ko.validation.rules['phoneNumber'] = {
2914  validator: function (phoneNumber, params) {
2915    if (!phoneNumber) return params.allowEmpty === true;
2916    var digits = phoneNumber.replace(/\D/g, '');
2917    if (digits.length === 11 && digits[0] === '7') return true;
2918    return false;
2919  },
2920  message: 'Введите корректный номер телефона в формате +7 (XXX) XXX-XX-XX'
2921};
2922
2923
2924ko.validation.rules['required'].message = 'Обязательное поле';
2925ko.validation.rules['email'].message = 'Введите корректный адрес эл. почты';
2926
2927// Год раньше 1900 — опечатка (1099 вместо 1999), такое в Медиалог не отправляем.
2928ko.validation.rules['validPastDate'] = {
2929  validator: function (val) {
2930    if (!val) return true;
2931    var d = moment(val, 'DD.MM.YYYY', true);
2932    return d.isValid() && d.year() >= 1900 && d.isBefore(moment());
2933  },
2934  message: 'Введите корректную дату'
2935};
2936
2937ko.validation.rules['minAge'] = {
2938  validator: function (val, years) {
2939    if (!val) return true;
2940    var d = moment(val, 'DD.MM.YYYY', true);
2941    if (!d.isValid()) return true;
2942    return d.isSameOrBefore(moment().subtract(years, 'years'));
2943  },
2944  message: 'Возраст должен быть не менее {0} лет'
2945};
2946
2947ko.validation.registerExtenders();
2948ko.bindingHandlers.specialistImageOnError = {
2949  init: function (element) {
2950    $(element).on('error', function () {
2951      $(this).css('display', 'none');
2952      $(this).parent().addClass('empty');
2953    });
2954  }
2955};
2956ko.bindingHandlers.flatpickrDate = {
2957  init: function (element, valueAccessor) {
2958    const input = $(element).find('input');
2959    flatpickr(input, {
2960      dateFormat: 'd.m.Y',
2961      locale: 'ru',
2962      maxDate: valueAccessor(),
2963      appendTo: element,
2964      allowInput: false,
2965      disableMobile: true,
2966      static: true
2967    });
2968  }
2969};
2970ko.bindingHandlers.flatpickrDisableBefore = {
2971  init: function (element, valueAccessor) {
2972    const input = $(element).find('input');
2973    flatpickr(input, {
2974      dateFormat: 'd.m.Y',
2975      locale: 'ru',
2976      minDate: moment(new Date()).format('DD.MM.YYYY'),
2977      appendTo: element,
2978      allowInput: false,
2979      disableMobile: true,
2980      static: true
2981    });
2982  }
2983}
2984ko.bindingHandlers.flatpickrRange = {
2985  init: function (element) {
2986    flatpickr(element, {
2987      dateFormat: 'd M, Y',
2988      locale: 'ru',
2989      mode: 'range',
2990      static: true,
2991      wrap: true,
2992      position: 'below'
2993    });
2994  }
2995};
2996
2997/**
2998 * Превращает дефолтный числовой year-input flatpickr в нативный <select>
2999 * с диапазоном minYear..maxYear. Используется в onReady биндингов
3000 * flatpickrModern* — по дизайну год выбирается из дропдауна, а не стрелками.
3001 */
3002function _modernReplaceYearWithSelect(fpInstance, minYear, maxYear) {
3003  var monthsEl = fpInstance.calendarContainer.querySelector('.flatpickr-current-month');
3004  if (!monthsEl) return;
3005  var yearInput = monthsEl.querySelector('input.cur-year');
3006  if (!yearInput) return; // уже заменён или нет
3007
3008  var select = document.createElement('select');
3009  select.className = 'cur-year cur-year-select';
3010  for (var y = maxYear; y >= minYear; y--) {
3011    var opt = document.createElement('option');
3012    opt.value = String(y);
3013    opt.textContent = String(y);
3014    if (y === fpInstance.currentYear) opt.selected = true;
3015    select.appendChild(opt);
3016  }
3017  select.addEventListener('change', function () {
3018    fpInstance.changeYear(parseInt(this.value, 10));
3019  });
3020
3021  // Удаляем стрелки year (arrowUp/arrowDown)
3022  var wrapper = yearInput.closest('.numInputWrapper');
3023  if (wrapper && wrapper.parentNode) {
3024    wrapper.parentNode.replaceChild(select, wrapper);
3025  } else {
3026    yearInput.parentNode.replaceChild(select, yearInput);
3027  }
3028
3029  // При перелистывании месяцами стрелками — синкаем select
3030  fpInstance.config.onYearChange = (fpInstance.config.onYearChange || []).concat([function () {
3031    select.value = String(fpInstance.currentYear);
3032  }]);
3033}
3034
3035function _isModernMobileViewport() {
3036  return typeof window !== 'undefined' && window.matchMedia
3037    && window.matchMedia('(max-width: 767px)').matches;
3038}
3039
3040ko.bindingHandlers.flatpickrModernDate = {
3041  init: function (element) {
3042    var input = element.querySelector('input');
3043    if (!input) return;
3044    var trigger = element.querySelector('[data-pickr-trigger]');
3045    var mobile = _isModernMobileViewport();
3046    var maxYear = new Date().getFullYear();
3047    var minYear = maxYear - 100;
3048
3049    var instance = flatpickr(input, {
3050      dateFormat: 'd.m.Y',
3051      locale: 'ru',
3052      maxDate: 'today',
3053      appendTo: element,
3054      clickOpens: !mobile,
3055      disableMobile: true,
3056      static: true,
3057      monthSelectorType: 'dropdown',
3058      onReady: function (selectedDates, dateStr, fpInstance) {
3059        fpInstance.calendarContainer.classList.add('flatpickr-modern');
3060        _modernReplaceYearWithSelect(fpInstance, minYear, maxYear);
3061      }
3062    });
3063
3064    // flatpickr без allowInput делает input readonly — снимаем, чтобы работала
3065    // клавиатура. Парсинг ввода flatpickr при этом не перехватывает.
3066    input.removeAttribute('readonly');
3067
3068    if (trigger) {
3069      trigger.addEventListener('click', function (e) {
3070        e.preventDefault();
3071        e.stopPropagation();
3072        instance.open();
3073      });
3074    }
3075
3076    ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
3077      instance.destroy();
3078    });
3079  }
3080};
3081
3082ko.bindingHandlers.flatpickrModernPeriod = {
3083  init: function (element, valueAccessor) {
3084    var opts = ko.unwrap(valueAccessor()) || {};
3085    var startObs = opts.start;
3086    var endObs = opts.end;
3087    if (!ko.isObservable(startObs) || !ko.isObservable(endObs)) return;
3088
3089    var startInput = element.querySelector('[data-pickr="start"]');
3090    var endInput = element.querySelector('[data-pickr="end"]');
3091    if (!startInput || !endInput) return;
3092
3093    if (_isModernMobileViewport()) {
3094      _initModernPeriodMobile(element, startInput, endInput, startObs, endObs);
3095    } else {
3096      _initModernPeriodDesktop(element, startInput, endInput, startObs, endObs);
3097    }
3098  }
3099};
3100
3101
3102function _initModernPeriodDesktop(element, startInput, endInput, startObs, endObs) {
3103  var anchor = startInput.closest('.flatpickr-wrap') || element;
3104  var hidden = document.createElement('input');
3105  hidden.type = 'text';
3106  hidden.tabIndex = -1;
3107  hidden.setAttribute('aria-hidden', 'true');
3108  hidden.style.cssText = 'position:absolute;opacity:0;pointer-events:none;width:0;height:0;padding:0;border:0;';
3109  anchor.appendChild(hidden);
3110
3111  var format = function (d) { return moment(d).format('DD.MM.YYYY'); };
3112  var parse = function (s) {
3113    var m = moment(s, 'DD.MM.YYYY', true);
3114    return m.isValid() ? m.toDate() : null;
3115  };
3116
3117  var maxYear = new Date().getFullYear();
3118  var minYear = maxYear - 10;
3119  var syncing = false;
3120
3121  var instance = flatpickr(hidden, {
3122    mode: 'range',
3123    dateFormat: 'd.m.Y',
3124    locale: 'ru',
3125    maxDate: 'today',
3126    monthSelectorType: 'dropdown',
3127    disableMobile: true,
3128    static: true,
3129    appendTo: anchor,
3130    onReady: function (selectedDates, dateStr, fpInstance) {
3131      fpInstance.calendarContainer.classList.add('flatpickr-modern');
3132      _modernReplaceYearWithSelect(fpInstance, minYear, maxYear);
3133    },
3134    onChange: function (selectedDates) {
3135      syncing = true;
3136      try {
3137        if (selectedDates[0]) startObs(format(selectedDates[0]));
3138        if (selectedDates.length >= 2 && selectedDates[1]) {
3139          endObs(format(selectedDates[1]));
3140        } else if (selectedDates.length === 1) {
3141          endObs('');
3142          setTimeout(function () { endInput.focus(); }, 0);
3143        }
3144      } finally { syncing = false; }
3145    },
3146    onClose: function (selectedDates) {
3147      if (selectedDates.length === 1) {
3148        syncing = true;
3149        try { endObs(format(selectedDates[0])); } finally { syncing = false; }
3150      }
3151    }
3152  });
3153
3154  var openCalendar = function () { instance.open(); };
3155  startInput.addEventListener('focus', openCalendar);
3156  startInput.addEventListener('click', openCalendar);
3157  endInput.addEventListener('focus', openCalendar);
3158  endInput.addEventListener('click', openCalendar);
3159
3160  var pushToFlatpickr = function () {
3161    if (syncing) return;
3162    var s = parse(startObs());
3163    var e = parse(endObs());
3164    syncing = true;
3165    try {
3166      if (s && e) instance.setDate([s, e], false);
3167      else if (s) instance.setDate([s], false);
3168      else instance.clear(false);
3169    } finally { syncing = false; }
3170  };
3171  var startSub = startObs.subscribe(pushToFlatpickr);
3172  var endSub = endObs.subscribe(pushToFlatpickr);
3173
3174  ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
3175    instance.destroy();
3176    startSub.dispose();
3177    endSub.dispose();
3178  });
3179}
3180
3181function _initModernPeriodMobile(element, startInput, endInput, startObs, endObs) {
3182  var startWrap = startInput.closest('.flatpickr-wrap') || element;
3183  var endWrap = endInput.closest('.flatpickr-wrap') || element;
3184  var startTrigger = startWrap.querySelector('[data-pickr-trigger]');
3185  var endTrigger = endWrap.querySelector('[data-pickr-trigger]');
3186
3187  var format = function (d) { return moment(d).format('DD.MM.YYYY'); };
3188  var parse = function (s) {
3189    var m = moment(s, 'DD.MM.YYYY', true);
3190    return m.isValid() ? m.toDate() : null;
3191  };
3192
3193  var maxYear = new Date().getFullYear();
3194  var minYear = maxYear - 10;
3195  var syncing = false;
3196
3197  var commonOpts = function (anchor) {
3198    return {
3199      dateFormat: 'd.m.Y',
3200      locale: 'ru',
3201      maxDate: 'today',
3202      monthSelectorType: 'dropdown',
3203      disableMobile: true,
3204      static: true,
3205      clickOpens: false,
3206      appendTo: anchor,
3207      onReady: function (selectedDates, dateStr, fpInstance) {
3208        fpInstance.calendarContainer.classList.add('flatpickr-modern');
3209        _modernReplaceYearWithSelect(fpInstance, minYear, maxYear);
3210      }
3211    };
3212  };
3213
3214  var startFp = flatpickr(startInput, Object.assign(commonOpts(startWrap), {
3215    defaultDate: parse(startObs()),
3216    onChange: function (selectedDates) {
3217      if (syncing) return;
3218      syncing = true;
3219      try {
3220        if (selectedDates[0]) {
3221          startObs(format(selectedDates[0]));
3222          startFp.close();
3223          // Автопереход на «Конец периода» — открываем второй пикер + фокус.
3224          setTimeout(function () {
3225            endFp.open();
3226            endInput.focus();
3227          }, 0);
3228        } else {
3229          startObs('');
3230        }
3231      } finally { syncing = false; }
3232    }
3233  }));
3234
3235  var endFp = flatpickr(endInput, Object.assign(commonOpts(endWrap), {
3236    defaultDate: parse(endObs()),
3237    onChange: function (selectedDates) {
3238      if (syncing) return;
3239      syncing = true;
3240      try {
3241        if (selectedDates[0]) {
3242          endObs(format(selectedDates[0]));
3243          endFp.close();
3244        } else {
3245          endObs('');
3246        }
3247      } finally { syncing = false; }
3248    }
3249  }));
3250
3251  startInput.removeAttribute('readonly');
3252  endInput.removeAttribute('readonly');
3253
3254  if (startTrigger) {
3255    startTrigger.addEventListener('click', function (e) {
3256      e.preventDefault();
3257      e.stopPropagation();
3258      endFp.close();
3259      startFp.open();
3260    });
3261  }
3262  if (endTrigger) {
3263    endTrigger.addEventListener('click', function (e) {
3264      e.preventDefault();
3265      e.stopPropagation();
3266      startFp.close();
3267      endFp.open();
3268    });
3269  }
3270
3271  // Внешние изменения observable (preset-кнопки) → пушим в нужный пикер.
3272  var pushStart = function () {
3273    if (syncing) return;
3274    var d = parse(startObs());
3275    syncing = true;
3276    try {
3277      if (d) startFp.setDate(d, false);
3278      else startFp.clear(false);
3279    } finally { syncing = false; }
3280  };
3281  var pushEnd = function () {
3282    if (syncing) return;
3283    var d = parse(endObs());
3284    syncing = true;
3285    try {
3286      if (d) endFp.setDate(d, false);
3287      else endFp.clear(false);
3288    } finally { syncing = false; }
3289  };
3290  var startSub = startObs.subscribe(pushStart);
3291  var endSub = endObs.subscribe(pushEnd);
3292
3293  ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
3294    startFp.destroy();
3295    endFp.destroy();
3296    startSub.dispose();
3297    endSub.dispose();
3298  });
3299}
3300ko.bindingHandlers.changeOnInput = {
3301  init: function (element) {
3302    $(element).on('input', function () {
3303      $(element).change();
3304    });
3305  }
3306};
3307ko.bindingHandlers.nullChecked = {
3308  init: function (element, valueAccessor) {
3309    ko.bindingHandlers.checked.init(element, valueAccessor);
3310  },
3311  update: function (element, valueAccessor) {
3312    var value = ko.utils.unwrapObservable(valueAccessor());
3313    if (value == null) element.isChecked = true;
3314    ko.bindingHandlers.checked.update(element, function () {
3315      return value;
3316    });
3317  }
3318};
3319ko.bindingHandlers.departmentKnowMore = {
3320  init: function (element, valueAccessor) {
3321    var data = valueAccessor();
3322    $(element).on('click', function (e) {
3323      e.stopPropagation();
3324      // close all other modals
3325      $('.departments-section__popup').css('display', 'none');
3326      var id = '#department-popup-' + data.id;
3327      $(id).css('display', data.display);
3328      // close on #content click
3329      $('#content').on('click', function () {
3330        $(id).css('display', 'none');
3331        $('#content').off('click');
3332      });
3333      $(id).on('click', function (e) {
3334        e.stopPropagation();
3335      });
3336    });
3337  }
3338};
3339
3340ko.bindingHandlers.toggleBaseDropdown = {
3341  init: function (element) {
3342    $(element).on('click', function (event) {
3343      event.stopPropagation();
3344      // Hide all dropdowns and activate current
3345      $('[data-bind="toggleBaseDropdown"]').not(element).removeClass('active');
3346      const isActive = $(element).hasClass('active');
3347      if (!isActive) $(element).addClass('active');
3348      else $(element).removeClass('active');
3349    });
3350    // Hide dropdown on click outside off dropdown
3351    $('body').on('click', function () {
3352      $(element).removeClass('active');
3353    });
3354  }
3355};
3356
3357ko.bindingHandlers.toggleStrictDropdown = {
3358  init: function (element) {
3359    $(element).on('click', function (event) {
3360      event.stopPropagation();
3361      if (event.target !== element) return;
3362      $(element).toggleClass('active');
3363    });
3364    $('body').on('click', function () {
3365      if ($(element).hasClass('active')) {
3366        $(element).removeClass('active');
3367      }
3368    });
3369  }
3370};
3371
3372$(function () {
3373  const HIDE_TIMEOUT_TIME = 1000;
3374  const CLASSES = {
3375    OPENER: {
3376      ACTIVE: 'active',
3377      ID: 'header-bottom__navigation-text'
3378    },
3379    BODY: {
3380      ACTIVE: 'show-extra-nav compensate-for-scrollbar'
3381    },
3382    MENU: {
3383      ID: 'extra-navigation_type_closes-by-timeout'
3384    },
3385    CONTAINER: {
3386      ID: 'header-bottom__navigation'
3387    }
3388  };
3389  const jqueryHeaderTop = $('.header-top');
3390  const jqueryHeaderBottom = $('.header-bottom');
3391  const jqueryBody = $(document.body);
3392  const menuToOpenerMap = new Map();
3393
3394  let lastTickImportantElement = null;
3395  let activeMenuElement = null;
3396  let closeTimeoutId = null;
3397
3398  function getOpenerContainerByOpener(element) {
3399    return $(element).parent()[0];
3400  }
3401
3402  function open(menuElement) {
3403    const openerElement = getOpenerElementByMenu(menuElement);
3404    const openerContainerElement = getOpenerContainerByOpener(openerElement);
3405    $(openerContainerElement).addClass(CLASSES.OPENER.ACTIVE);
3406    $(menuElement).show();
3407    return menuElement;
3408  }
3409
3410  function close(menuElement) {
3411    const openerElement = getOpenerElementByMenu(menuElement);
3412    const openerContainerElement = getOpenerContainerByOpener(openerElement);
3413    $(openerContainerElement).removeClass(CLASSES.OPENER.ACTIVE);
3414    $(menuElement).hide();
3415  }
3416
3417  function unblurBody() {
3418    jqueryBody.removeClass(CLASSES.BODY.ACTIVE);
3419  }
3420
3421  function blurBody() {
3422    jqueryBody.addClass(CLASSES.BODY.ACTIVE);
3423  }
3424
3425  function getMenuElementByOpener(element) {
3426    const opensId = $(element).data('opens-id');
3427
3428    if (opensId !== undefined) {
3429      const menuElement = document.getElementById(opensId);
3430      menuToOpenerMap.set(menuElement, element);
3431      return menuElement;
3432    }
3433
3434    return $(element)
3435      .parent()
3436      .children(`.${CLASSES.MENU.ID}`)[0];
3437  }
3438
3439  function getOpenerElementByMenu(element) {
3440    const mappedElement = menuToOpenerMap.get(element);
3441    if (mappedElement !== undefined) return mappedElement;
3442
3443    return $(element)
3444      .parent()
3445      .children(`.${CLASSES.OPENER.ID}`)[0];
3446  }
3447
3448  const closeActiveAndUnblur = () => {
3449    unblurBody();
3450    close(activeMenuElement);
3451    activeMenuElement = null;
3452  };
3453
3454  window.ummcHeaderExtraNav = {
3455    closeActiveAndUnblur
3456  };
3457
3458  function onMouseMove(jqueryEvent) {
3459    const isCecutient = getCecucientCookieStatus();
3460    if (isCecutient) return;
3461
3462    const { originalEvent } = jqueryEvent;
3463    const { type, element } = getImportantTarget(originalEvent);
3464    lastTickImportantElement = element;
3465
3466    switch (type) {
3467      case 'opener':
3468        const openersMenuElement = getMenuElementByOpener(element);
3469        if (activeMenuElement !== null && activeMenuElement !== openersMenuElement) {
3470          close(activeMenuElement);
3471          activeMenuElement = open(openersMenuElement);
3472        } else if (activeMenuElement === null) {
3473          blurBody();
3474          activeMenuElement = open(openersMenuElement);
3475        }
3476        break;
3477      case 'menu':
3478        break;
3479      default:
3480        if (activeMenuElement !== null && closeTimeoutId === null) {
3481          closeTimeoutId = setTimeout(() => {
3482            if (lastTickImportantElement === null) {
3483              closeActiveAndUnblur();
3484            }
3485            closeTimeoutId = null;
3486          }, HIDE_TIMEOUT_TIME);
3487        }
3488        break;
3489    }
3490  }
3491
3492  function getImportantTarget(event) {
3493    let element = null;
3494    let type = null;
3495    const _path = event.path || event.composedPath();
3496
3497    for (const _element of _path) {
3498      if ($(_element).hasClass(CLASSES.MENU.ID)) {
3499        element = _element;
3500        type = 'menu';
3501        break;
3502      } else if ($(_element).hasClass(CLASSES.OPENER.ID)) {
3503        element = _element;
3504        type = 'opener';
3505        break;
3506      } else if ($(_element).hasClass(CLASSES.CONTAINER.ID)) {
3507        break;
3508      }
3509    }
3510
3511    return { type, element };
3512  }
3513
3514  ko.bindingHandlers.initExtraNav = {
3515    init: function () {
3516      jqueryBody.on('mousemove', onMouseMove);
3517    }
3518  };
3519});
3520
3521$(function () {
3522  function showMobileHeader() {
3523    $('.header_mobile').removeClass('header_mobile--hidden');
3524    $('.header_mobile').removeClass('header_mobile--hidden-notification');
3525    $('.blur-inner').removeClass('blur-inner--active');
3526  }
3527  ko.bindingHandlers.initMainSearch = {
3528    init: function () {
3529      if (m_record_common_model.isRecordPage()) return
3530      const isOriginPage = window.location.href === window.location.origin + '/';
3531      const searchBtn = $('body').find('.header-bottom__search-btn');
3532      const clearBtn = $('body').find('.header-search__input .icon-search-close');
3533      const isSearchPage = window.location.href.includes(window.location.origin + '/search/');
3534      $('body').addClass('show-main-search close-search');
3535      if (isOriginPage) {
3536        if (!helper.isMobile()) {
3537          const searchInput = $('body').find('.header-search input')[0];
3538          if (searchInput) searchInput.focus({ preventScroll: true });
3539        };
3540      }
3541      if (isSearchPage) {
3542        $('body').addClass('scroll-add-to-body');
3543      }
3544      $(searchBtn).on('click', function () {
3545        $('body').addClass('up close-search').removeClass('down');
3546        $('body').addClass('show-main-search');
3547        $('body').find('.header-search__input input').focus();
3548        window.scrollTo({
3549          top: 0,
3550        })
3551      });
3552      $(clearBtn).on('click', function () {
3553        if (helper.isMobile() || helper.isMobileContent()) {
3554          $('.header-search__search-results').hide();
3555          m_search.searchString('');
3556          document.body.classList.remove('show-mobile-search');
3557          showMobileHeader();
3558        } else {
3559          $('.header-search__search-results').hide();
3560          m_search.searchString('');
3561        }
3562      });
3563    },
3564    update: function () {
3565      $('.header-search__item-missing').insertAfter($('.header-search__search-results'));
3566    }
3567  };
3568  ko.bindingHandlers.upSearch = {
3569    init: function () {
3570      if (helper.isMobile()) {
3571        $('.header-search__input input[type="text"]').focusin(function () {
3572          $('.header_mobile').addClass('header_mobile--hidden');
3573          if ($('.notification-wrapper').hasClass('open')) {
3574            $('.header_mobile').addClass('header_mobile--hidden-notification');
3575          } else {
3576            $('.header_mobile').addClass('header_mobile--hidden');
3577
3578          }
3579          $('.blur-inner').addClass('blur-inner--active');
3580          $('.header-search').css('z-index', '998');
3581        });
3582
3583        $('.header-search__input input[type="text"]').focusout(function () {
3584          if (m_search.searchString().length < 3) {
3585            showMobileHeader();
3586          }
3587        });
3588      }
3589    }
3590  };
3591});
3592ko.bindingHandlers.initWatchOnScroll = {
3593  init: function (element) {
3594    const documentElement = document.documentElement;
3595    let previousScrollTop = documentElement.scrollTop;
3596    document.addEventListener('scroll', function (e) {
3597      const isSearchStringNotEmpty = m_search.searchString() !== '';
3598      const isScrollTopZero = documentElement.scrollTop === 0;
3599      const isScrollUp = previousScrollTop - documentElement.scrollTop > 0;
3600      if (m_record_common_model.isRecordPage()) return
3601      $('body').toggleClass('show-main-search', isScrollTopZero || isSearchStringNotEmpty);
3602      if (isScrollUp) {
3603        $(element).addClass('up').removeClass('down');
3604        if (!$('body').hasClass('close-search'))
3605          $('body').removeClass('show-main-search');
3606      } else if (!isSearchStringNotEmpty) {
3607        $(element).addClass('down').removeClass('up');
3608      }
3609      previousScrollTop = documentElement.scrollTop;
3610    })
3611  }
3612};
3613ko.bindingHandlers.animateNumber = {
3614  update: function (element, valueAccessor) {
3615    var step = null;
3616    var value = ko.unwrap(valueAccessor());
3617    var number = null;
3618    var symbol = '';
3619    if (typeof value === 'string') {
3620      var numberMatch = value.match(/[0-9]+/g);
3621      if (numberMatch != null) {
3622        number = numberMatch[0];
3623      }
3624      var symbolMatch = value.match(/[^0-9]/g);
3625      if (symbolMatch != null) {
3626        symbol = symbolMatch[0];
3627      }
3628      step = function (now, tween) {
3629        var target = $(tween.elem);
3630        target.text(Math.floor(now) + symbol);
3631      };
3632    } else {
3633      number = value;
3634      step = $.animateNumber.numberStepFactories.separator(' ');
3635    }
3636    $(element).animateNumber({
3637      number: number,
3638      numberStep: step
3639    }, {
3640      easing: 'swing',
3641      duration: 3000
3642    });
3643  }
3644};
3645
3646ko.bindingHandlers.sliderThreeItems = {
3647  init: function (element) {
3648    // Сравниваем pathname, а не весь href: с ?ysclid/?utm_* (переход из Яндекса
3649    // или рекламы) строгое сравнение с origin+'/' не срабатывало, главная
3650    // уезжала в десктопную карусель на 2 карточки вместо мобильного скролла.
3651    const isMainPage = window.location.pathname === '/';
3652    const isServicesSlider = $(element).hasClass('services-slider');
3653    // if (isMainPage && helper.isTablet()) return;
3654    if (helper.isTablet()) {
3655      $(element).children('.services-intro-card--desktop, .services-all-card').remove();
3656    }
3657    if (isMainPage && isServicesSlider && window.matchMedia('(max-width: 991px)').matches) {
3658      $(element).addClass('services-slider--scroll doctors-list');
3659      return;
3660    }
3661    const childCount = $(element).children().length;
3662    var owlOptions = {
3663      margin: 30,
3664      mouseDrag: childCount > 3,
3665      dots: false,
3666      nav: true,
3667      video: true,
3668      navText: ['', ''],
3669      navClass: ['slider-btn slider-btn_left', 'slider-btn slider-btn_right'],
3670      navContainerClass: 'slider-btns',
3671      responsive: {
3672        1: {
3673          items: 2,
3674          margin: 12
3675        },
3676        768: {
3677          items: 2
3678        },
3679        991: {
3680          items: 3
3681        }
3682      },
3683      rewind: true
3684    };
3685    $(element).owlCarousel(owlOptions).addClass('owl-carousel');
3686  }
3687};
3688
3689ko.bindingHandlers.sliderThreeItemsRoddom = {
3690  init: function (element) {
3691    const isMainPage = window.location.href === window.location.origin + '/';
3692    if (isMainPage && helper.isTablet()) return;
3693    const childCount = $(element).children().length;
3694
3695    // Функция для обновления позиции точек
3696    function updateDynamicDots(currentPage) {
3697      var $dots = $(element).find('.owl-dot');
3698      var $dotsContainer = $(element).find('.slider-dots');
3699      var totalDots = $dots.length;
3700      var maxVisibleDots = 4;
3701
3702      // Если точек <= 4, центрируем контейнер
3703      if (totalDots <= maxVisibleDots) {
3704        var containerWidth = 95; // ширина контейнера
3705        var dotsWidth = totalDots * 24; // 24px на точку (12px ширина + 12px margin)
3706        var centerOffset = (containerWidth - dotsWidth) / 2;
3707        $dots.css('transform', 'translateX(' + centerOffset + 'px)');
3708        return;
3709      }
3710
3711      // Вычисляем смещение (24px = ширина точки 12px + margin 12px)
3712      var offset = 0;
3713
3714      if (currentPage < 2) {
3715        offset = 0;
3716      } else if (currentPage >= totalDots - 2) {
3717        offset = -(totalDots - maxVisibleDots) * 24;
3718      } else {
3719        offset = -(currentPage - 1) * 24;
3720      }
3721
3722      // Двигаем точки
3723      $dots.css('transform', 'translateX(' + offset + 'px)');
3724    }
3725
3726    var owlOptions = {
3727      margin: 30,
3728      mouseDrag: childCount > 3,
3729      dots: true,
3730      dotsEach: Math.ceil($(element).children().length / 4),
3731      dotsClass: 'owl-dots slider-dots',
3732      dotClass: 'owl-dot slider-dot',
3733      nav: true,
3734      video: true,
3735      navText: ['<img src="/static/mobile_app/www/assets/images/roddom/vector.svg">', '<img src="/static/mobile_app/www/assets/images/roddom/vector.svg">'],
3736      navClass: ['slider-btn slider-btn_left', 'slider-btn slider-btn_right'],
3737      navContainerClass: 'slider-btns',
3738      responsive: {
3739        1: {
3740          items: 1
3741        },
3742        768: {
3743          items: 2
3744        },
3745        1120: {
3746          items: 3
3747        }
3748      },
3749      rewind: true,
3750      onInitialized: function(event) {
3751        var $dots = $(element).find('.owl-dots');
3752        var $navButtons = $(element).find('.slider-btns');
3753        $dots.detach().insertAfter($navButtons.find('.slider-btn_left'));
3754
3755        setTimeout(function() {
3756          updateDynamicDots(event.page.index);
3757        }, 50);
3758      },
3759      onChanged: function(event) {
3760        updateDynamicDots(event.page.index);
3761      }
3762    };
3763    $(element).owlCarousel(owlOptions).addClass('owl-carousel');
3764  }
3765};
3766
3767ko.bindingHandlers.sliderTwoItems = {
3768
3769  init: function (element) {
3770    var owl = $(element);
3771    owl.addClass('owl-carousel');
3772    var owlOptions = {
3773      margin: 30,
3774      mouseDrag: true,
3775      dots: false,
3776      nav: true,
3777      video: true,
3778      navText: ['', ''],
3779      navClass: ['slider-btn slider-btn_left', 'slider-btn slider-btn_right'],
3780      navContainerClass: 'slider-btns',
3781      items: 2,
3782      rewind: true,
3783      lazyLoad: true
3784    };
3785    owl.owlCarousel(owlOptions);
3786  }
3787};
3788
3789ko.bindingHandlers.sliderTwoItemsRoddom = {
3790  init: function (element) {
3791    var owl = $(element);
3792    owl.addClass('owl-carousel');
3793
3794    // Функция для обновления позиции точек
3795    function updateDynamicDots(currentPage) {
3796      var $dots = $(element).find('.owl-dot');
3797      var $dotsContainer = $(element).find('.slider-dots');
3798      var totalDots = $dots.length;
3799      var maxVisibleDots = 4;
3800
3801      // Если точек <= 4, центрируем контейнер
3802      if (totalDots <= maxVisibleDots) {
3803        var containerWidth = 95; // ширина контейнера
3804        var dotsWidth = totalDots * 24; // 24px на точку (12px ширина + 12px margin)
3805        var centerOffset = (containerWidth - dotsWidth) / 2;
3806        $dots.css('transform', 'translateX(' + centerOffset + 'px)');
3807        return;
3808      }
3809
3810      // Вычисляем смещение (24px = ширина точки 12px + margin 12px)
3811      var offset = 0;
3812
3813      if (currentPage < 2) {
3814        offset = 0;
3815      } else if (currentPage >= totalDots - 2) {
3816        offset = -(totalDots - maxVisibleDots) * 24;
3817      } else {
3818        offset = -(currentPage - 1) * 24;
3819      }
3820
3821      // Двигаем точки
3822      $dots.css('transform', 'translateX(' + offset + 'px)');
3823    }
3824
3825    var owlOptions = {
3826      mouseDrag: true,
3827      dots: true,
3828      dotsEach: 1,
3829      dotsClass: 'owl-dots slider-dots',
3830      dotClass: 'owl-dot slider-dot',
3831      nav: true,
3832      video: true,
3833      navText: ['<img src="/static/mobile_app/www/assets/images/roddom/vector.svg">', '<img src="/static/mobile_app/www/assets/images/roddom/vector.svg">'],
3834      navClass: ['slider-btn slider-btn_left', 'slider-btn slider-btn_right'],
3835      navContainerClass: 'slider-btns',
3836      responsive: {
3837        0: {
3838          items: 1,
3839          margin: 10
3840        },
3841        600: {
3842          items: 2,
3843          margin: 16
3844        },
3845        769: {
3846          items: 2,
3847          margin: 32
3848        }
3849      },
3850      rewind: true,
3851      lazyLoad: true,
3852      onInitialized: function(event) {
3853        var $dots = $(element).find('.owl-dots');
3854        var $navButtons = $(element).find('.slider-btns');
3855        $dots.detach().insertAfter($navButtons.find('.slider-btn_left'));
3856
3857        setTimeout(function() {
3858          updateDynamicDots(event.page.index);
3859        }, 50);
3860      },
3861      onChanged: function(event) {
3862        updateDynamicDots(event.page.index);
3863      }
3864    };
3865    owl.owlCarousel(owlOptions);
3866  }
3867};
3868
3869function startCarousel(owl) {
3870  owl.addClass('owl-carousel');
3871  let MobileMargin = helper.isMobile() ? -40 : 10;
3872  owl.owlCarousel({
3873    items: 1,
3874    margin: MobileMargin,
3875    nav: true,
3876    dots: false,
3877    navText: ['', ''],
3878    navClass: ['slider-btn slider-btn_left', 'slider-btn slider-btn_right'],
3879    navContainer: '#main-slider-btns',
3880    navContainerClass: 'slider-btns',
3881    mouseDrag: true,
3882    autoplay: true,
3883    autoplayTimeout: 5000,
3884    autoplayHoverPause: true,
3885    loop: true,
3886    center: true,
3887    lazyLoad: true,
3888    smartSpeed: 800
3889  });
3890
3891  // Reset autoplay timer on btn click
3892  owl.on('changed.owl.carousel', () => {
3893    owl.trigger('stop.owl.autoplay');
3894    owl.trigger('play.owl.autoplay');
3895  });
3896}
3897
3898function stopCarousel(owl) {
3899  owl.trigger('destroy.owl.carousel');
3900  owl.addClass('mobile');
3901}
3902
3903
3904ko.bindingHandlers.mainSlider = {
3905  init: function (element) {
3906    const owl = $(element);
3907    const detachedNews = $('.main-slider__slide--news').detach();
3908
3909    // Init
3910    $('.main-slider__slides').append(detachedNews);
3911    $('.owl-carousel').addClass('mobile');
3912    $('.main-slider__slide--news').detach();
3913    startCarousel(owl);
3914
3915  }
3916};
3917
3918
3919
3920ko.bindingHandlers.slideImage = {
3921  init: function (element, valueAccessor) {
3922    function setBackground() {
3923      const image = helper.isTablet() ?
3924        `url(media/${mobileImage})` :
3925        `url(media/${desktopImage})`;
3926      $(element).css('background-image', image);
3927    }
3928
3929    const desktopImage = valueAccessor().image;
3930    const mobileImage = valueAccessor().image_min;
3931    setBackground();
3932
3933    $(window).resize(function () {
3934      setBackground();
3935    });
3936  }
3937};
3938
3939ko.bindingHandlers.sliderOneItem = {
3940  init: function (element, valueAccessor) {
3941    var owl = $(element);
3942    owl.addClass('owl-carousel');
3943
3944    let customConfig;
3945    if (valueAccessor().isOnboarding) {
3946      customConfig = {
3947        navText: ['', 'Далее'],
3948        navClass: ['onboarding__btns-previous', 'onboarding__btns-next'],
3949        navContainerClass: 'slider-btns',
3950        loop: false
3951      };
3952    }
3953    const config = {
3954      items: 1,
3955      nav: true,
3956      dots: valueAccessor().dots,
3957      navText: ['', ''],
3958      dotClass: 'slider-dot',
3959      navClass: ['slider-btn slider-btn_left', 'slider-btn slider-btn_right'],
3960      navContainer: valueAccessor().navContainer,
3961      navContainerClass: valueAccessor().navContainerClass,
3962      dotsContainer: valueAccessor().dotsContainer,
3963      mouseDrag: true,
3964      autoplay: valueAccessor().autoplay,
3965      autoplayTimeout: 5000,
3966      autoplayHoverPause: valueAccessor().autoplay,
3967      loop: valueAccessor().loop,
3968      center: true,
3969      smartSpeed: 800,
3970      ...customConfig
3971    };
3972
3973    owl.owlCarousel(config);
3974
3975    // Reset autoplay timer on btn click
3976    if (valueAccessor().autoplay) {
3977      owl.on('changed.owl.carousel', () => {
3978        owl.trigger('stop.owl.autoplay');
3979        owl.trigger('play.owl.autoplay');
3980      });
3981    }
3982
3983    if (valueAccessor().isOnboarding) {
3984      owl.on('changed.owl.carousel', () => {
3985        if ($('.onboarding__btns-next').hasClass('disabled')) {
3986          if (window.location.href === window.location.origin + '/') {
3987            $('.onboarding__btns-next').text('Перейти к сайту');
3988          } else {
3989            $('.onboarding__btns-next').text('Перейти к сайту');
3990          }
3991          $('.onboarding__btns-next').on('click.closeModal', function () {
3992            helper.closeArcticModal();
3993          });
3994        } else {
3995          $('.onboarding__btns-next').text('Далее');
3996          $('.onboarding__btns-next').off('click.closeModal');
3997        }
3998      });
3999    }
4000  }
4001};
4002
4003ko.bindingHandlers.sliderOneItemRoddom = {
4004  init: function (element) {
4005    var owl = $(element);
4006    owl.addClass('owl-carousel');
4007
4008    // Функция для обновления позиции точек
4009    function updateDynamicDots(currentPage) {
4010      var $dots = $(element).find('.owl-dot');
4011      var $dotsContainer = $(element).find('.slider-dots');
4012      var totalDots = $dots.length;
4013      var maxVisibleDots = 4;
4014
4015      // Если точек <= 4, центрируем контейнер
4016      if (totalDots <= maxVisibleDots) {
4017        var containerWidth = 95; // ширина контейнера
4018        var dotsWidth = totalDots * 24; // 24px на точку (12px ширина + 12px margin)
4019        var centerOffset = (containerWidth - dotsWidth) / 2;
4020        $dots.css('transform', 'translateX(' + centerOffset + 'px)');
4021        return;
4022      }
4023
4024      // Вычисляем смещение (24px = ширина точки 12px + margin 12px)
4025      var offset = 0;
4026
4027      if (currentPage < 2) {
4028        offset = 0;
4029      } else if (currentPage >= totalDots - 2) {
4030        offset = -(totalDots - maxVisibleDots) * 24;
4031      } else {
4032        offset = -(currentPage - 1) * 24;
4033      }
4034
4035      // Двигаем точки
4036      $dots.css('transform', 'translateX(' + offset + 'px)');
4037    }
4038
4039    var owlOptions = {
4040      margin: 30,
4041      mouseDrag: true,
4042      dots: true,
4043      dotsClass: 'owl-dots slider-dots',
4044      dotClass: 'owl-dot slider-dot',
4045      nav: true,
4046      video: true,
4047      navText: ['<img src="/static/mobile_app/www/assets/images/roddom/vector.svg">', '<img src="/static/mobile_app/www/assets/images/roddom/vector.svg">'],
4048      navClass: ['slider-btn slider-btn_left', 'slider-btn slider-btn_right'],
4049      navContainerClass: 'slider-btns',
4050      responsive: {
4051        0: {
4052          items: 1
4053        }
4054      },
4055      rewind: true,
4056      lazyLoad: true,
4057      onInitialized: function(event) {
4058        var $dots = $(element).find('.owl-dots');
4059        var $navButtons = $(element).find('.slider-btns');
4060        $dots.detach().insertAfter($navButtons.find('.slider-btn_left'));
4061
4062        setTimeout(function() {
4063          updateDynamicDots(event.page.index);
4064        }, 50);
4065      },
4066      onChanged: function(event) {
4067        updateDynamicDots(event.page.index);
4068      }
4069    };
4070    owl.owlCarousel(owlOptions);
4071  }
4072};
4073
4074ko.bindingHandlers.doctorSlider = {
4075  init: function (element) {
4076    const owl = $(element);
4077    owl.addClass('owl-carousel');
4078    const config = {
4079      items: 1,
4080      nav: true,
4081      dots: false,
4082      navText: ['', ''],
4083      navClass: ['slider-btn slider-btn_left', 'slider-btn slider-btn_right'],
4084      navContainerClass: 'card-review__doctors-carousel-buttons',
4085      mouseDrag: true,
4086      autoplay: false,
4087      loop: false,
4088      center: true,
4089      smartSpeed: 800
4090    };
4091
4092    owl.owlCarousel(config);
4093  }
4094};
4095
4096ko.bindingHandlers.initResponsiveNavigation = {
4097  init: function (element) {
4098    var $list = $(element);
4099    var topNavs = $list.find('> .navigation-list__item');
4100
4101    function toggleAccordion($item, $siblings) {
4102      var isOpened = $item.hasClass('opened');
4103      $siblings.removeClass('opened');
4104      if (!isOpened) {
4105        $item.addClass('opened');
4106      }
4107    }
4108
4109    topNavs.each(function (index, navItem) {
4110      var $navItem = $(navItem);
4111      var $trigger = $navItem.find('> .navigation-list__item-btn-next, > .navigation-list__item-a-next');
4112
4113      $trigger.on('click', function (e) {
4114        e.preventDefault();
4115        toggleAccordion($navItem, topNavs);
4116      });
4117
4118      // nested items (e.g. link_groups inside Услуги и цены)
4119      $navItem.find('.navigation-list__item').each(function (_, nestedItem) {
4120        var $nestedItem = $(nestedItem);
4121        var $nestedSiblings = $nestedItem.siblings('.navigation-list__item');
4122        $nestedItem.find('> .navigation-list__item-btn-next, > .navigation-list__item-a-next')
4123          .on('click', function (e) {
4124            e.preventDefault();
4125            e.stopPropagation();
4126            toggleAccordion($nestedItem, $nestedSiblings);
4127          });
4128      });
4129    });
4130  }
4131};
4132function installFixedPhoneMask(element, observable, matrixFallback) {
4133  var digits = '';
4134  var maskUpdating = false;
4135  var skipNextInput = false;
4136  var supportsBeforeInput = 'onbeforeinput' in document.createElement('input');
4137
4138  function getMatrix() {
4139    return element.getAttribute('placeholder')
4140      || matrixFallback
4141      || window.PHONE_PLACEHOLDER
4142      || '+7(___)___-__-__';
4143  }
4144
4145  function getDef() {
4146    return getMatrix().replace(/\D/g, '');
4147  }
4148
4149  function setCursorPosition(pos, elem) {
4150    if (elem.setSelectionRange) {
4151      elem.setSelectionRange(pos, pos);
4152    } else if (elem.createTextRange) {
4153      var range = elem.createTextRange();
4154      range.collapse(true);
4155      range.moveEnd('character', pos);
4156      range.moveStart('character', pos);
4157      range.select();
4158    }
4159  }
4160
4161  function updateObservable(formatted) {
4162    if (!ko.isObservable(observable)) {
4163      return;
4164    }
4165    maskUpdating = true;
4166    observable(formatted);
4167    maskUpdating = false;
4168  }
4169
4170  // Индекс цифры, перед которой стоит курсор после последнего форматирования.
4171  var lastCaret = 0;
4172
4173  function formatDigits(val, eventType, caretIndex) {
4174    if (!val.length) {
4175      if (eventType === 'focus') {
4176        val = getDef();
4177      } else {
4178        element.value = '';
4179        digits = '';
4180        updateObservable('');
4181        return;
4182      }
4183    }
4184
4185    var matrix = getMatrix();
4186    var def = getDef();
4187    var i = 0;
4188    var formatted = matrix.replace(/./g, function (a) {
4189      if (/[_\d]/.test(a)) {
4190        return i < val.length ? val.charAt(i++) : '_';
4191      }
4192      return a;
4193    });
4194
4195    if (eventType === 'blur' && formatted.replace(/\D/g, '').length <= def.length) {
4196      element.value = '';
4197      digits = '';
4198      updateObservable('');
4199      return;
4200    }
4201
4202    element.value = formatted;
4203    digits = val;
4204
4205    if (eventType !== 'blur') {
4206      lastCaret = caretIndex === undefined ? val.length : caretIndex;
4207      setCursorPosition(caretAt(lastCaret), element);
4208    }
4209
4210    updateObservable(formatted);
4211  }
4212
4213  function getMaxDigits() {
4214    return slotPositions().length;
4215  }
4216
4217  // Позиции ячеек под цифры в маске: и кода страны, и «_».
4218  function slotPositions() {
4219    var positions = [];
4220    getMatrix().replace(/./g, function (a, index) {
4221      if (/[_\d]/.test(a)) {
4222        positions.push(index);
4223      }
4224      return a;
4225    });
4226    return positions;
4227  }
4228
4229  // Позиция курсора перед цифрой с индексом index (за последней — перед первой «_»).
4230  function caretAt(index) {
4231    var slots = slotPositions();
4232    return index < slots.length ? slots[index] : element.value.length;
4233  }
4234
4235  // Сколько набранных цифр стоит левее позиции pos.
4236  function digitIndexAt(pos) {
4237    var slots = slotPositions();
4238    var index = 0;
4239    while (index < slots.length && slots[index] < pos) {
4240      index++;
4241    }
4242    return Math.min(index, digits.length);
4243  }
4244
4245  // Выделение в цифрах. Код страны не редактируется — правка начинается после него.
4246  function selectedDigits() {
4247    var fixed = getDef().length;
4248    return {
4249      start: Math.max(fixed, digitIndexAt(element.selectionStart)),
4250      end: Math.max(fixed, digitIndexAt(element.selectionEnd))
4251    };
4252  }
4253
4254  // Курсор ставится куда угодно в набранном номере, но не в код страны и не дальше
4255  // первой пустой ячейки. Выделение не трогаем: его заменят или сотрут.
4256  function clampCursor() {
4257    if (document.activeElement !== element || element.selectionStart !== element.selectionEnd) {
4258      return;
4259    }
4260    var pos = Math.min(
4261      Math.max(element.selectionStart, caretAt(getDef().length)),
4262      caretAt(digits.length)
4263    );
4264    if (pos !== element.selectionStart) {
4265      setCursorPosition(pos, element);
4266    }
4267  }
4268
4269  // Вставленный целиком номер (+7…, 8…, 10 цифр) кладём в маску как есть:
4270  // иначе его код страны дописался бы к уже подставленному в поле.
4271  function wholeNumber(newChars) {
4272    var def = getDef();
4273    var body = getMaxDigits() - def.length;
4274    return newChars.length < body ? '' : def + newChars.slice(-body);
4275  }
4276
4277  // Ставит цифры на место выделения или у курсора.
4278  function insertDigits(newChars) {
4279    var whole = newChars.length > 1 ? wholeNumber(newChars) : '';
4280    if (whole) {
4281      formatDigits(whole.slice(0, getMaxDigits()), 'input');
4282      return;
4283    }
4284    var range = selectedDigits();
4285    var end = range.end;
4286    // В набранном целиком номере цифра у курсора заменяется, а не выталкивает последнюю.
4287    if (range.start === range.end && digits.length + newChars.length > getMaxDigits()) {
4288      end = Math.min(digits.length, range.start + newChars.length);
4289    }
4290    var combined = (digits.slice(0, range.start) + newChars + digits.slice(end)).slice(0, getMaxDigits());
4291    formatDigits(combined, 'input', Math.min(range.start + newChars.length, combined.length));
4292  }
4293
4294  // direction: -1 — Backspace (цифра левее курсора), 1 — Delete (правее).
4295  function deleteDigits(direction) {
4296    var range = selectedDigits();
4297    if (range.start === range.end) {
4298      if (direction < 0) {
4299        range.start = Math.max(getDef().length, range.start - 1);
4300      } else {
4301        range.end = Math.min(digits.length, range.end + 1);
4302      }
4303    }
4304    formatDigits(digits.slice(0, range.start) + digits.slice(range.end), 'input', range.start);
4305  }
4306
4307  function onKeydown(event) {
4308    if (event.key === 'Backspace' || event.key === 'Delete') {
4309      if (supportsBeforeInput) {
4310        return;
4311      }
4312      event.preventDefault();
4313      event.stopImmediatePropagation();
4314      deleteDigits(event.key === 'Backspace' ? -1 : 1);
4315      return;
4316    }
4317
4318    if (/^\d$/.test(event.key)) {
4319      if (supportsBeforeInput) {
4320        return;
4321      }
4322      event.preventDefault();
4323      event.stopImmediatePropagation();
4324      insertDigits(event.key);
4325      return;
4326    }
4327
4328    if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
4329      event.preventDefault();
4330      event.stopImmediatePropagation();
4331    }
4332  }
4333
4334  function onBeforeInput(event) {
4335    if (event.inputType === 'deleteContentBackward' || event.inputType === 'deleteContentForward') {
4336      event.preventDefault();
4337      event.stopImmediatePropagation();
4338      // Ввод при наборе через подсказки клавиатуры отменить нельзя: браузер всё
4339      // равно поменяет поле, и в onInput вернём нашу строку.
4340      skipNextInput = !event.cancelable;
4341      deleteDigits(event.inputType === 'deleteContentBackward' ? -1 : 1);
4342      return;
4343    }
4344
4345    if (
4346      event.inputType === 'insertText'
4347      || event.inputType === 'insertCompositionText'
4348      || event.inputType === 'insertFromPaste'
4349    ) {
4350      var text = event.data != null
4351        ? event.data
4352        : (event.dataTransfer ? event.dataTransfer.getData('text/plain') : '');
4353      var data = (text || '').replace(/\D/g, '');
4354      event.preventDefault();
4355      event.stopImmediatePropagation();
4356      skipNextInput = !event.cancelable;
4357      if (data) {
4358        insertDigits(data);
4359      }
4360    }
4361  }
4362
4363  function onInput(event) {
4364    event.stopImmediatePropagation();
4365    if (skipNextInput) {
4366      skipNextInput = false;
4367      formatDigits(digits, 'input', lastCaret);
4368      return;
4369    }
4370
4371    // Правка мимо обработчиков выше (автозаполнение, вырезание, старый браузер):
4372    // изменённый кусок — всё, что не совпало с прежними цифрами в начале и в конце.
4373    var val = element.value.replace(/\D/g, '');
4374    var head = 0;
4375    while (head < digits.length && head < val.length && digits[head] === val[head]) {
4376      head++;
4377    }
4378    var tail = 0;
4379    while (
4380      tail < digits.length - head
4381      && tail < val.length - head
4382      && digits[digits.length - 1 - tail] === val[val.length - 1 - tail]
4383    ) {
4384      tail++;
4385    }
4386    var inserted = val.slice(head, val.length - tail);
4387    var whole = inserted.length > 1 ? wholeNumber(inserted) : '';
4388    if (whole) {
4389      formatDigits(whole.slice(0, getMaxDigits()), 'input');
4390      return;
4391    }
4392    if (head < getDef().length) {
4393      formatDigits(digits, 'input', lastCaret);
4394      return;
4395    }
4396    var combined = (digits.slice(0, head) + inserted + digits.slice(digits.length - tail)).slice(0, getMaxDigits());
4397    formatDigits(combined, 'input', Math.min(head + inserted.length, combined.length));
4398  }
4399
4400  function onFocus() {
4401    if (!digits.length) {
4402      formatDigits('', 'focus');
4403    } else {
4404      requestAnimationFrame(clampCursor);
4405    }
4406  }
4407
4408  function onBlur() {
4409    formatDigits(digits, 'blur');
4410  }
4411
4412  function onCursorLock(event) {
4413    if (event.type === 'keyup') {
4414      var navKeys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'];
4415      if (navKeys.indexOf(event.key) === -1) {
4416        return;
4417      }
4418    }
4419    requestAnimationFrame(clampCursor);
4420  }
4421
4422  // capture: true — раньше textInput Knockout, иначе он восстанавливает строку
4423  element.addEventListener('keydown', onKeydown, true);
4424  element.addEventListener('beforeinput', onBeforeInput, true);
4425  element.addEventListener('input', onInput, true);
4426  element.addEventListener('focus', onFocus);
4427  element.addEventListener('blur', onBlur);
4428  element.addEventListener('click', onCursorLock);
4429  element.addEventListener('mouseup', onCursorLock);
4430  element.addEventListener('keyup', onCursorLock);
4431
4432  if (ko.isObservable(observable) && observable()) {
4433    digits = observable().replace(/\D/g, '');
4434    formatDigits(digits, 'input');
4435  }
4436
4437  if (ko.isObservable(observable)) {
4438    var subscription = observable.subscribe(function (newValue) {
4439      if (maskUpdating) {
4440        return;
4441      }
4442      var newDigits = (newValue || '').replace(/\D/g, '');
4443      if (newDigits !== digits) {
4444        formatDigits(newDigits, 'input');
4445      }
4446    });
4447    ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
4448      subscription.dispose();
4449      element.removeEventListener('keydown', onKeydown, true);
4450      element.removeEventListener('beforeinput', onBeforeInput, true);
4451      element.removeEventListener('input', onInput, true);
4452      element.removeEventListener('focus', onFocus);
4453      element.removeEventListener('blur', onBlur);
4454      element.removeEventListener('click', onCursorLock);
4455      element.removeEventListener('mouseup', onCursorLock);
4456      element.removeEventListener('keyup', onCursorLock);
4457    });
4458  } else {
4459    ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
4460      element.removeEventListener('keydown', onKeydown, true);
4461      element.removeEventListener('beforeinput', onBeforeInput, true);
4462      element.removeEventListener('input', onInput, true);
4463      element.removeEventListener('focus', onFocus);
4464      element.removeEventListener('blur', onBlur);
4465      element.removeEventListener('click', onCursorLock);
4466      element.removeEventListener('mouseup', onCursorLock);
4467      element.removeEventListener('keyup', onCursorLock);
4468    });
4469  }
4470}
4471
4472ko.bindingHandlers.maskedPhone = {
4473  init: function (element, valueAccessor, allBindings) {
4474    var observable = allBindings.get('textInput') || allBindings.get('value');
4475    installFixedPhoneMask(element, observable);
4476    if (!element.getAttribute('placeholder') && window.PHONE_PLACEHOLDER) {
4477      element.setAttribute('placeholder', window.PHONE_PLACEHOLDER);
4478    }
4479  }
4480};
4481ko.bindingHandlers.birthDateMask = {
4482  // input mask for birth date input field
4483  init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
4484    var options = valueAccessor() || {};
4485    var observable = allBindings.get('value');
4486    $(element).on('input', function (e) {
4487      var value = e.target.value;
4488      value = value.replace(/[^\d.]/g, '');
4489      var formatted = '';
4490      var digits = value.replace(/\./g, '');
4491      if (digits.length > 0) formatted += digits.substring(0, Math.min(2, digits.length));
4492      if (digits.length > 2) formatted += '.' + digits.substring(2, Math.min(4, digits.length));
4493      if (digits.length > 4) formatted += '.' + digits.substring(4, Math.min(8, digits.length));
4494      e.target.value = formatted;
4495      if (ko.isObservable(observable)) observable(formatted);
4496    });
4497    if (ko.isObservable(observable) && observable()) element.value = observable();
4498  }
4499};
4500ko.bindingHandlers.phoneMaskNew = {
4501  init: function (element, valueAccessor, allBindings) {
4502    var observable = allBindings.get('textInput') || allBindings.get('value');
4503    var matrixFallback = '+7 (___) ___-__-__';
4504    installFixedPhoneMask(element, observable, matrixFallback);
4505    if (!element.getAttribute('placeholder')) {
4506      element.setAttribute('placeholder', window.PHONE_PLACEHOLDER || matrixFallback);
4507    }
4508  }
4509};
4510function parseVideoId(url) {
4511  const regexRutube = /[http|https]+:\/\/(?:www\.|)rutube\.ru\/video\/([a-zA-Z0-9_\-]+)\//i;
4512  const regexYoutube = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=)([^#\&\?]*).*/;
4513  if (url.includes('youtube')) {
4514    return url.match(regexYoutube)[1]
4515  } else if (url.includes('rutube')) {
4516    return url.match(regexRutube)[1]
4517  }
4518}
4519ko.bindingHandlers.parseYoutubeVideoId = {
4520  init: function (element, valueAccessor) {
4521    const videoId = parseVideoId(ko.unwrap(valueAccessor()));
4522    if (ko.unwrap(valueAccessor()).includes('youtube')) {
4523      $(element).attr('src', 'https://www.youtube.com/embed/' + videoId);
4524    } else if (ko.unwrap(valueAccessor()).includes('rutube')) {
4525      $(element).attr('src', 'https://rutube.ru/play/embed/' + videoId);
4526    }
4527  }
4528};
4529ko.bindingHandlers.parseYoutubeVideoThumbnail = {
4530  init: function (element, valueAccessor) {
4531    const videoId = parseVideoId(ko.unwrap(valueAccessor()));
4532    $(element).attr('src', 'https://img.youtube.com/vi/' + videoId + '/mqdefault.jpg');
4533  }
4534};
4535ko.bindingHandlers.specialityCards = {
4536  init: function (element) {
4537    $('.specialists-card__wrap').has('.card-specialist--c').css('display', 'none');
4538    var adultGridClass = 'specialists-adult';
4539    var childGridClass = 'specialists-child';
4540    var activeButtonClass = 'toggle-button--active';
4541    var grid = $(element).find('.specialists-cards');
4542    $('.toggle-button').on('click', function () {
4543      var input = $(this).find('input');
4544      var value = input.attr('value');
4545      var cityCookie = document.cookie.match(new RegExp(
4546        "(?:^|; )" + "city".replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"
4547      ))
4548      var city = cityCookie ? cityCookie[1] : ((typeof window !== 'undefined' && window.DEFAULT_CITY_SLUG) ? window.DEFAULT_CITY_SLUG : 'ekaterinburg');
4549      if (value === 'adult') {
4550        grid.removeClass(childGridClass);
4551        $('.specialists-card__wrap').has('.card-specialist--a').css('display', 'block');
4552        $('.specialists-card__wrap').has('.card-specialist--c').css('display', 'none');
4553        grid.addClass(adultGridClass);
4554      } else {
4555        grid.removeClass(adultGridClass);
4556        $('.specialists-card__wrap').has('.card-specialist--a').css('display', 'none');
4557        $('.specialists-card__wrap').has('.card-specialist--c').css('display', 'block');
4558        grid.addClass(childGridClass);
4559        if (city == "ekaterinburg")
4560          $("#all-specialists").attr("href", "/speciality/?clinic=children");
4561      }
4562      $(element).find('.toggle-button').removeClass(activeButtonClass);
4563      $(this).addClass(activeButtonClass);
4564    });
4565  }
4566};
4567ko.bindingHandlers.toggleTabs = {
4568  init: function (element, valueAccessor) {
4569    var toggleBtns = $(element).find('.toggle-button');
4570    var toggleTabs = $(element).find('.toggle-tab');
4571    $(toggleBtns).on('click', function () {
4572      var value = $(this).find('input').attr('value');
4573      var tab = $(toggleTabs).filter(`[data-tab=${value}]`);
4574      if (tab) {
4575        $(tab).addClass('toggle-tab--active');
4576        $(tab).siblings().removeClass('toggle-tab--active');
4577        $(this).addClass('toggle-button--active');
4578        $(this).siblings().removeClass('toggle-button--active');
4579      }
4580    });
4581  }
4582};
4583ko.bindingHandlers.filterHeadProfession = {
4584  init: function (element, valueAccessor) {
4585    var professionText = ko.unwrap(valueAccessor());
4586    var a_professionText = professionText.split(',');
4587    var a_filteredText = a_professionText.filter(function (phrase, i) {
4588      return !(/заведующий/i).test(phrase);
4589    });
4590    element.innerHTML = a_filteredText.join(',');
4591  }
4592};
4593ko.bindingHandlers.stickyAsideInit = {
4594  init: function (element, valueAccessor) {
4595    function scrollHandler() {
4596      // Получаем таргеты здесь, т.к. они могли добавиться после инита
4597      const targets = $(element).find('.content-with-aside__target');
4598      const headerHeight = helper.getHeaderHeight();
4599      $(targets).each(function (index, target) {
4600        const isScrolled = $(target).offset().top - $(window).scrollTop() <= headerHeight + 100;
4601        if (isScrolled && !$(target).hasClass('active')) {
4602          $(element)
4603            .find(`.content-nav__${target.name}`).addClass('active')
4604            .siblings().removeClass('active');
4605        }
4606      });
4607    }
4608    // Следим за скроллом для своевременного перекрашивания меню с навигацией
4609    $(window).scroll(helper.throttle(scrollHandler, 100));
4610
4611    // Переопределяем клики по итемам меню
4612    $(element).find('.content-with-aside__navigation-list').on('click', function (e) {
4613      e.preventDefault();
4614      const targets = [...$(element).find('.content-with-aside__target')];
4615      const href = $(e.target).attr('href').substring(1);
4616      const target = targets.find((target) => target.name === href);
4617      helper.scrollToElement(target);
4618    });
4619  }
4620};
4621ko.bindingHandlers.customInput = {
4622  init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
4623    const index = bindingContext.$index();
4624    function handleKeyDown(e) {
4625      const inputFields = Array.from(document.querySelectorAll('.opt-input'));
4626      const currentIndex = inputFields.indexOf(element);
4627      const allowedKeys = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'];
4628
4629      if (!/^\d$/.test(e.key) && !allowedKeys.includes(e.key)) {
4630        e.preventDefault();
4631        return;
4632      }
4633      if (e.key === 'Backspace') {
4634        bindingContext.$parent.smsValidation.showError(false);
4635        if (currentIndex > 0) {
4636          e.preventDefault();
4637          if (element.value !== '') {
4638            const vm = bindingContext.$parent.smsValidation;
4639            if (vm && vm.updateCode) vm.updateCode(currentIndex, '');
4640          }
4641          const prevInput = inputFields[currentIndex - 1];
4642          prevInput.focus();
4643          const length = prevInput.value.length;
4644          prevInput.setSelectionRange(length, length);
4645        } else if (currentIndex === 0 && element.value !== '') {
4646          const vm = bindingContext.$parent.smsValidation;
4647          if (vm && vm.updateCode) vm.updateCode(currentIndex, '');
4648        }
4649      }
4650      else if (e.key === 'ArrowLeft') {
4651        if (currentIndex > 0) {
4652          e.preventDefault();
4653          const prevInput = inputFields[currentIndex - 1];
4654          prevInput.focus();
4655          const length = prevInput.value.length;
4656          prevInput.setSelectionRange(length, length);
4657        }
4658      }
4659      else if (e.key === 'ArrowRight') {
4660        if (currentIndex < inputFields.length - 1) {
4661          e.preventDefault();
4662          const nextInput = inputFields[currentIndex + 1];
4663          nextInput.focus();
4664          nextInput.setSelectionRange(0, 0);
4665        }
4666      }
4667      else if (/^\d$/.test(e.key) && !e.ctrlKey && !e.altKey && !e.metaKey) {
4668        if (element.value !== '' && currentIndex < inputFields.length - 1) {
4669          e.preventDefault();
4670          const nextInput = inputFields[currentIndex + 1];
4671          nextInput.focus();
4672          const vm = bindingContext.$parent.smsValidation;
4673          if (vm && vm.updateCode) vm.updateCode(currentIndex + 1, e.key);
4674        }
4675      }
4676    }
4677    element.addEventListener('keydown', handleKeyDown);
4678    ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
4679      element.removeEventListener('keydown', handleKeyDown);
4680    });
4681    return { controlsDescendantBindings: false };
4682  }
4683};
4684
4685ko.bindingHandlers.contentParserInit = {
4686  init: function (element, valueAccessor) {
4687    // Удаление строк, которые использовались как отступы
4688    $(element).find('p').each(function () {
4689      if ($(this).html() === '&nbsp;') $(this).remove();
4690    });
4691    // Удаление бокового навбара, если в тексте не более 1 заголовка
4692    if ($(element).find('h2').length <= 1) {
4693      $('.content-with-aside__aside').remove();
4694      return;
4695    }
4696    // Если в тексте нет заголовка - добавлять заголовок 'описание'
4697    if ($(element).children().get(0).nodeName !== 'H2' && location.pathname !== '/psychology/') {
4698      $(element).prepend(`<h2 class="content-block__title">Описание</h2>`);
4699    }
4700    // Парсинг заголовков и добавление в навбар
4701    $(element).find('h2').addClass('content-block__title');
4702    $(element).find('h2').each(function (index) {
4703      // Оборачивание заголовков и текста в section
4704      $(this).nextUntil($(element).find('h2')).addBack().wrapAll('<section class="content-block" />');
4705      // Добавление ссылок для навбара
4706      $(`<a name="target-${index}" class="content-with-aside__target"></a>`).insertBefore($(this).parent());
4707      // Добавление ссылок в навбар
4708      const title = $(this).text().replace(/:$/, '');
4709      $('.navigation-list-to-fill').before(`
4710      <li class="content-with-aside__navigation-item content-nav__target-${index}">
4711      <a href="#target-${index}">${title}</a>
4712      </li>
4713      `);
4714    });
4715    $('.navigation-list-to-fill').remove();
4716  }
4717};
4718ko.bindingHandlers.initSpecialistsPartialListFromGlobal = {
4719  init: function () {
4720    m_specialists.init_from_global();
4721  }
4722};
4723ko.bindingHandlers.alphabetScrollInit = {
4724  init: function (element, valueAccessor) {
4725    const letters = $(element).find('.letters-list');
4726    const targets = $(element).find('.alphabetical-specialists__group-letter');
4727    $(letters).find('.letters-list__item').on('click', function (e) {
4728      e.preventDefault();
4729      const href = $(e.target).attr('href').substring(1);
4730      const target = $.grep(targets, function (t) {
4731        return t.name === href;
4732      });
4733      const lettersListHeight = $('.letters-list').outerHeight();
4734      helper.scrollToElement(target, lettersListHeight + 115);
4735    });
4736  }
4737};
4738/**
4739 * Биндит на input стили и вставку файлов в указанный параметром массив
4740 *
4741 * @param {object} array Ссылка на массив куда складывать файлы
4742 */
4743ko.bindingHandlers.fileDrop = {
4744  init: function (element, valueAccessor) {
4745    const params = ko.utils.unwrapObservable(valueAccessor());
4746    const koArray = params.array; // в какой массив добавлять файлы
4747    // Hover эффект
4748    const $dropArea = $(element);
4749    const $dropCss = $dropArea.parent('.base-filedrop');
4750    ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
4751      $dropArea[0].addEventListener(eventName, preventDefaults, false);
4752    });
4753
4754    function preventDefaults(event) {
4755      event.preventDefault();
4756      event.stopPropagation();
4757    }
4758    ['dragenter', 'dragover'].forEach(eventName => {
4759      $dropArea[0].addEventListener(eventName, highlight, false);
4760    });
4761    ['dragleave', 'drop'].forEach(eventName => {
4762      $dropArea[0].addEventListener(eventName, unhighlight, false);
4763    });
4764
4765    function highlight(e) {
4766      $dropCss[0].classList.add('highlight');
4767    }
4768
4769    function unhighlight(e) {
4770      $dropCss[0].classList.remove('highlight');
4771    }
4772    // Добавление файлов в массив
4773    $dropArea[0].addEventListener('drop', handleDrop, false);
4774    $dropArea[0].addEventListener('input', handleInputClick, false);
4775
4776    function handleAppClick(event) {
4777      event.preventDefault();
4778      FilePicker.pickFile((path) => {
4779        createFile(path).then((file) => {
4780          addToArray([file]);
4781        }).catch(e => console.log('error create file ' + e));
4782      }, () => console.log('error pick file'));
4783    }
4784
4785    function handleInputClick(event) {
4786      const files = event.target.files;
4787      if (files.length) {
4788        addToArray(files);
4789      }
4790    }
4791
4792    function handleDrop(event) {
4793      const files = event.dataTransfer.files;
4794      if (files.length) {
4795        addToArray(files);
4796      }
4797    }
4798
4799    function addToArray(files) {
4800      let double = false;
4801      Array.prototype.forEach.call(files, function (file) {
4802        double = false;
4803        ko.utils.arrayForEach(koArray(), function (koFile) { // отсекаем повторы
4804          if (file.name === koFile.name) {
4805            double = true;
4806          }
4807        });
4808        if (!double) koArray.push(file);
4809      });
4810    }
4811
4812    async function createFile(url, type) {
4813      const response = await fetch(url);
4814      const data = await response.blob();
4815      // const metadata = {
4816      //   type: type || 'video/quicktime'
4817      // }
4818      const filename = url.split('/').pop() || new Date().getTime() + '.jpg';
4819      return new File([data], filename, { type: response.headers.get('content-type') || 'image/jpeg' });
4820    }
4821  }
4822};
4823ko.bindingHandlers.imagePopup = {
4824  init: function (element, valueAccessor) {
4825    let customConfig = {};
4826    if (valueAccessor().isSlider) {
4827      customConfig = {
4828        delegate: 'a',
4829        gallery: {
4830          enabled: true,
4831          tPrev: 'Назад',
4832          tNext: 'Далее',
4833          tCounter: '%curr% из %total%'
4834        },
4835        callbacks: {
4836          open: () => $('body').addClass('disable-scroll'),
4837          close: () => $('body').removeClass('disable-scroll')
4838        }
4839      };
4840    };
4841    $(valueAccessor().isSlider ? '.slider-container' : '.image-popup-vertical').magnificPopup({
4842      type: 'image',
4843      tClose: 'Закрыть (Esc)',
4844      tLoading: 'Загрузка...',
4845      removalDelay: 300,
4846      mainClass: 'mfp-fade',
4847      fixedContentPos: true,
4848      ...customConfig
4849    });
4850  }
4851};
4852ko.bindingHandlers.videoPopup = {
4853  init: function (element, valueAccessor) {
4854    var videoId = valueAccessor().videoId;
4855    $(element).click(() => {
4856      const url = `https://www.youtube.com/embed/${videoId}?autoplay=1&mute=1&enablejsapi=1`;
4857      $('.video-overlay iframe').attr('src', url);
4858      $('body').addClass('disable-scroll compensate-for-scrollbar');
4859      $('.video-overlay').addClass('video-overlay--active').click(() => {
4860        $('body').removeClass('disable-scroll compensate-for-scrollbar');
4861        $('.video-overlay').removeClass('video-overlay--active');
4862      });
4863    });
4864  }
4865};
4866ko.bindingHandlers.mobileNavbar = {
4867  init: function (element) {
4868    $(element).find('a[href*="#"]').each(function () {
4869      $(this).on('click', function (e) {
4870        const href = $(this).attr('href');
4871        // Проверяем, что это якорь на текущей странице
4872        if (href && href.startsWith('#') && href.length > 1) {
4873          e.preventDefault();
4874          e.stopPropagation(); // Останавливаем всплытие события
4875          const target = $(href);
4876          if (target.length) {
4877            // Закрываем мобильное меню
4878            $('body').removeClass('showRespNav');
4879            // Закрываем все дропдауны
4880            if (typeof m_new_eco !== 'undefined') {
4881              m_new_eco.isHeaderAllAboutEcoActive(false);
4882              m_new_eco.isHeaderPricesActive(false);
4883              m_new_eco.isHeaderDonorsActive(false);
4884            }
4885            // Плавная прокрутка к элементу с небольшой задержкой для закрытия меню
4886            setTimeout(function() {
4887              helper.scrollToElement(href);
4888            }, 100);
4889          }
4890        } else {
4891          // Просто закрываем меню для внешних ссылок
4892          $('body').removeClass('showRespNav');
4893        }
4894      });
4895    });
4896  }
4897};
4898ko.bindingHandlers.dropdownNavigation = {
4899  init: function (element) {
4900    var navItemHeaders = $(element).find('.simple-header__navigation-header');
4901    navItemHeaders.each(function() {
4902      var $header = $(this);
4903      var $item = $header.closest('.simple-header__navigation-item');
4904
4905      $header.on('click', function(e) {
4906        // Проверяем что клик не был по ссылке внутри заголовка
4907        if (!$(e.target).is('a') && !$(e.target).closest('a').length) {
4908          e.preventDefault();
4909          $item.toggleClass('active');
4910        }
4911      });
4912    });
4913
4914    $(element).find('.simple-header__navigation-dropdown a').on('click', function(e) {
4915      const href = $(this).attr('href');
4916      // Проверяем, что это якорь на текущей странице
4917      if (href && href.startsWith('#') && href.length > 1) {
4918        e.preventDefault();
4919        e.stopPropagation(); // Останавливаем всплытие
4920        const target = $(href);
4921        if (target.length) {
4922          // Закрываем дропдаун
4923          $(this).closest('.simple-header__navigation-item').removeClass('active');
4924          // Закрываем мобильное меню
4925          $('body').removeClass('showRespNav');
4926          // Плавная прокрутка к элементу с небольшой задержкой
4927          setTimeout(function() {
4928            helper.scrollToElement(href);
4929          }, 100);
4930        }
4931      } else {
4932        // Просто закрываем дропдаун для внешних ссылок
4933        $(this).closest('.simple-header__navigation-item').removeClass('active');
4934        $('body').removeClass('showRespNav');
4935      }
4936    });
4937  }
4938};
4939ko.bindingHandlers.appBlur = {
4940  init: function (element) {
4941    $(document).ready(function () {
4942      $(element).on('click', function () {
4943        m_cabinet.mobileMenuIsActive(false);
4944        m_cabinet.mobileRecordMenuIsActive(false);
4945      });
4946      // $('.bottom-bar, .cabinet__sidebar-content').children().not(':first').click(function() {
4947      //   m_cabinet.mobileMenuIsActive(false);
4948      //   m_cabinet_calendar.modalIsActive(false);
4949      // });
4950    });
4951  }
4952};
4953ko.bindingHandlers.appExternalLink = {
4954  init: function (element, valueAccessor) {
4955    $(element).on('click', function () {
4956      window.open(SERVER_URL + valueAccessor(), '_system');
4957    });
4958  }
4959};
4960
4961// Returns new and old value
4962ko.subscribable.fn.subscribeChanged = function (callback) {
4963  let previousValue;
4964  this.subscribe(function (_previousValue) {
4965    previousValue = _previousValue;
4966  }, undefined, 'beforeChange');
4967  this.subscribe(function (latestValue) {
4968    callback(latestValue, previousValue);
4969  });
4970};
4971
4972ko.bindingHandlers.noUiSlider = {
4973  init(element, valueAccessor) {
4974    const { config, onUpdate } = valueAccessor();
4975    noUiSlider.create(element, config);
4976    element.noUiSlider.disableUpdates = false;
4977    element.noUiSlider.on('start', () => {
4978      element.noUiSlider.disableUpdates = true;
4979    });
4980    element.noUiSlider.on('end', () => {
4981      element.noUiSlider.disableUpdates = false;
4982    });
4983    element.noUiSlider.on('update', onUpdate);
4984  }
4985};
4986
4987ko.bindingHandlers.noUiSliderSetMin = {
4988  update(element, valueAccessor) {
4989    const min = ko.unwrap(valueAccessor());
4990    if (element.noUiSlider.disableUpdates) return;
4991    element.noUiSlider.set([min]);
4992  }
4993};
4994
4995ko.bindingHandlers.noUiSliderSetMax = {
4996  update(element, valueAccessor) {
4997    const max = ko.unwrap(valueAccessor());
4998    if (element.noUiSlider.disableUpdates) return;
4999    element.noUiSlider.set([null, max]);
5000  }
5001};
5002
5003(function () {
5004  const EXECUTER_ID = 'ko.bindingHandlers.toFalseOnClickOutsideIfUpdatedToTrue';
5005  ko.bindingHandlers.hideOnClickOutside = {
5006    init(element, valueAccessor) {
5007      const visible = valueAccessor();
5008
5009      element[EXECUTER_ID] = (event) => {
5010        const path = event.composedPath();
5011        if (path.indexOf(element) === -1) {
5012          event.stopPropagation();
5013          visible(false);
5014          document.body.removeEventListener('click', element[EXECUTER_ID], true);
5015        }
5016      };
5017    },
5018    update(element, valueAccessor) {
5019      const visible = valueAccessor();
5020      const executer = element[EXECUTER_ID];
5021
5022      if (visible() === true) {
5023        document.body.addEventListener('click', executer, true);
5024      } else {
5025        document.body.removeEventListener('click', executer, true);
5026      }
5027    }
5028  };
5029})();
5030
5031ko.bindingHandlers.clampedUnwrappable = {
5032  init(element, valueAccessor) {
5033    const options = valueAccessor();
5034    const { original } = $clamp(element, options);
5035    const unwrapper = document.createElement('button');
5036    unwrapper.classList.add(...options.unwrapperClasses);
5037    unwrapper.innerText = 'Показать полностью';
5038    const setOriginal = () => {
5039      element.innerText = original.replaceAll('\n', '');
5040      unwrapper.removeEventListener('click', setOriginal);
5041    };
5042    unwrapper.addEventListener('click', setOriginal);
5043    element.appendChild(unwrapper);
5044  }
5045};
5046
5047ko.bindingHandlers.stickyToHeader = {
5048  init(element, valueAccessor) {
5049    const headerMobile = $('.header_mobile');
5050    const { wrapperClassName } = valueAccessor();
5051
5052    $(element).sticky({
5053      topSpacing: headerMobile.css('display') === 'none' ? 148 : headerMobile.height(),
5054      wrapperClassName,
5055      widthFromWrapper: false,
5056      getWidthFrom: document.body
5057    });
5058  }
5059};
5060(function () {
5061  const getPosition = (element) => {
5062    const elementTop = $(element).offset().top;
5063    const elementBottom = elementTop + $(element).outerHeight();
5064    const viewportTop = $(window).scrollTop();
5065    const viewportBottom = viewportTop + $(window).height();
5066
5067    return { viewportBottom, elementBottom };
5068  };
5069
5070  ko.bindingHandlers.stickyTillBottomOfWrapper = {
5071    init(_element) {
5072      const element = $(_element);
5073      const container = element.parent();
5074      const initialPosition = element.css('position');
5075      const THROTTLE_MS = 100;
5076
5077      const containerBottomInViewport = () => {
5078        const {
5079          elementBottom,
5080          viewportBottom
5081        } = getPosition(container);
5082
5083        return viewportBottom > elementBottom;
5084      };
5085
5086      container.height(element.height());
5087      if (window.screen.width > 425) {
5088        element.addClass('sticky-till-bottom');
5089      }
5090
5091      const unstickListener = _.throttle(() => {
5092        const bottomInViewPort = containerBottomInViewport();
5093        if (bottomInViewPort) {
5094          element.css('position', initialPosition);
5095          window.removeEventListener('scroll', unstickListener);
5096          window.addEventListener('scroll', stickListner);
5097        }
5098      }, THROTTLE_MS);
5099
5100      const stickListner = _.throttle(() => {
5101        const bottomOutViewport = !containerBottomInViewport();
5102        if (bottomOutViewport) {
5103          element.css('position', 'fixed');
5104          window.removeEventListener('scroll', stickListner);
5105          window.addEventListener('scroll', unstickListener);
5106        }
5107      }, THROTTLE_MS);
5108
5109      if (containerBottomInViewport()) {
5110        window.addEventListener('scroll', unstickListener);
5111      } else {
5112        window.addEventListener('scroll', stickListner);
5113      }
5114
5115      const setWidth = () => element.css('width', container.width());
5116      setWidth();
5117      window.addEventListener('resize', setWidth);
5118    }
5119  };
5120})();
5121
5122ko.bindingHandlers.wrapped = {
5123  update(element, valueAccessor) {
5124    const observable = valueAccessor();
5125    const wrapped = ko.unwrap(observable);
5126
5127    if (wrapped) {
5128      $(element).slideUp(300, 'swing');
5129    } else {
5130      $(element).slideToggle(300, 'swing');
5131    }
5132  }
5133};
5134
5135ko.bindingHandlers.donorImageSlider = {
5136  init: function (element) {
5137    const owlOptions = {
5138      items: 1,
5139      singleItem: true,
5140      margin: 0,
5141      center: true,
5142      mouseDrag: true,
5143      dots: true
5144    };
5145    $(element).owlCarousel(owlOptions).addClass('owl-carousel');
5146  }
5147};
5148
5149captchaIds = {}
5150// Невидимая капча отвечает не сразу. Пока проверка идёт, повторный клик не
5151// запускает её заново: иначе каждое прохождение уходит на сервер отдельным запросом.
5152// Метка времени, а не флаг — чтобы кнопка ожила, даже если капча не прислала событие.
5153captchaPendingSince = {}
5154const CAPTCHA_PENDING_TIMEOUT = 30000;
5155
5156// false — капча не загрузилась (заблокирован скрипт, нет ключа), запускать нечего.
5157function executeYandexCaptcha(name) {
5158  if (Date.now() - (captchaPendingSince[name] || 0) < CAPTCHA_PENDING_TIMEOUT) {
5159    return true;
5160  }
5161  if (typeof window.smartCaptcha === 'undefined' || captchaIds[name] === undefined) {
5162    return false;
5163  }
5164  captchaPendingSince[name] = Date.now();
5165  window.smartCaptcha.reset(captchaIds[name]);
5166  window.smartCaptcha.execute(captchaIds[name]);
5167  return true;
5168}
5169
5170// Невидимая капча показывает задание только подозрительным посетителям. Для проверки
5171// в своём браузере: localStorage.smartCaptchaTest = '1' — задание будет всегда.
5172function captchaTestMode() {
5173  try {
5174    return window.localStorage.getItem('smartCaptchaTest') === '1';
5175  } catch (e) {
5176    return false;
5177  }
5178}
5179
5180ko.bindingHandlers.yandexCaptcha = {
5181  init: function (element, valueAccessor) {
5182    let val = ko.utils.unwrapObservable(valueAccessor());
5183    let key = val.key;
5184    let name = val.name;
5185    const callback = val.callback;
5186    const onCancel = val.onCancel;
5187
5188    if (!key) {
5189      return;
5190    }
5191
5192    function loadYandexCaptcha() {
5193      if (typeof window.smartCaptcha !== 'undefined') {
5194        const target = document.createElement("div");
5195        element.appendChild(target);
5196
5197        const widgetId = window.smartCaptcha.render(target, {
5198          sitekey: key,
5199          invisible: true,
5200          hideShield: true,
5201          test: captchaTestMode()
5202        });
5203        captchaIds[name] = widgetId;
5204
5205        window.smartCaptcha.subscribe(widgetId, "success", () => {
5206          captchaPendingSince[name] = 0;
5207          callback();
5208        });
5209        // Задание закрыли, не решив, или проверка упала — даём нажать ещё раз.
5210        ["challenge-hidden", "network-error", "javascript-error"].forEach((event) => {
5211          window.smartCaptcha.subscribe(widgetId, event, () => {
5212            if (!captchaPendingSince[name]) {
5213              return;
5214            }
5215            captchaPendingSince[name] = 0;
5216            if (onCancel) {
5217              onCancel();
5218            }
5219          });
5220        });
5221
5222      } else {
5223        setTimeout(loadYandexCaptcha, 500);
5224      }
5225    }
5226
5227    loadYandexCaptcha();
5228  }
5229};
5230
5231ko.bindingHandlers.swiperInit = {
5232  init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
5233    var options = ko.unwrap(valueAccessor()) || {};
5234    var sliderId = allBindings.get('sliderId') || element.getAttribute('data-slider-id') || 'swiper-' + Math.random().toString(36).substr(2, 9);
5235    element.setAttribute('data-slider-id', sliderId);
5236    var dependencies = ko.unwrap(allBindings.get('swiperDependencies')) || [];
5237    var updateOptions = ko.unwrap(allBindings.get('swiperUpdateOptions')) || {};
5238    var autoUpdate = updateOptions.autoUpdate !== false;
5239    var resetPosition = updateOptions.resetPosition !== false;
5240    var updateDelay = updateOptions.delay || 100;
5241
5242    // Инициализируем Swiper с задержкой, чтобы DOM успел обновиться
5243    setTimeout(function () {
5244      var swiper = new Swiper(element, options);
5245      ko.utils.domData.set(element, 'swiperInstance', swiper);
5246      if (typeof viewModel.setSwiper === 'function') {
5247        viewModel.setSwiper(swiper, sliderId);
5248      } else {
5249        if (!viewModel.swipers) {
5250          viewModel.swipers = {};
5251        }
5252        viewModel.swipers[sliderId] = swiper;
5253        if (!viewModel.updateSwiper) {
5254          viewModel.updateSwiper = function (id) {
5255            var targetSwiper = viewModel.swipers[id];
5256            if (targetSwiper) {
5257              updateSwiperCompletely(targetSwiper, resetPosition);
5258            }
5259          };
5260        }
5261
5262        if (!viewModel.updateAllSwipers) {
5263          viewModel.updateAllSwipers = function () {
5264            Object.keys(viewModel.swipers).forEach(function (id) {
5265              viewModel.updateSwiper(id);
5266            });
5267          };
5268        }
5269      }
5270
5271      function updateSwiperCompletely(swiperInstance, resetPos) {
5272        if (!swiperInstance) return;
5273
5274        swiperInstance.update();
5275        swiperInstance.updateSize();
5276        swiperInstance.updateSlides();
5277        swiperInstance.updateProgress();
5278        swiperInstance.updateSlidesClasses();
5279
5280        if (swiperInstance.navigation && swiperInstance.navigation.update) swiperInstance.navigation.update();
5281        if (swiperInstance.pagination && swiperInstance.pagination.render) {
5282          swiperInstance.pagination.render();
5283          swiperInstance.pagination.update();
5284        }
5285        if (resetPos && swiperInstance.slides.length > 0) swiperInstance.slideTo(0, 0, false);
5286        updateNavigationState(swiperInstance);
5287      }
5288
5289      function updateNavigationState(swiperInstance) {
5290        if (!swiperInstance || !swiperInstance.navigation) return;
5291        var prevButton = swiperInstance.navigation.prevEl;
5292        var nextButton = swiperInstance.navigation.nextEl;
5293        if (!prevButton && !nextButton) return;
5294      }
5295
5296      var updateTimeout;
5297      function scheduleUpdate() {
5298        clearTimeout(updateTimeout);
5299        updateTimeout = setTimeout(function () {
5300          updateSwiperCompletely(swiper, resetPosition);
5301        }, updateDelay);
5302      }
5303
5304      swiper.on('update', function () {
5305        updateNavigationState(swiper);
5306      });
5307
5308      swiper.on('resize', function () {
5309        updateNavigationState(swiper);
5310      });
5311
5312      if (autoUpdate) {
5313        var subscriptions = [];
5314        if (Array.isArray(dependencies)) {
5315          dependencies.forEach(function (dep) {
5316            if (typeof dep === 'function' && dep.subscribe) {
5317              var subscription = dep.subscribe(scheduleUpdate);
5318              subscriptions.push(subscription);
5319            }
5320          });
5321        }
5322        ko.utils.domData.set(element, 'swiperSubscriptions', subscriptions);
5323      }
5324
5325      var observer = new MutationObserver(function (mutations) {
5326        var slideCountChanged = mutations.some(function (mutation) {
5327          return mutation.type === 'childList' &&
5328            (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0);
5329        });
5330        if (slideCountChanged) scheduleUpdate();
5331      });
5332
5333      var swiperWrapper = element.querySelector('.swiper-wrapper');
5334      if (swiperWrapper) {
5335        observer.observe(swiperWrapper, {
5336          childList: true,
5337          subtree: false
5338        });
5339      }
5340
5341      ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
5342        observer.disconnect();
5343        var subs = ko.utils.domData.get(element, 'swiperSubscriptions') || [];
5344        subs.forEach(function (subscription) {
5345          subscription.dispose();
5346        });
5347        clearTimeout(updateTimeout);
5348        if (swiper && swiper.destroy) {
5349          swiper.destroy(true, true);
5350        }
5351        if (viewModel.swipers && viewModel.swipers[sliderId]) {
5352          delete viewModel.swipers[sliderId];
5353        }
5354      });
5355      updateSwiperCompletely(swiper, false);
5356    }, 0);
5357    // Возвращаем { controlsDescendantBindings: true }, чтобы Knockout не применял биндинги к дочерним элементам
5358    return { controlsDescendantBindings: false };
5359  },
5360  update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
5361    var swiper = ko.utils.domData.get(element, 'swiperInstance');
5362    if (!swiper) return;
5363    var newOptions = ko.unwrap(valueAccessor());
5364    var optionsChanged = false;
5365
5366    if (newOptions) {
5367      if (newOptions.slidesPerView !== undefined &&
5368        newOptions.slidesPerView !== swiper.params.slidesPerView) {
5369        swiper.params.slidesPerView = newOptions.slidesPerView;
5370        optionsChanged = true;
5371      }
5372      if (newOptions.spaceBetween !== undefined &&
5373        newOptions.spaceBetween !== swiper.params.spaceBetween) {
5374        swiper.params.spaceBetween = newOptions.spaceBetween;
5375        optionsChanged = true;
5376      }
5377    }
5378    if (optionsChanged) {
5379      setTimeout(function () {
5380        swiper.update();
5381        swiper.updateSize();
5382        swiper.updateSlides();
5383        if (swiper.navigation && swiper.navigation.update) {
5384          swiper.navigation.update();
5385        }
5386      }, 50);
5387    }
5388  }
5389};
5390
5391ko.bindingHandlers.recordFlatpickr = {
5392  init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
5393    var options = ko.unwrap(valueAccessor()) || {};
5394    const availableDates = ko.computed(() => {
5395      const availableScheduleGroupedByDateTime = viewModel.search().availableScheduleGroupedByDateTime();
5396      return Object.keys(availableScheduleGroupedByDateTime);
5397    });
5398
5399    // Настройки по умолчанию
5400    var defaultOptions = {
5401      inline: true,
5402      monthSelectorType: 'static',
5403      showMonths: 1,
5404      static: true,
5405      yearSelectorType: 'static',
5406      altFormat: "F Y",
5407      locale: 'ru',
5408      dateFormat: 'Y.m.d',
5409      enable: availableDates(),
5410    };
5411
5412
5413
5414    var mergedOptions = Object.assign({}, defaultOptions, options);
5415    if (allBindings.has('selectedDate')) {
5416      var selectedDate = allBindings.get('selectedDate');
5417      if (ko.isObservable(selectedDate) && selectedDate()) {
5418        mergedOptions.defaultDate = selectedDate();
5419      }
5420
5421      mergedOptions.onReady = function (selectedDates, dateStr, instance) {
5422        if (ko.isObservable(selectedDate) && selectedDate()) {
5423          return;
5424        }
5425        const sortedEnableDates = instance.config._enable.sort((a, b) => new Date(a) - new Date(b));
5426        selectedDate(sortedEnableDates[0])
5427      }
5428
5429      // Обновляем observable при изменении даты
5430      mergedOptions.onChange = function (selectedDates, dateStr, instance) {
5431        if (selectedDates.length > 0 && ko.isObservable(selectedDate)) {
5432          const selectedDateValue = selectedDates[0];
5433          selectedDate(selectedDateValue);
5434        }
5435      };
5436
5437    }
5438    var instance = flatpickr(element, mergedOptions);
5439
5440    const scheduleSubscription = availableDates.subscribe(() => {
5441      if (!instance || typeof instance.set !== 'function') {
5442        return;
5443      }
5444
5445      instance.set('enable', availableDates());
5446      const sortedEnableDates = (instance.config._enable || []).slice().sort((a, b) => new Date(a) - new Date(b));
5447      const urlParams = new URLSearchParams(window.location.search);
5448      const dateFromUrl = urlParams.get('scheduleDay');
5449
5450      if (ko.isObservable(selectedDate) && selectedDate()) {
5451        instance.setDate(selectedDate(), false);
5452        return;
5453      }
5454
5455      if (dateFromUrl) {
5456        const parsedDate = moment(dateFromUrl).toDate();
5457        instance.set('defaultDate', parsedDate);
5458        selectedDate(parsedDate);
5459      } else if (sortedEnableDates.length > 0) {
5460        instance.set('defaultDate', sortedEnableDates[0]);
5461        selectedDate(sortedEnableDates[0]);
5462      }
5463    });
5464
5465    ko.utils.domData.set(element, 'flatpickr', instance);
5466    ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
5467      instance.destroy();
5468      scheduleSubscription.dispose();
5469    });
5470
5471  },
5472  update: function (element, valueAccessor, allBindings) {
5473    var instance = ko.utils.domData.get(element, 'flatpickr');
5474    // Если есть observable для выбранной даты
5475    if (allBindings.has('selectedDate') && instance) {
5476      var selectedDate = allBindings.get('selectedDate');
5477      if (ko.isObservable(selectedDate) && selectedDate()) {
5478        var selectedDates = instance.selectedDates || [];
5479        var currentDate = selectedDates.length ? selectedDates[0] : null;
5480        var newDate = selectedDate();
5481
5482        var currentTime = currentDate ? currentDate.getTime() : null;
5483        var newTime = moment(newDate).toDate().getTime();
5484
5485        if (currentTime !== newTime && typeof instance.setDate === 'function') {
5486          instance.setDate(newDate, true);
5487        }
5488      }
5489    }
5490  }
5491};
5492
5493ko.bindingHandlers.onRender = {
5494  init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
5495    var callback = valueAccessor();
5496    $(document).ready(() => {
5497      callback.call(viewModel);
5498    })
5499
5500  }
5501};
5502
5503ko.bindingHandlers.clickOutsideHide = {
5504  init: function (element, valueAccessor) {
5505    var observable = valueAccessor();
5506
5507    var handleOutsideClick = function (event) {
5508      if (!element.contains(event.target) && observable()) {
5509        observable(false);
5510      }
5511    };
5512    document.addEventListener('mousedown', handleOutsideClick);
5513    document.addEventListener('touchstart', handleOutsideClick);
5514
5515    ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
5516      document.removeEventListener('mousedown', handleOutsideClick);
5517      document.removeEventListener('touchstart', handleOutsideClick);
5518    });
5519  }
5520};
5521
5522ko.bindingHandlers.scrollToTop = {
5523  init: function(element, valueAccessor) {
5524    var options = ko.unwrap(valueAccessor()) || {};
5525    var duration = options.duration || 300;
5526    var offset = options.offset || 0;
5527
5528    $(element).on('click', function() {
5529      $('html, body').animate({ scrollTop: offset }, duration);
5530    });
5531  }
5532};
5533
5534// Мини-замена pagerjs (выпилен как легаси): страница видна, когда первый сегмент
5535// location.hash совпадает с id (пустой hash = 'home'). Контент биндится лениво при
5536// первом показе (аналог sourceCache: true), опция with задаёт контекст данных,
5537// sourceOnShow подгружает HTML при первом показе.
5538ko.bindingHandlers.page = {
5539  init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
5540    var opts = valueAccessor() || {};
5541    var bound = false;
5542    var visible = false;
5543
5544    var currentRoute = function() {
5545      return window.location.hash.slice(1).split('/')[0].split('?')[0] || 'home';
5546    };
5547
5548    var bindContent = function() {
5549      if (bound) return;
5550      bound = true;
5551      var context = opts.with ? bindingContext.createChildContext(opts.with) : bindingContext;
5552      ko.applyBindingsToDescendants(context, element);
5553    };
5554
5555    var show = function() {
5556      var render = function() {
5557        bindContent();
5558        element.style.display = '';
5559        if (opts.title) document.title = opts.title;
5560        if (typeof opts.afterShow === 'function') opts.afterShow();
5561      };
5562      if (typeof opts.beforeShow === 'function') opts.beforeShow();
5563      if (opts.sourceOnShow && !element.__pageSourceLoaded) {
5564        element.__pageSourceLoaded = true;
5565        $.ajax({ url: opts.sourceOnShow, dataType: 'html' })
5566          .done(function(html) { $(element).html(html); })
5567          .always(render);
5568      } else {
5569        render();
5570      }
5571    };
5572
5573    var hide = function() {
5574      if (typeof opts.beforeHide === 'function') opts.beforeHide();
5575      element.style.display = 'none';
5576    };
5577
5578    var onRouteChange = function() {
5579      var match = currentRoute() === opts.id;
5580      if (match && !visible) {
5581        visible = true;
5582        show();
5583      } else if (!match && visible) {
5584        visible = false;
5585        hide();
5586      }
5587    };
5588
5589    element.style.display = 'none';
5590    window.addEventListener('hashchange', onRouteChange);
5591    ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
5592      window.removeEventListener('hashchange', onRouteChange);
5593    });
5594    onRouteChange();
5595
5596    return { controlsDescendantBindings: true };
5597  }
5598};

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.