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.