PageSourceSearch

https://orion-e.co/

html orion-e.co collected 2026-10-07 18:03:23 UTC 31,710 bytes, 1,003 lines download raw bytes

1<!doctype html>
2<html lang="ru">
3  <head>
4    <meta charset="UTF-8" />
5    <link rel="icon" type="image/svg+xml" href="/orion_logo.svg" />
6    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7    <title>Орион</title>
8 
9    
9<script type="module" crossorigin src="/assets/index-map-coordinates-fixed-v3.js"></script>
9
10    <link rel="stylesheet" crossorigin href="/assets/index-DY46cZQi.css">
11  <!-- Yandex.Metrika counter -->
12<script type="text/javascript">
13    (function(m,e,t,r,i,k,a){
14        m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
15        m[i].l=1*new Date();
16        for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
17        k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)
18    })(window, document,'script','//mc.yandex.ru/metrika/tag.js?id=104880811', 'ym');
19
20    ym(104880811, 'init', {ssr:true, webvisor:true, clickmap:true, ecommerce:"dataLayer", accurateTrackBounce:true, trackLinks:true});
21</script>
21
22<noscript><div><img src="//mc.yandex.ru/watch/104880811" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
23<!-- /Yandex.Metrika counter -->
24</head>
25
26  <body>
27    <div id="root"></div>
28
29<script>
30document.addEventListener('DOMContentLoaded', () => {
31  /* ============================================================
32     Хелпер: события смены маршрута для SPA
33  ============================================================ */
34  (function routeEvents(){
35    const _ps = history.pushState, _rs = history.replaceState;
36    function fire(){ window.dispatchEvent(new Event('locationchange')); }
37    history.pushState = function(){ const r=_ps.apply(this, arguments); fire(); return r; };
38    history.replaceState = function(){ const r=_rs.apply(this, arguments); fire(); return r; };
39    window.addEventListener('popstate', fire);
40  })();
41
42  /* ============================================================
43     /about → плитка «Производственная безопасность» (в самый низ колонки)
44     + модалка с таблицей документов
45  ============================================================ */
46  function mountPB(){
47    if (location.pathname.replace(/\/+$/,'') !== '/about') return;
48
49const DOCS = [
50  { title: 'Политика по ОТ ОРИОН', href: '/ot.pdf' },
51  { title: 'Сводная ведомость СОУТ 2025', href: '/sout.pdf' },
52  { title: 'Перечень рекомендуемых мероприятий по УУТ по итогам СОУТ 2025', href: '/sout-mer.pdf' },
53  { title: 'Сводная ведомость СОУТ 2026', href: '/sout26.pdf' },
54  { title: 'Перечень рекомендуемых мероприятий по УУТ по итогам СОУТ 2026', href: '/sout-mer26.pdf' },
55  { title: 'Категория риска ООО «ОРИОН»', href: '/risk.pdf' },
56];
57
58    const text = el => (el?.textContent || '').replace(/\s+/g,' ').trim();
59
60    function findAnyTile(){
61      return Array.from(document.querySelectorAll('a,button'))
62        .find(a => /Презентац/i.test(text(a))) ||
63             Array.from(document.querySelectorAll('a,button'))
64        .find(a => /Разрешител/i.test(text(a)));
65    }
66
67    function countTiles(node){
68      if (!node) return 0;
69      let c = 0;
70      node.querySelectorAll('a,button').forEach(a=>{
71        if (a.querySelector('img') && a.querySelector('svg')) c++;
72      });
73      return c;
74    }
75
76    function climbToColumn(start){
77      let n = start;
78      while (n && n !== document.documentElement) {
79        if (countTiles(n) >= 2) return n;
80        n = n.parentElement;
81      }
82      return null;
83    }
84
85    function ensureModal(){
86      if (document.getElementById('pb-modal')) return;
87
88      const style = document.createElement('style');
89      style.textContent = `
90        .pb-overlay{position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index:9999}
91        .pb-hidden{display:none}
92        .pb-modal{background:#fff;border-radius:16px;box-shadow:0 20px 60px rgba(0,0,0,.25);width:min(92vw,720px);max-height:88vh;overflow:auto}
93        .pb-head{display:flex;align-items:center;justify-content:space-between;padding:18px 22px;border-bottom:1px solid #e5e7eb}
94        .pb-title{font-size:18px;font-weight:600;color:#111827;margin:0}
95        .pb-close{appearance:none;border:0;background:transparent;font-size:22px;line-height:1;cursor:pointer;color:#6b7280}
96        .pb-close:hover{color:#111827}
97        .pb-body{padding:18px 22px}
98        .pb-table{width:100%;border-collapse:collapse}
99        .pb-table th,.pb-table td{border-top:1px solid #e5e7eb;padding:10px 12px;text-align:left;font-size:15px;color:#111827}
100        .pb-table th{background:#f9fafb;color:#374151;font-weight:600}
101        .pb-link{color:#2563eb;text-decoration:none}
102        .pb-link:hover{color:#1d4ed8;text-decoration:underline}
103      `;
104      document.head.appendChild(style);
105
106      const overlay = document.createElement('div');
107      overlay.id = 'pb-modal';
108      overlay.className = 'pb-overlay pb-hidden';
109      overlay.innerHTML = `
110        <div class="pb-modal" role="dialog" aria-modal="true" aria-labelledby="pb-title">
111          <div class="pb-head">
112            <h3 id="pb-title" class="pb-title">Производственная безопасность — Содержимое</h3>
113            <button class="pb-close" type="button" aria-label="Закрыть">✕</button>
114          </div>
115          <div class="pb-body">
116            <table class="pb-table">
117              <thead>
118                <tr>
119                  <th style="width:64%">Наименование</th>
120                  <th>Файл</th>
121                </tr>
122              </thead>
123              <tbody id="pb-tbody"></tbody>
124            </table>
125          </div>
126        </div>
127      `;
128      document.body.appendChild(overlay);
129
130      const tbody = overlay.querySelector('#pb-tbody');
131      DOCS.forEach(d => {
132        const tr = document.createElement('tr');
133        tr.innerHTML = `
134          <td>${d.title}</td>
135          <td><a class="pb-link" href="${d.href}" target="_blank" rel="noopener">Открыть</a></td>
136        `;
137        tbody.appendChild(tr);
138      });
139
140      const hide = ()=> overlay.classList.add('pb-hidden');
141      overlay.addEventListener('click', e => { if (e.target === overlay) hide(); });
142      overlay.querySelector('.pb-close').addEventListener('click', hide);
143      document.addEventListener('keydown', e => { if (e.key==='Escape' && !overlay.classList.contains('pb-hidden')) hide(); });
144    }
145
146    function openModal(){ ensureModal(); document.getElementById('pb-modal').classList.remove('pb-hidden'); }
147
148    function makeTile(sampleClass){
149      const btn = document.createElement('button');
150      btn.type = 'button';
151      // копируем классы плитки и даём ей занимать отдельную строку
152      btn.className = (sampleClass || '') + ' block w-full my-2';
153      btn.setAttribute('data-pb-tile','1');
154      btn.style.cursor = 'pointer';
155      btn.innerHTML = `
156        <img src="/assets/sout.svg"
157             alt="Иконка"
158             class="w-12 h-12 md:w-14 md:h-14 lg:w-16 lg:h-16 flex-shrink-0">
159        <span class="flex-grow text-base md:text-lg font-medium text-gray-900 text-left">
160          Производственная безопасность
161        </span>
162        <svg class="w-8 h-8 lg:w-10 h-8 lg:h-10 text-[#8AC640] flex-shrink-0 md:mr-2"
163             fill="none" stroke="currentColor" viewBox="0 0 24 24">
164          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
165                d="M9 5l7 7-7 7"></path>
166        </svg>
167      `;
168      btn.addEventListener('click', openModal);
169      return btn;
170    }
171
172    function tryPlaceOnce(){
173      if (document.querySelector('[data-pb-tile="1"]')) return true;
174
175      const ref = findAnyTile();
176      if (!ref) return false;
177
178      const column = climbToColumn(ref);
179      if (!column) return false;
180
181      const sample = Array.from(column.querySelectorAll('a,button'))
182        .find(a => a.querySelector('img') && a.querySelector('svg')) || ref;
183
184      const tile = makeTile(sample.getAttribute('class') || '');
185      column.appendChild(tile);           // ← в самый низ колонки
186      return true;
187    }
188
189    // Пытаемся вставить сразу и ждём дорисовки DOM (SPA)
190    let tries = 0;
191    const timer = setInterval(() => {
192      tries++;
193      if (tryPlaceOnce() || tries > 60) clearInterval(timer); // до ~18 сек
194    }, 300);
195
196    // Если DOM меняется — пробуем снова
197    new MutationObserver(() => tryPlaceOnce())
198      .observe(document.getElementById('root') || document.documentElement, { childList:true, subtree:true });
199  }
200
201  // Запускаем на текущем маршруте и при каждой смене URL
202  function bootAbout(){ mountPB(); }
203  window.addEventListener('locationchange', bootAbout);
204  bootAbout();
205
206  /* ============================================================
207     Мобильное меню: добавляем «Транспорт» и «Услуги»
208  ============================================================ */
209  const CORE_LABELS = ['О компании','Проекты','Пресс-центр','Карьера'];
210  const EXTRA = [
211    { href: '/transport', text: 'Транспорт' },
212    { href: '/services',  text: 'Услуги' }
213  ];
214  const getText = el => (el.textContent || '').replace(/\s+/g,' ').trim();
215
216  function findMobileMenuContainer() {
217    const candidates = new Set([
218      ...document.querySelectorAll('[id*="mobile"], [class*="mobile"]'),
219      ...document.querySelectorAll('[class*="fixed"][class*="inset"]'),
220      ...document.querySelectorAll('nav, menu')
221    ]);
222    document.querySelectorAll('a[href^="/"]').forEach(a=>{
223      if (CORE_LABELS.includes(getText(a))) {
224        const p = a.closest('div,nav,menu,section') || a.parentElement;
225        if (p) candidates.add(p);
226      }
227    });
228    let best=null, score=0;
229    candidates.forEach(n=>{
230      let s=0; n.querySelectorAll('a').forEach(a=>{ if (CORE_LABELS.includes(getText(a))) s++; });
231      if (s>score){score=s; best=n;}
232    });
233    return score?best:null;
234  }
235
236  function ensureMobileLinks(){
237    const cont = findMobileMenuContainer();
238    if (!cont) return;
239    const anchors = Array.from(cont.querySelectorAll('a'));
240    const has = href => !!anchors.find(a => a.getAttribute('href')===href);
241    const sample = anchors.find(a => CORE_LABELS.includes(getText(a))) || anchors[0];
242    const cls = sample ? (sample.getAttribute('class') || '') : 'text-[#18181B] hover:text-[#8AC640]';
243    const cta = anchors.find(a => /Написать нам/i.test(getText(a)));
244    EXTRA.forEach(({href,text})=>{
245      if (!has(href)){
246        const a=document.createElement('a');
247        a.href=href; a.textContent=text; a.className=cls;
248        if (cta && cta.parentNode) cta.parentNode.insertBefore(a, cta);
249        else cont.appendChild(a);
250      }
251    });
252  }
253
254  let triesMenu = 0;
255  const bootMenu = setInterval(()=>{ triesMenu++; ensureMobileLinks(); if (triesMenu>30) clearInterval(bootMenu); }, 500);
256  document.addEventListener('click', e=>{
257    const t=e.target;
258    if (t.closest('button[aria-label="Меню"]') || /burger/i.test(t.id||'') || t.closest('#burger')){
259      setTimeout(ensureMobileLinks,60);
260      setTimeout(ensureMobileLinks,300);
261      setTimeout(ensureMobileLinks,700);
262    }
263  }, true);
264  new MutationObserver(()=>ensureMobileLinks()).observe(document.documentElement,{childList:true,subtree:true});
265
266  /* ============================================================
267     «Жёсткая» навигация для серверных страниц (обход SPA)
268  ============================================================ */
269  (function(){
270    const HARD = new Set(['/transport','/services']);
271    const FLAG = 'data-hard-nav';
272    function enhance(a){
273      if (!a || a.getAttribute(FLAG)==='1') return;
274      a.setAttribute(FLAG,'1');
275      a.addEventListener('click', function(e){
276        if (e.button===0 && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey){
277          try{
278            const url = new URL(a.getAttribute('href')||'', location.origin);
279            if (HARD.has(url.pathname)){ e.preventDefault(); window.location.assign(url.href); }
280          }catch(_){}
281        }
282      }, {capture:true});
283    }
284    function scan(){ document.querySelectorAll('a[href^="/"]').forEach(enhance); }
285    if (document.readyState==='loading') document.addEventListener('DOMContentLoaded', scan, {once:true}); else scan();
286    new MutationObserver(scan).observe(document.documentElement,{childList:true,subtree:true});
287  })();
288});
289</script>
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303<!-- Отправка формы через MODX SMTP -->
304<script>
305(function () {
306
307    const CONFIG = {
308        endpoint: '/contact-form-api',
309        successMessage: 'Спасибо! Сообщение успешно отправлено.',
310        errorMessage: 'Не удалось отправить сообщение. Попробуйте ещё раз.'
311    };
312
313    let handledForm = null;
314
315    function findContactForm() {
316        /*
317         * В первую очередь ищем форму по старому имени EmailJS.
318         */
319        const emailInput =
320            document.querySelector(
321                'form input[name="from_email"]'
322            );
323
324        if (emailInput && emailInput.form) {
325            return emailInput.form;
326        }
327
328        /*
329         * Запасной поиск по полю e-mail и textarea.
330         */
331        return Array.from(
332            document.querySelectorAll('form')
333        ).find(function (form) {
334            return (
335                form.querySelector('input[type="email"]') &&
336                form.querySelector('textarea')
337            );
338        }) || null;
339    }
340
341    function findField(form, selectors) {
342        for (const selector of selectors) {
343            const element = form.querySelector(selector);
344
345            if (element) {
346                return element;
347            }
348        }
349
350        return null;
351    }
352
353    function getFormFields(form) {
354        const textInputs = Array.from(
355            form.querySelectorAll(
356                'input:not([type="hidden"]):not([type="checkbox"]):not([type="submit"])'
357            )
358        );
359
360        return {
361            name: findField(form, [
362                '[name="from_name"]',
363                '[name="name"]',
364                'input[autocomplete="name"]'
365            ]) || textInputs[0] || null,
366
367            email: findField(form, [
368                '[name="from_email"]',
369                '[name="email"]',
370                'input[type="email"]'
371            ]) || textInputs[1] || null,
372
373            phone: findField(form, [
374                '[name="phone"]',
375                '[name="from_phone"]',
376                'input[type="tel"]'
377            ]) || textInputs[2] || null,
378
379            subject: findField(form, [
380                '[name="subject"]',
381                '[name="topic"]',
382                '[name="theme"]'
383            ]) || textInputs[3] || null,
384
385            message: findField(form, [
386                '[name="message"]',
387                'textarea'
388            ]),
389
390            consent: findField(form, [
391                '[name="consent"]',
392                'input[type="checkbox"]'
393            ])
394        };
395    }
396
397    function showMessage(text, type) {
398        let box = document.getElementById(
399            'contact-form-result'
400        );
401
402        if (!box) {
403            box = document.createElement('div');
404            box.id = 'contact-form-result';
405
406            Object.assign(box.style, {
407                position: 'fixed',
408                left: '50%',
409                top: '30px',
410                transform: 'translateX(-50%)',
411                zIndex: '100000',
412                maxWidth: '520px',
413                width: 'calc(100% - 32px)',
414                padding: '18px 22px',
415                borderRadius: '12px',
416                boxShadow: '0 15px 45px rgba(0,0,0,.25)',
417                fontSize: '16px',
418                fontWeight: '600',
419                textAlign: 'center'
420            });
421
422            document.body.appendChild(box);
423        }
424
425        box.textContent = text;
426
427        if (type === 'success') {
428            box.style.background = '#ffffff';
429            box.style.color = '#256b1d';
430            box.style.border = '2px solid #7fc331';
431        } else {
432            box.style.background = '#ffffff';
433            box.style.color = '#b42318';
434            box.style.border = '2px solid #d92d20';
435        }
436
437        box.style.display = 'block';
438
439        setTimeout(function () {
440            box.style.display = 'none';
441        }, 5000);
442    }
443
444    async function submitForm(form) {
445        const fields = getFormFields(form);
446
447        const submitButton =
448            form.querySelector(
449                'button[type="submit"], input[type="submit"]'
450            );
451
452        if (!fields.name || !fields.email || !fields.message) {
453            showMessage(
454                'Не удалось определить поля формы.',
455                'error'
456            );
457
458            return;
459        }
460
461        const payload = {
462            name: fields.name.value.trim(),
463            email: fields.email.value.trim(),
464            phone: fields.phone
465                ? fields.phone.value.trim()
466                : '',
467            subject: fields.subject
468                ? fields.subject.value.trim()
469                : '',
470            message: fields.message.value.trim(),
471            consent: fields.consent && fields.consent.checked
472                ? '1'
473                : '0',
474
475            /*
476             * Скрытое антиспам-поле.
477             */
478            website: ''
479        };
480
481        if (
482            !payload.name ||
483            !payload.email ||
484            !payload.phone ||
485            !payload.message
486        ) {
487            showMessage(
488                'Заполните обязательные поля.',
489                'error'
490            );
491
492            return;
493        }
494
495        if (payload.consent !== '1') {
496            showMessage(
497                'Подтвердите согласие на обработку персональных данных.',
498                'error'
499            );
500
501            return;
502        }
503
504        const oldButtonText = submitButton
505            ? submitButton.textContent
506            : '';
507
508        if (submitButton) {
509            submitButton.disabled = true;
510            submitButton.textContent = 'Отправка...';
511        }
512
513        try {
514            const response = await fetch(
515                CONFIG.endpoint,
516                {
517                    method: 'POST',
518                    credentials: 'same-origin',
519                    headers: {
520                        'Content-Type': 'application/json',
521                        'Accept': 'application/json'
522                    },
523                    body: JSON.stringify(payload)
524                }
525            );
526
527            const text = await response.text();
528
529            let result;
530
531            try {
532                result = JSON.parse(text);
533            } catch (error) {
534                throw new Error(
535                    'Сервер вернул некорректный ответ'
536                );
537            }
538
539            if (!response.ok || !result.success) {
540                throw new Error(
541                    result.message ||
542                    CONFIG.errorMessage
543                );
544            }
545
546            form.reset();
547
548            showMessage(
549                CONFIG.successMessage,
550                'success'
551            );
552
553        } catch (error) {
554            console.error(
555                'Ошибка формы:',
556                error
557            );
558
559            showMessage(
560                error.message ||
561                CONFIG.errorMessage,
562                'error'
563            );
564
565        } finally {
566            if (submitButton) {
567                submitButton.disabled = false;
568                submitButton.textContent =
569                    oldButtonText || 'Отправить';
570            }
571        }
572    }
573
574    function attachHandler() {
575        const form = findContactForm();
576
577        if (!form || form === handledForm) {
578            return;
579        }
580
581        handledForm = form;
582
583        /*
584         * Перехватываем раньше обработчика React/EmailJS.
585         */
586        form.addEventListener(
587            'submit',
588            function (event) {
589                event.preventDefault();
590                event.stopPropagation();
591                event.stopImmediatePropagation();
592
593                submitForm(form);
594            },
595            true
596        );
597
598        console.log(
599            'Форма подключена к MODX SMTP'
600        );
601    }
602
603    function startObserver() {
604        attachHandler();
605
606        const root =
607            document.getElementById('root') ||
608            document.documentElement;
609
610        const observer = new MutationObserver(function () {
611            attachHandler();
612        });
613
614        observer.observe(root, {
615            childList: true,
616            subtree: true
617        });
618
619        setInterval(
620            attachHandler,
621            1000
622        );
623    }
624
625    if (document.readyState === 'loading') {
626        document.addEventListener(
627            'DOMContentLoaded',
628            startObserver
629        );
630    } else {
631        startObserver();
632    }
633
634})();
635</script>
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659<!-- Виджет HH.ru -->
660<script>
661(function () {
662
663    /* ============================================================
664       ВСЕ НАСТРОЙКИ
665       Меняйте значения только в этом разделе
666    ============================================================ */
667
668    const CONFIG = {
669        /* Ширина внешнего серого блока */
670        outerMaxWidth: 1300,
671
672        /*
673         * Ширина карточек вакансий.
674         * 900 px — широкий вариант с одной вакансией в строке.
675         * Если появятся колонки, уменьшите до 850.
676         */
677        widgetDesktopWidth: 900,
678
679        /* Высота блока */
680        desktopWidgetHeight: 1050,
681        tabletWidgetHeight: 1150,
682        mobileWidgetHeight: 1250,
683
684        /* Контрольные точки экранов */
685        mobileBreakpoint: 480,
686        tabletBreakpoint: 820,
687
688        /* Минимальная ширина */
689        widgetMinWidth: 280,
690
691        /* Отступы внешнего блока от экрана */
692        desktopOuterSideGap: 80,
693        tabletOuterSideGap: 24,
694        mobileOuterSideGap: 12,
695
696        /* Отступы виджета от экрана */
697        tabletWidgetSideGap: 40,
698        mobileWidgetSideGap: 24,
699
700        /* Внешний вертикальный отступ */
701        outerMarginTopBottom: 30,
702
703        /* Внутренние отступы серого блока */
704        outerPaddingTop: 20,
705        outerPaddingRight: 20,
706        outerPaddingBottom: 20,
707        outerPaddingLeft: 20,
708
709        /* Оформление */
710        outerBorderRadius: 18,
711        outerBackground: '#f1f4f8',
712
713        /* Количество вакансий */
714        vacanciesPerPage: 5,
715
716        /* Настройки элементов */
717        enableRegionFilter: true,
718        showRegion: true,
719        showSalary: true,
720        showLogo: true,
721
722        /*
723         * Рабочая комбинация HH.
724         * Не убирайте EMPLOYER — это вызывает зависание виджета.
725         */
726        elementsOrder: 'NAME_SALARY_AREA_EMPLOYER'
727    };
728
729
730    /* ============================================================
731       ЛОГИКА
732    ============================================================ */
733
734    let widgetMounted = false;
735
736    function isCareerPage() {
737        return window.location.pathname.replace(/\/+$/, '') === '/career';
738    }
739
740    function getOuterWidth() {
741        const viewportWidth = document.documentElement.clientWidth;
742
743        if (viewportWidth <= CONFIG.mobileBreakpoint) {
744            return Math.max(
745                CONFIG.widgetMinWidth,
746                viewportWidth - CONFIG.mobileOuterSideGap
747            );
748        }
749
750        if (viewportWidth <= CONFIG.tabletBreakpoint) {
751            return viewportWidth - CONFIG.tabletOuterSideGap;
752        }
753
754        if (viewportWidth <= CONFIG.outerMaxWidth + 100) {
755            return viewportWidth - CONFIG.desktopOuterSideGap;
756        }
757
758        return CONFIG.outerMaxWidth;
759    }
760
761    function getWidgetWidth() {
762        const viewportWidth = document.documentElement.clientWidth;
763
764        if (viewportWidth <= CONFIG.mobileBreakpoint) {
765            return Math.max(
766                CONFIG.widgetMinWidth,
767                viewportWidth - CONFIG.mobileWidgetSideGap
768            );
769        }
770
771        if (viewportWidth <= CONFIG.tabletBreakpoint) {
772            return viewportWidth - CONFIG.tabletWidgetSideGap;
773        }
774
775        return CONFIG.widgetDesktopWidth;
776    }
777
778    function getWidgetHeight() {
779        const viewportWidth = document.documentElement.clientWidth;
780
781        if (viewportWidth <= CONFIG.mobileBreakpoint) {
782            return CONFIG.mobileWidgetHeight;
783        }
784
785        if (viewportWidth <= CONFIG.tabletBreakpoint) {
786            return CONFIG.tabletWidgetHeight;
787        }
788
789        return CONFIG.desktopWidgetHeight;
790    }
791
792    function findErrorBlock() {
793        return Array.from(
794            document.querySelectorAll('#root div')
795        ).find(function (element) {
796            const text = (element.textContent || '')
797                .replace(/\s+/g, ' ')
798                .trim();
799
800            return (
801                text.includes('Не удалось загрузить вакансии') &&
802                text.includes('Ошибка 403')
803            );
804        });
805    }
806
807    function findSmallestErrorBlock(errorBlock) {
808        let target = errorBlock;
809
810        Array.from(
811            errorBlock.querySelectorAll('div')
812        ).forEach(function (child) {
813            const text = (child.textContent || '')
814                .replace(/\s+/g, ' ')
815                .trim();
816
817            if (
818                text.includes('Не удалось загрузить вакансии') &&
819                text.includes('Ошибка 403')
820            ) {
821                target = child;
822            }
823        });
824
825        return target;
826    }
827
828    function mountHhWidget() {
829        if (!isCareerPage() || widgetMounted) {
830            return;
831        }
832
833        if (document.getElementById('hh-widget-outer')) {
834            widgetMounted = true;
835            return;
836        }
837
838        const errorBlock = findErrorBlock();
839
840        if (!errorBlock) {
841            return;
842        }
843
844        const target = findSmallestErrorBlock(errorBlock);
845
846        const outerWidth = getOuterWidth();
847        const widgetWidth = getWidgetWidth();
848        const widgetHeight = getWidgetHeight();
849
850        widgetMounted = true;
851
852        const outerContainer = document.createElement('div');
853
854        outerContainer.id = 'hh-widget-outer';
855        outerContainer.style.width = 'calc(100% - 24px)';
856        outerContainer.style.maxWidth = outerWidth + 'px';
857        outerContainer.style.minHeight = widgetHeight + 'px';
858
859        outerContainer.style.margin =
860            CONFIG.outerMarginTopBottom + 'px auto';
861
862        outerContainer.style.padding =
863            CONFIG.outerPaddingTop + 'px ' +
864            CONFIG.outerPaddingRight + 'px ' +
865            CONFIG.outerPaddingBottom + 'px ' +
866            CONFIG.outerPaddingLeft + 'px';
867
868        outerContainer.style.boxSizing = 'border-box';
869        outerContainer.style.background = CONFIG.outerBackground;
870
871        outerContainer.style.borderRadius =
872            CONFIG.outerBorderRadius + 'px';
873
874        outerContainer.style.position = 'relative';
875        outerContainer.style.zIndex = '10';
876        outerContainer.style.overflow = 'visible';
877
878        const widgetPlace = document.createElement('div');
879
880        widgetPlace.id = 'hh-widget-place';
881        widgetPlace.style.width = widgetWidth + 'px';
882        widgetPlace.style.maxWidth = '100%';
883        widgetPlace.style.minHeight = widgetHeight + 'px';
884        widgetPlace.style.margin = '0 auto';
885        widgetPlace.style.padding = '0';
886        widgetPlace.style.boxSizing = 'border-box';
887        widgetPlace.style.overflow = 'visible';
888        widgetPlace.style.position = 'relative';
889
890        outerContainer.appendChild(widgetPlace);
891
892        target.parentElement.insertBefore(
893            outerContainer,
894            target
895        );
896
897        target.style.display = 'none';
898
899        const script = document.createElement('script');
900
901        script.type = 'module';
902        script.className = 'widgetHhScript';
903
904        script.textContent = `
905            import initVacanciesWidget
906            from 'https://widgets.hh.ru/vacanciesWidget/vacanciesWidget.js';
907
908            initVacanciesWidget({
909                "country": "RU",
910
911                "searchParams": {
912                    "employerId": ["3688427"],
913                    "host": "hh.ru",
914                    "locale": "RU",
915                    "perPage": ${CONFIG.vacanciesPerPage}
916                },
917
918                "settings": {
919                    "locale": "RU",
920                    "theme": "LIGHT",
921
922                    "size": {
923                        "width": ${widgetWidth},
924                        "height": ${widgetHeight}
925                    },
926
927                    "elementsOrder": "${CONFIG.elementsOrder}",
928
929                    "isLogoVisible": ${CONFIG.showLogo},
930                    "isRegionVisible": ${CONFIG.showRegion},
931                    "isRegionSearchEnabled": ${CONFIG.enableRegionFilter},
932                    "isProfRolesSearchEnabled": false,
933                    "isSalaryVisible": ${CONFIG.showSalary}
934                },
935
936                "version": "1"
937            });
938        `;
939
940        widgetPlace.appendChild(script);
941    }
942
943    function startObserver() {
944        const root = document.getElementById('root');
945
946        if (!root) {
947            setTimeout(startObserver, 200);
948            return;
949        }
950
951        const observer = new MutationObserver(function () {
952            mountHhWidget();
953        });
954
955        observer.observe(root, {
956            childList: true,
957            subtree: true,
958            characterData: true
959        });
960
961        mountHhWidget();
962
963        setTimeout(mountHhWidget, 300);
964        setTimeout(mountHhWidget, 1000);
965        setTimeout(mountHhWidget, 2500);
966    }
967
968    function resetWidget() {
969        widgetMounted = false;
970
971        const oldWidget = document.getElementById('hh-widget-outer');
972
973        if (oldWidget && !isCareerPage()) {
974            oldWidget.remove();
975        }
976
977        setTimeout(mountHhWidget, 300);
978        setTimeout(mountHhWidget, 1000);
979    }
980
981    window.addEventListener('locationchange', resetWidget);
982    window.addEventListener('popstate', resetWidget);
983
984    if (document.readyState === 'loading') {
985        document.addEventListener(
986            'DOMContentLoaded',
987            startObserver
988        );
989    } else {
990        startObserver();
991    }
992
993})();
994</script>
994
995
996
997
998
999
1000
1001
1002  </body>
1003</html>

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.