1<!doctype html> 2<html lang="ru"> 3<head> 4<meta charset="utf-8"> 5<meta name="viewport" content="width=device-width, initial-scale=1"> 6<title>РабоÑа в пиÑевой пÑомÑÑленноÑÑи â IndMart</title> 7<meta name="description" content="ÐаканÑии и ÑезÑме в пиÑевой пÑомÑÑленноÑÑи РоÑÑии. ÐекаÑи, ÑÐµÑ Ð½Ð¾Ð»Ð¾Ð³Ð¸, Ð¼ÐµÑ Ð°Ð½Ð¸ÐºÐ¸, ÑÑководиÑели пекаÑен и кондиÑеÑÑÐºÐ¸Ñ ."> 8<link rel="canonical" href="https://indmart.ru/jobs"> 9<link rel="preconnect" href="https://fonts.googleapis.com"> 10<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 11<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@700;800&family=Inter:wght@400;500;600&display=swap" rel="stylesheet"> 12<style> 13:root { 14 --navy: #1B3A6B; 15 --navy-light: #254d8f; 16 --orange: #FF6B35; 17 --orange-dark: #e55a25; 18 --bg: #f8f9fc; 19 --card: #ffffff; 20 --border: #e0e4ef; 21 --text: #1a2236; 22 --muted: #8892a4; 23 --green: #059669; 24 --radius: 12px; 25} 26 27* { box-sizing: border-box; margin: 0; padding: 0; } 28body { font-family: 'Inter', sans-serif; background: var(--bg); color: var(--text); -webkit-font-smoothing: antialiased; } 29 30/* NAV */ 31nav { 32 background: white; 33 border-bottom: 1px solid #eee; 34 height: 56px; 35 display: flex; 36 align-items: center; 37 justify-content: space-between; 38 padding: 0 24px; 39 position: sticky; 40 top: 0; 41 z-index: 200; 42} 43.nav-logo { font-family: 'Montserrat', sans-serif; font-weight: 800; font-size: 20px; color: var(--navy); text-decoration: none; } 44.nav-logo span { color: var(--orange); } 45.nav-links { display: flex; gap: 4px; } 46.nav-link { color: #555; text-decoration: none; font-size: 14px; padding: 6px 12px; border-radius: 6px; transition: all 0.15s; } 47.nav-link:hover, .nav-link.active { color: var(--navy); background: #f5f7ff; } 48.nav-right { display: flex; gap: 8px; align-items: center; } 49.btn-post { background: var(--orange); color: white; border: none; border-radius: 8px; padding: 8px 16px; font-size: 13px; font-weight: 600; cursor: pointer; font-family: inherit; transition: background 0.15s; } 50.btn-post:hover { background: var(--orange-dark); } 51.btn-login { background: white; color: var(--navy); border: 1px solid var(--border); border-radius: 8px; padding: 6px 14px; font-size: 13px; cursor: pointer; font-family: inherit; } 52 53/* HERO */ 54.hero { 55 background: white; 56 padding: 40px 24px 0; 57 border-bottom: 1px solid var(--border); 58} 59.hero-inner { max-width: 1100px; margin: 0 auto; } 60.hero-top { 61 display: flex; 62 align-items: center; 63 justify-content: space-between; 64 margin-bottom: 24px; 65} 66.hero h1 { 67 font-family: 'Montserrat', sans-serif; 68 font-weight: 800; 69 font-size: clamp(22px, 3vw, 32px); 70 color: var(--navy); 71} 72.hero h1 span { color: var(--orange); } 73.hero-sub { font-size: 15px; color: var(--muted); margin-top: 4px; } 74 75/* FILTERS */ 76.filters { 77 max-width: 1100px; 78 margin: 0 auto; 79 padding: 16px 0; 80 display: flex; 81 gap: 10px; 82 align-items: end; 83 flex-wrap: wrap; 84} 85.f-field { display: flex; flex-direction: column; gap: 4px; } 86.f-label { font-size: 11px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; } 87.f-input, .f-select { 88 height: 38px; 89 border: 1.5px solid var(--border); 90 border-radius: 8px; 91 padding: 0 12px; 92 font-size: 14px; 93 font-family: inherit; 94 color: var(--text); 95 background: white; 96 outline: none; 97 transition: border-color 0.15s; 98} 99.f-input:focus, .f-select:focus { border-color: var(--navy); } 100.f-field-search { flex: 1; min-width: 200px; } 101.f-field-industry { flex: 0 0 180px; } 102.f-field-city { flex: 0 0 140px; } 103.f-field-type { flex: 0 0 140px; } 104.btn-search { 105 height: 38px; 106 background: white; 107 color: var(--navy); 108 border: 2px solid var(--navy); 109 border-radius: 8px; 110 padding: 0 20px; 111 font-size: 14px; 112 font-weight: 600; 113 cursor: pointer; 114 font-family: inherit; 115 transition: all 0.15s; 116 white-space: nowrap; 117} 118.btn-search:hover { background: var(--navy); color: white; } 119 120/* GEO AUTOCOMPLETE */ 121.geo-wrap { position: relative; } 122.geo-dropdown { 123 position: absolute; 124 top: 100%; 125 left: 0; 126 right: 0; 127 background: white; 128 border: 1.5px solid var(--border); 129 border-top: none; 130 border-radius: 0 0 8px 8px; 131 box-shadow: 0 8px 24px rgba(27,58,107,0.10); 132 z-index: 500; 133 max-height: 200px; 134 overflow-y: auto; 135 display: none; 136} 137.geo-dropdown.open { display: block; } 138.geo-option { 139 padding: 8px 12px; 140 font-size: 13px; 141 cursor: pointer;
142 color: var(--text); 143 border-bottom: 1px solid var(--bg); 144} 145.geo-option:hover, .geo-option.active { background: #f5f7ff; color: var(--navy); } 146 147/* TABS */ 148.tabs-wrap { 149 max-width: 1100px; 150 margin: 0 auto; 151 padding: 0 0 0; 152 display: flex; 153 gap: 0; 154 border-top: 1px solid var(--border); 155} 156.tab-btn { 157 padding: 12px 20px; 158 font-size: 14px; 159 font-weight: 600; 160 cursor: pointer; 161 border: none; 162 background: none; 163 color: var(--muted); 164 font-family: inherit; 165 border-bottom: 2px solid transparent; 166 transition: all 0.15s; 167} 168.tab-btn.active { color: var(--navy); border-bottom-color: var(--navy); } 169.tab-btn:hover { color: var(--navy); } 170 171/* MAIN */ 172.main { max-width: 1100px; margin: 0 auto; padding: 24px; } 173 174/* STATS */ 175.stats-row { 176 display: flex; 177 gap: 24px; 178 margin-bottom: 20px; 179 font-size: 14px; 180 color: var(--muted); 181} 182.stats-row strong { color: var(--text); } 183 184/* JOB CARD */ 185.jobs-list { display: flex; flex-direction: column; gap: 12px; } 186.job-card { 187 background: white; 188 border: 1.5px solid var(--border); 189 border-radius: var(--radius); 190 padding: 20px 24px; 191 cursor: pointer; 192 transition: all 0.2s; 193 display: flex; 194 gap: 20px; 195 align-items: flex-start; 196} 197.job-card:hover { border-color: var(--navy); box-shadow: 0 4px 16px rgba(27,58,107,0.08); transform: translateY(-1px); } 198.job-card.urgent { border-left: 3px solid var(--orange); } 199.job-logo { 200 width: 48px; 201 height: 48px; 202 border-radius: 10px; 203 background: var(--bg); 204 border: 1.5px solid var(--border); 205 display: flex; 206 align-items: center; 207 justify-content: center; 208 flex-shrink: 0; 209 font-family: 'Montserrat', sans-serif; 210 font-weight: 800; 211 font-size: 16px; 212 color: var(--navy); 213} 214.job-body { flex: 1; min-width: 0; } 215.job-title { font-weight: 700; font-size: 16px; color: var(--navy); margin-bottom: 4px; } 216.job-company { font-size: 14px; color: var(--text); margin-bottom: 8px; } 217.job-meta { display: flex; gap: 12px; flex-wrap: wrap; } 218.job-badge { 219 display: inline-flex; 220 align-items: center; 221 gap: 4px; 222 padding: 3px 8px; 223 border-radius: 6px; 224 font-size: 12px; 225 font-weight: 500; 226} 227.badge-city { background: var(--bg); color: var(--muted); } 228.badge-salary { background: #f0fdf4; color: var(--green); font-weight: 600; } 229.badge-type { background: #eff6ff; color: #1d4ed8; } 230.badge-urgent { background: #fff3ee; color: var(--orange); } 231.badge-industry { background: #f5f7ff; color: var(--navy); } 232.job-right { text-align: right; flex-shrink: 0; } 233.job-salary { font-family: 'Montserrat', sans-serif; font-weight: 800; font-size: 16px; color: var(--navy); } 234.job-date { font-size: 12px; color: var(--muted); margin-top: 4px; } 235 236/* EMPTY */ 237.empty { text-align: center; padding: 80px 24px; color: var(--muted); } 238.empty h3 { font-size: 18px; color: var(--text); margin-bottom: 8px; } 239 240/* NEW JOB FORM */ 241.form-wrap { max-width: 680px; margin: 0 auto; } 242.form-card { 243 background: white; 244 border: 1.5px solid var(--border); 245 border-radius: 16px; 246 padding: 32px; 247} 248.form-title { font-family: 'Montserrat', sans-serif; font-weight: 800; font-size: 20px; color: var(--navy); margin-bottom: 24px; } 249.field { margin-bottom: 18px; } 250.field label { display: block; font-size: 12px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; } 251.ctl { 252 width: 100%; 253 height: 42px; 254 border: 1.5px solid var(--border); 255 border-radius: 8px; 256 padding: 0 14px; 257 font-size: 14px; 258 font-family: inherit; 259 color: var(--text); 260 background: white; 261 outline: none; 262 transition: border-color 0.15s; 263} 264.ctl:focus { border-color: var(--navy); } 265textarea.ctl { height: auto; min-height: 100px; padding: 12px 14px; resize: vertical; line-height: 1.6; } 266.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; } 267.btn-submit { 268 width: 100%; 269 height: 48px; 270 background: var(--orange); 271 color: white; 272 border: none; 273 border-radius: 10px; 274 font-size: 16px; 275 font-weight: 700; 276 cursor: pointer; 277 font-family: inherit; 278 transition: background 0.15s; 279 margin-top: 8px; 280} 281.btn-submit:hover { background: var(--orange-dark); } 282.form-error { color: #dc2626; font-size: 13px; margin-bottom: 12px; display: none; } 283 284/* INDUSTRY CHIPS */ 285.industry-chips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 20px; } 286.chip { 287 padding: 6px 14px; 288 border-radius: 20px; 289 border: 1.5px solid var(--border); 290 font-size: 13px; 291 font-weight: 500; 292 cursor: pointer; 293 background: white; 294 color: var(--muted); 295 transition: all 0.15s; 296} 297.chip:hover { border-color: var(--navy); color: var(--navy); } 298.chip.active { background: var(--navy); color: white; border-color: var(--navy); } 299 300/* FOOTER */ 301footer { 302 background: white; 303 border-top: 1px solid #eee; 304 padding: 28px 24px; 305 text-align: center; 306 color: var(--muted); 307 font-size: 13px; 308 margin-top: 60px; 309} 310.footer-logo { font-family: 'Montserrat', sans-serif; font-weight: 800; font-size: 16px; color: var(--navy); margin-bottom: 6px; } 311.footer-logo span { color: var(--orange); } 312 313/* MODAL */ 314.modal-overlay { 315 display: none; 316 position: fixed; 317 inset: 0; 318 background: rgba(0,0,0,0.55); 319 z-index: 1000; 320 align-items: center; 321 justify-content: center; 322 backdrop-filter: blur(4px); 323} 324.modal-overlay.open { display: flex; } 325.modal { 326 background: white; 327 border-radius: 16px; 328 padding: 32px; 329 width: 100%; 330 max-width: 560px; 331 margin: 16px; 332 position: relative; 333 max-height: 90vh; 334 overflow-y: auto; 335} 336.modal-close { position: absolute; top: 14px; right: 14px; background: none; border: none; font-size: 20px; cursor: pointer; color: var(--muted); } 337.modal h2 { font-family: 'Montserrat', sans-serif; font-weight: 800; font-size: 20px; color: var(--navy); margin-bottom: 16px; } 338.detail-row { display: flex; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid var(--bg); font-size: 14px; } 339.detail-row:last-child { border-bottom: none; } 340.detail-key { color: var(--muted); } 341.detail-val { font-weight: 500; max-width: 60%; text-align: right; } 342.detail-desc { font-size: 14px; line-height: 1.7; color: var(--text); margin-top: 16px; white-space: pre-wrap; } 343.btn-contact { 344 display: block; 345 width: 100%; 346 margin-top: 20px; 347 padding: 13px; 348 background: var(--navy); 349 color: white; 350 border: none; 351 border-radius: 10px; 352 font-size: 15px; 353 font-weight: 700; 354 cursor: pointer; 355 font-family: inherit; 356 text-align: center; 357 text-decoration: none; 358 transition: background 0.15s; 359} 360.btn-contact:hover { background: var(--navy-light); } 361 362@media (max-width: 768px) { 363 .nav-links { display: none; } 364 .filters { flex-wrap: wrap; } 365 .f-field-industry, .f-field-city, .f-field-type { flex: 1 1 140px; } 366 .form-row { grid-template-columns: 1fr; } 367 .job-card { flex-direction: column; gap: 12px; } 368 .job-right { text-align: left; } 369} 370</style> 371<!-- Yandex.Metrika counter -->
372<script type="text/javascript"> 373(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};m[i].l=1*new Date();for(var j=0;j<document.scripts.length;j++){if(document.scripts[j].src===r){return;}}k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})(window,document,"script","https://mc.yandex.ru/metrika/tag.js?id=110152856","ym"); 374ym(110152856,"init",{ssr:true,webvisor:true,clickmap:true,accurateTrackBounce:true,trackLinks:true}); 375</script>
375 376<noscript><div><img src="https://mc.yandex.ru/watch/110152856" style="position:absolute;left:-9999px;" alt=""/></div></noscript> 377<!-- /Yandex.Metrika counter --> 378<link rel="icon" type="image/svg+xml" href="/favicon.svg"> 379</head> 380<body>
381<script src="/js/ui.js?v=20260722"></script>
381 382 383<nav> 384 <a href="/" class="nav-logo">Ind<span>Mart</span></a> 385 <div class="nav-links"> 386 <a href="/listings" class="nav-link">ÐбÑÑвлениÑ</a> 387 <a href="/wanted" class="nav-link">ÐÑÑ</a> 388 <a href="/jobs" class="nav-link active">РабоÑа</a> 389 <a href="/baza" class="nav-link">Ðаза знаний</a> 390 </div> 391 <div class="nav-right"> 392 <button class="btn-post" onclick="showTab('new')">+ РазмеÑÑиÑÑ Ð²Ð°ÐºÐ°Ð½ÑиÑ</button> 393 <div id="navUser" style="display:none;position:relative;align-items:center;"> 394 <div style="display:flex;align-items:center;gap:8px;cursor:pointer;" onclick="toggleUserMenu()"> 395 <div id="navAvatar" style="width:32px;height:32px;border-radius:50%;background:#FF6B35;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:700;color:white;">A</div> 396 <span id="navName" style="font-size:13px;color:var(--navy);font-weight:600;"></span> 397 <span style="font-size:10px;color:var(--muted);">â¼</span> 398 </div> 399 <div id="userDropdown" style="display:none;position:absolute;right:0;top:44px;background:white;border:1.5px solid #e0e4ef;border-radius:12px;box-shadow:0 8px 32px rgba(0,0,0,0.12);
399min-width:200px;z-index:9999;overflow:hidden;"> 400 <a href="/cabinet" style="display:flex;align-items:center;gap:10px;padding:11px 16px;font-size:14px;color:#1a2236;text-decoration:none;" onmouseover="this.style.background='#f5f7ff'" onmouseout="this.style.background=''">ð¤ ÐиÑнÑй кабинеÑ</a> 401 <a href="/messages" style="display:flex;align-items:center;gap:10px;padding:11px 16px;font-size:14px;color:#1a2236;text-decoration:none;" onmouseover="this.style.background='#f5f7ff'" onmouseout="this.style.background=''">ð¬ СообÑениÑ</a> 402 <a href="/consultant" style="display:flex;align-items:center;gap:10px;padding:11px 16px;font-size:14px;color:#1a2236;text-decoration:none;" onmouseover="this.style.background='#f5f7ff'" onmouseout="this.style.background=''">ð¤ AI ÐонÑÑлÑÑанÑ</a> 403 <a href="/model-search.html" style="display:flex;align-items:center;gap:10px;padding:11px 16px;font-size:14px;color:#1a2236;text-decoration:none;" onmouseover="this.style.background='#f5f7ff'" onmouseout="this.style.background=''">ð ÐналиÑика Ñен</a> 404 <div style="height:1px;background:#e0e4ef;margin:4px 0;"></div> 405 <button onclick="logout()" style="display:flex;align-items:center;gap:10px;padding:11px 16px;font-size:14px;color:#dc2626;background:none;border:none;width:100%;cursor:pointer;font-family:inherit;" onmouseover="this.style.background='#fff0f0'" onmouseout="this.style.background=''">ðª ÐÑйÑи</button> 406 </div> 407 </div> 408 <button class="btn-login" id="navLoginBtn" onclick="window.openLoginModal()">ÐойÑи</button> 409 </div> 410</nav> 411 412<div class="hero"> 413 <div class="hero-inner"> 414 <div class="hero-top"> 415 <div> 416 <h1>РабоÑа в <span>пиÑевой пÑомÑÑленноÑÑи</span></h1> 417 <div class="hero-sub">ÐаканÑии Ð´Ð»Ñ Ð¿ÐµÐºÐ°Ñей, ÑÐµÑ Ð½Ð¾Ð»Ð¾Ð³Ð¾Ð², Ð¼ÐµÑ Ð°Ð½Ð¸ÐºÐ¾Ð² и ÑÑководиÑелей</div> 418 </div> 419 <div id="heroCount" style="font-family:'Montserrat',sans-serif;font-weight:800;font-size:32px;color:var(--navy);"></div> 420 </div> 421 422 <!-- ФилÑÑÑÑ --> 423 <div class="filters"> 424 <div class="f-field f-field-search"> 425 <div class="f-label">ÐоиÑк</div> 426 <input id="fSearch" class="f-input" placeholder="ÐолжноÑÑÑ, компаниÑâ¦" onkeydown="if(event.key==='Enter') doSearch()"> 427 </div> 428 <div class="f-field f-field-industry"> 429 <div class="f-label">ÐÑÑаÑлÑ</div> 430 <select id="fIndustry" class="f-select"> 431 <option value="">ÐÑе оÑÑаÑли</option> 432 <option value="hlebopekarnoe">ÐекаÑни и кондиÑеÑÑкие</option> 433 <option value="myasopererabotka">ÐÑÑопеÑеÑабоÑка</option> 434 <option value="molochnoe">ÐолоÑное пÑоизводÑÑво</option> 435 <option value="horeca">ÐбÑеÑÑвенное пиÑание</option> 436 </select> 437 </div> 438 <div class="f-field f-field-city"> 439 <div class="f-label">ÐоÑод</div> 440 <div class="geo-wrap"> 441 <input id="fCity" class="f-input" placeholder="ÐоÑод" autocomplete="off" oninput="geoSuggest(this,'fCityDrop')" onkeydown="geoKeydown(event,this,'fCityDrop')"> 442 <div class="geo-dropdown" id="fCityDrop"></div> 443 </div> 444 </div> 445 <div class="f-field f-field-type"> 446 <div class="f-label">Тип занÑÑоÑÑи</div> 447 <select id="fType" class="f-select"> 448 <option value="">ÐÑбой</option> 449 <option value="full">ÐолнаÑ</option> 450 <option value="part">ЧаÑÑиÑнаÑ</option> 451 <option value="contract">ÐоговоÑ</option> 452 <option value="remote">УдалÑнно</option> 453 </select> 454 </div> 455 <div class="f-field"> 456 <div class="f-label"> </div> 457 <button class="btn-search" onclick="doSearch()">ÐайÑи</button> 458 </div> 459 </div> 460 461 <!-- ÐÑÑаÑлевÑе ÑÐ¸Ð¿Ñ --> 462 <div class="industry-chips" id="industryChips"> 463 <div class="chip active" onclick="setIndustry('',this)">ÐÑе</div> 464 <div class="chip" onclick="setIndustry('hlebopekarnoe',this)">ÐекаÑни и кондиÑеÑÑкие</div> 465 <div class="chip" onclick="setIndustry('myasopererabotka',this)">ÐÑÑопеÑеÑабоÑка</div> 466 <div class="chip" onclick="setIndustry('molochnoe',this)">ÐолоÑное</div> 467 <div class="chip" onclick="setIndustry('horeca',this)">HoReCa</div> 468 </div> 469 470 <!-- Ð¢Ð°Ð±Ñ --> 471 <div class="tabs-wrap"> 472 <button class="tab-btn active" id="tabVacancies" onclick="showTab('vacancies')">
472ÐаканÑии</button> 473 <button class="tab-btn" id="tabResumes" onclick="showTab('resumes')">РезÑме</button> 474 <button class="tab-btn" id="tabNew" onclick="showTab('new')">+ РазмеÑÑиÑÑ</button> 475 </div> 476 </div> 477</div> 478 479<!-- MAIN CONTENT --> 480<div class="main"> 481 482 <!-- ÐаканÑии --> 483 <div id="pageVacancies"> 484 <div class="stats-row"> 485 <span id="statsText">ÐагÑÑзка...</span> 486 </div> 487 <div id="jobsList" class="jobs-list"></div> 488 <div id="emptyJobs" class="empty" style="display:none"> 489 <h3>ÐаканÑий пока неÑ</h3> 490 <p>СÑанÑÑе пеÑвÑм â ÑазмеÑÑиÑе ваканÑÐ¸Ñ Ð±ÐµÑплаÑно</p> 491 <button onclick="showTab('new')" style="margin-top:16px;padding:10px 24px;background:var(--orange);color:white;border:none;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;">РазмеÑÑиÑÑ Ð²Ð°ÐºÐ°Ð½ÑиÑ</button> 492 </div> 493 </div> 494 495 <!-- РезÑме --> 496 <div id="pageResumes" style="display:none"> 497 <div class="empty"> 498 <h3>Раздел ÑезÑме ÑкоÑо оÑкÑоеÑÑÑ</h3> 499 <p>Ðока Ð²Ñ Ð¼Ð¾Ð¶ÐµÑе ÑазмеÑÑиÑÑ Ð²Ð°ÐºÐ°Ð½ÑÐ¸Ñ Ð¸ найÑи ÑоÑÑÑдника</p> 500 <button onclick="showTab('new')" style="margin-top:16px;padding:10px 24px;background:var(--orange);color:white;border:none;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;">РазмеÑÑиÑÑ Ð²Ð°ÐºÐ°Ð½ÑиÑ</button> 501 </div> 502 </div> 503 504 <!-- ФоÑма ÑазмеÑÐµÐ½Ð¸Ñ --> 505 <div id="pageNew" style="display:none"> 506 <div class="form-wrap"> 507 508 <!-- Шаг 0: ÐÑÐ±Ð¾Ñ Ñипа --> 509 <div id="stepChoose" class="form-card" style="text-align:center;"> 510 <div class="form-title">ЧÑо Ñ Ð¾ÑиÑе ÑазмеÑÑиÑÑ?</div> 511 <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:8px;">
512 <div onclick="chooseType('vacancy')" style="border:2px solid var(--border);border-radius:12px;padding:28px 16px;cursor:pointer;transition:all 0.2s;" id="choiceVacancy"> 513 <div style="font-size:36px;margin-bottom:12px;">ð</div> 514 <div style="font-family:'Montserrat',sans-serif;font-weight:700;font-size:16px;color:var(--navy);">ÐаканÑиÑ</div> 515 <div style="font-size:13px;color:var(--muted);margin-top:6px;">ÐÑÑ ÑоÑÑÑдника в компаниÑ</div> 516 </div> 517 <div onclick="chooseType('resume')" style="border:2px solid var(--border);border-radius:12px;padding:28px 16px;cursor:pointer;transition:all 0.2s;" id="choiceResume"> 518 <div style="font-size:36px;margin-bottom:12px;">ð</div> 519 <div style="font-family:'Montserrat',sans-serif;font-weight:700;font-size:16px;color:var(--navy);">РезÑме</div> 520 <div style="font-size:13px;color:var(--muted);margin-top:6px;">ÐÑÑ ÑабоÑÑ Ð² оÑÑаÑли</div> 521 </div> 522 </div> 523 </div> 524 525 <!-- ФоÑма ваканÑии --> 526 <div id="formVacancy" style="display:none;"> 527 <div class="form-card"> 528 <button onclick="backToChoose()" style="background:none;border:none;color:var(--navy);cursor:pointer;font-size:13px;margin-bottom:16px;font-family:inherit;">â Ðазад</button> 529 <div class="form-title">РазмеÑÑиÑÑ Ð²Ð°ÐºÐ°Ð½ÑиÑ</div> 530 531 <!-- Ðлок о компании --> 532 <div style="background:#f5f7ff;border-radius:10px;padding:16px;margin-bottom:20px;"> 533 <div style="font-size:12px;font-weight:700;color:var(--navy);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:12px;">Ркомпании</div> 534 <div class="field"> 535 <label>Ðазвание компании *</label> 536 <input id="vCompany" class="ctl" placeholder="ÐекаÑÐ½Ñ 'Хлеб да СолÑ', ÐÐРХлебозаводâ¦"> 537 </div> 538 <div class="form-row"> 539 <div class="field"> 540 <label>ÐÑÑаÑÐ»Ñ *</label> 541 <select id="vIndustry" class="ctl"> 542 <option value="">ÐÑбеÑиÑе оÑÑаÑлÑ</option> 543 <option value="hlebopekarnoe">ÐекаÑни и кондиÑеÑÑкие</option> 544 <option value="myasopererabotka">ÐÑÑопеÑеÑабоÑка</option> 545 <option value="molochnoe">ÐолоÑное пÑоизводÑÑво</option> 546 <option value="ovoshchi-frukty">ÐеÑеÑабоÑка овоÑей и ÑÑÑкÑов</option> 547 <option value="horeca">ÐбÑеÑÑвенное пиÑание (HoReCa)</option> 548 <option value="rybnoe">Ð ÑбопеÑеÑабоÑка</option> 549 <option value="zernovoe">ÐеÑнопеÑеÑабоÑка</option> 550 <option value="korma">ÐÑоизводÑÑво коÑмов</option> 551 </select> 552 </div> 553 <div class="field"> 554 <label>ÐоÑод *</label> 555 <div class="geo-wrap"> 556 <input id="vCity" class="ctl" placeholder="ÐоÑква" autocomplete="off" oninput="geoSuggest(this,'vCityDrop')" onkeydown="geoKeydown(event,this,'vCityDrop')"> 557 <div class="geo-dropdown" id="vCityDrop"></div> 558 </div> 559 </div> 560 </div> 561 <div class="field" style="margin-bottom:0"> 562 <label>Ркомпании</label> 563 <textarea id="vCompanyDesc" class="ctl" style="min-height:70px;" placeholder="ÐÑаÑко о ваÑей компании, Ñем занимаеÑеÑÑ, ÑколÑко Ð»ÐµÑ Ð½Ð° ÑÑнкеâ¦"></textarea> 564 </div> 565 </div> 566 567 <!-- Ðлок о ваканÑии --> 568 <div style="background:#fff;border-radius:10px;padding:16px;margin-bottom:20px;border:1.5px solid var(--border);"> 569 <div style="font-size:12px;font-weight:700;color:var(--navy);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:12px;">
569РваканÑии</div> 570 <div class="field"> 571 <label>ÐолжноÑÑÑ *</label> 572 <input id="vTitle" class="ctl" placeholder="ÐекаÑÑ, ÑÐµÑ Ð½Ð¾Ð»Ð¾Ð³ пÑоизводÑÑва, Ð¼ÐµÑ Ð°Ð½Ð¸Ðºâ¦"> 573 </div> 574 <div class="form-row"> 575 <div class="field"> 576 <label>ÐаÑплаÑа оÑ, â½</label> 577 <input id="vSalaryFrom" class="ctl" type="number" placeholder="50000"> 578 </div> 579 <div class="field"> 580 <label>ÐаÑплаÑа до, â½</label> 581 <input id="vSalaryTo" class="ctl" type="number" placeholder="80000"> 582 </div> 583 </div> 584 <div class="field"> 585 <label>Тип занÑÑоÑÑи</label> 586 <select id="vType" class="ctl"> 587 <option value="full">ÐÐ¾Ð»Ð½Ð°Ñ Ð·Ð°Ð½ÑÑоÑÑÑ</option> 588 <option value="part">ЧаÑÑиÑнаÑ</option> 589 <option value="contract">ÐоговоÑ/пÑоекÑ</option> 590 <option value="remote">УдалÑнно</option> 591 </select> 592 </div> 593 <div class="field" style="margin-bottom:0"> 594 <label>ÐбÑзанноÑÑи и ÑÑебованиÑ</label> 595 <textarea id="vDesc" class="ctl" placeholder="ЧÑо нÑжно делаÑÑ, какой опÑÑ Ð½Ñжен, ÑÑÐ»Ð¾Ð²Ð¸Ñ ÑабоÑÑâ¦"></textarea> 596 </div> 597 </div> 598 599 <!-- ÐонÑакÑÑ --> 600 <div class="form-row"> 601 <div class="field"> 602 <label>ТелеÑон</label> 603 <input id="vPhone" class="ctl" placeholder="+7 900 000-00-00"> 604 </div> 605 <div class="field"> 606 <label>Telegram</label> 607 <input id="vTg" class="ctl" placeholder="username"> 608 </div> 609 </div> 610 <div style="font-size:12px;color:var(--muted);margin-bottom:12px;">УкажиÑе Ñ Ð¾ÑÑ Ð±Ñ Ð¾Ð´Ð¸Ð½ ÑпоÑоб ÑвÑзи</div> 611 <div class="form-error" id="vacancyError"></div> 612 <button class="btn-submit" onclick="submitVacancy()">ÐпÑбликоваÑÑ Ð²Ð°ÐºÐ°Ð½ÑÐ¸Ñ Ð±ÐµÑплаÑно</button> 613 </div> 614 </div> 615 616 <!-- ФоÑма ÑезÑме --> 617 <div id="formResume" style="display:none;"> 618 <div class="form-card"> 619 <button onclick="backToChoose()" style="background:none;border:none;color:var(--navy);cursor:pointer;font-size:13px;margin-bottom:16px;font-family:inherit;">â Ðазад</button> 620 <div class="form-title">РазмеÑÑиÑÑ ÑезÑме</div> 621 622 <!-- Ðлок о Ñебе --> 623 <div style="background:#f5f7ff;border-radius:10px;padding:16px;margin-bottom:20px;"> 624 <div style="font-size:12px;font-weight:700;color:var(--navy);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:12px;">Ð Ñебе</div> 625 <div class="form-row"> 626 <div class="field"> 627 <label>ÐÐ¼Ñ *</label> 628 <input id="rName" class="ctl" placeholder="Ðван Ðванов"> 629 </div> 630 <div class="field"> 631 <label>ÐоÑод *</label> 632 <div class="geo-wrap"> 633 <input id="rCity" class="ctl" placeholder="ÐоÑква" autocomplete="off" oninput="geoSuggest(this,'rCityDrop')" onkeydown="geoKeydown(event,this,'rCityDrop')"> 634 <div class="geo-dropdown" id="rCityDrop"></div> 635 </div> 636 </div> 637 </div> 638 <div class="form-row"> 639 <div class="field"> 640 <label>ÐпÑÑ ÑабоÑÑ</label> 641 <select id="rExp" class="ctl"> 642 <option value="no_exp">Ðез опÑÑа</option> 643 <option value="1-3">1-3 года</option> 644 <option value="3-6">3-6 леÑ</option> 645 <option value="6+">Ðолее 6 леÑ</option> 646 </select> 647 </div> 648 <div class="field"> 649 <label>ÐÑÑаÑлÑ</label> 650 <select id="rIndustry" class="ctl"> 651 <option value="">ÐÑбаÑ</option> 652 <option value="hlebopekarnoe">ÐекаÑни и кондиÑеÑÑкие</option> 653 <option value="myasopererabotka">ÐÑÑопеÑеÑабоÑка</option> 654 <option value="molochnoe">ÐолоÑное пÑоизводÑÑво</option> 655 <option value="horeca">ÐбÑеÑÑвенное пиÑание</option> 656 <option value="rybnoe">Ð ÑбопеÑеÑабоÑка</option> 657 <option value="zernovoe">ÐеÑнопеÑеÑабоÑка</option> 658 </select> 659 </div> 660 </div> 661 <div class="field" style="margin-bottom:0"> 662 <label>Ð Ñебе</label> 663 <textarea id="rAbout" class="ctl" style="min-height:70px;" placeholder="ÐÑаÑко о ваÑем опÑÑе, ÑпеÑиализаÑии, доÑÑижениÑÑ â¦"></textarea> 664 </div> 665 </div> 666 667 <!-- Ðлок ÑезÑме --> 668 <div style="background:#fff;border-radius:10px;padding:16px;margin-bottom:20px;border:1.5px solid var(--border);"> 669 <div style="font-size:12px;font-weight:700;color:var(--navy);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:12px;">ÐÐµÐ»Ð°ÐµÐ¼Ð°Ñ ÑабоÑа</div> 670 <div class="field"> 671 <label>ÐÐµÐ»Ð°ÐµÐ¼Ð°Ñ Ð´Ð¾Ð»Ð¶Ð½Ð¾ÑÑÑ *</label> 672 <input id="rTitle" class="ctl" placeholder="ÐекаÑÑ, ÑÐµÑ Ð½Ð¾Ð»Ð¾Ð³, Ð¼ÐµÑ Ð°Ð½Ð¸Ðº обоÑÑдованиÑâ¦"> 673 </div> 674 <div class="form-row"> 675 <div class="field"> 676 <label>ÐÐµÐ»Ð°ÐµÐ¼Ð°Ñ Ð·Ð°ÑплаÑа оÑ, â½</label> 677 <input id="rSalary" class="ctl" type="number" placeholder="60000"> 678 </div> 679 <div class="field"> 680 <label>Тип занÑÑоÑÑи</label> 681 <select id="rType" class="ctl"> 682 <option value="full">ÐÐ¾Ð»Ð½Ð°Ñ Ð·Ð°Ð½ÑÑоÑÑÑ</option> 683 <option value="part">ЧаÑÑиÑнаÑ</option> 684 <option value="contract">ÐоговоÑ/пÑоекÑ</option> 685 <option value="remote">УдалÑнно</option> 686 </select> 687 </div> 688 </div> 689 <div class="field" style="margin-bottom:0"> 690 <label>ÐпÑÑ Ð¸ навÑки</label> 691 <textarea id="rDesc" class="ctl" placeholder="Ðде ÑабоÑали, на каком обоÑÑдовании, ÑÑо ÑмееÑеâ¦"></textarea> 692 </div> 693 </div> 694 695 <!-- ÐонÑакÑÑ --> 696 <div class="form-row"> 697 <div class="field"> 698 <label>ТелеÑон</label> 699 <input id="rPhone" class="ctl" placeholder="+7 900 000-00-00"> 700 </div> 701 <div class="field"> 702 <label>Telegram</label> 703 <input id="rTg" class="ctl" placeholder="username"> 704 </div> 705 </div> 706 <div style="font-size:12px;color:var(--muted);margin-bottom:12px;">УкажиÑе Ñ Ð¾ÑÑ Ð±Ñ Ð¾Ð´Ð¸Ð½ ÑпоÑоб ÑвÑзи</div> 707 <div class="form-error" id="resumeError"></div> 708 <button class="btn-submit" onclick="submitResume()">ÐпÑбликоваÑÑ ÑезÑме беÑплаÑно</button> 709 </div> 710 </div> 711 712 </div> 713 </div> 714 715</div> 716 717<!-- JOB DETAIL MODAL --> 718<div class="modal-overlay" id="jobModal" onclick="if(event.target===this)closeModal()">
719 <div class="modal"> 720 <button class="modal-close" onclick="closeModal()">â</button> 721 <div id="modalContent"></div> 722 </div> 723</div> 724 725<footer> 726 <div class="footer-logo">Ind<span>Mart</span></div> 727 <div style="display:flex;justify-content:center;gap:20px;margin:8px 0;"> 728 <a href="/" style="color:var(--muted);text-decoration:none;">ÐлавнаÑ</a> 729 <a href="/listings" style="color:var(--muted);text-decoration:none;">ÐбÑÑвлениÑ</a> 730 <a href="/baza" style="color:var(--muted);text-decoration:none;">Ðаза знаний</a> 731 <a href="/upgrade" style="color:var(--muted);text-decoration:none;">ТаÑиÑÑ</a> 732 </div> 733 <div>© 2026 IndMart · РабоÑа в пиÑевой пÑомÑÑленноÑÑи</div> 734</footer> 735
736<script> 737const API = '/api'; 738let currentTab = 'vacancies'; 739let filters = {}; 740let jobs = []; 741let geoTimer = null; 742 743// ââ Auth ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 744const token = localStorage.getItem('indmart_token'); 745const userStr = localStorage.getItem('indmart_user'); 746let currentUser = null; 747if (userStr) try { currentUser = JSON.parse(userStr); } catch(e) {} 748 749function authHeaders() { 750 return token ? { 'Authorization': 'Bearer ' + token } : {}; 751} 752 753// ÐниÑиализаÑÐ¸Ñ Ð½Ð°Ð²Ð±Ð°Ñа 754(function() { 755 var u = localStorage.getItem('indmart_user'); 756 var t = localStorage.getItem('indmart_token'); 757 if (u && t) { 758 try { 759 var user = JSON.parse(u); 760 var name = user.username || user.email || 'User'; 761 document.getElementById('navUser').style.display = 'flex'; 762 document.getElementById('navLoginBtn').style.display = 'none'; 763 document.getElementById('navAvatar').textContent = name[0].toUpperCase(); 764 document.getElementById('navName').textContent = '@' + name; 765 // ÐваÑÐ°Ñ ÑоÑо 766 if (user.photo_url) { 767 var img = document.createElement('img'); 768 img.src = user.photo_url; 769 img.style.cssText = 'width:100%;height:100%;object-fit:cover;border-radius:50%;'; 770 document.getElementById('navAvatar').textContent = ''; 771 document.getElementById('navAvatar').appendChild(img); 772 } 773 } catch(e) {} 774 } 775})(); 776function logout() { 777 localStorage.removeItem('indmart_token'); 778 localStorage.removeItem('indmart_user'); 779 location.reload(); 780} 781function toggleUserMenu() { 782 var m = document.getElementById('userDropdown'); 783 m.style.display = m.style.display === 'none' ? 'block' : 'none'; 784} 785document.addEventListener('click', function(e) { 786 var m = document.getElementById('userDropdown'); 787 if (m && !e.target.closest('#navUser')) m.style.display = 'none'; 788}); 789 790// ââ Tabs ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 791function showTab(tab) { 792 currentTab = tab; 793 document.getElementById('pageVacancies').style.display = tab === 'vacancies' ? 'block' : 'none'; 794 document.getElementById('pageResumes').style.display = tab === 'resumes' ? 'block' : 'none'; 795 document.getElementById('pageNew').style.display = tab === 'new' ? 'block' : 'none'; 796 document.getElementById('tabVacancies').classList.toggle('active', tab === 'vacancies'); 797 document.getElementById('tabResumes').classList.toggle('active', tab === 'resumes'); 798 document.getElementById('tabNew').classList.toggle('active', tab === 'new'); 799 if (tab === 'vacancies') loadJobs(); 800 if (tab === 'resumes') loadResumes(); 801 if (tab === 'new') backToChoose(); // ÐÑегда ÑбÑаÑÑваем к вÑбоÑÑ Ñипа 802} 803 804// ââ Industry chips ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 805function setIndustry(val, el) { 806 document.querySelectorAll('.chip').forEach(c => c.classList.remove('active')); 807 el.classList.add('active'); 808 document.getElementById('fIndustry').value = val; 809 filters.industry = val; 810 loadJobs(); 811} 812 813// ââ Load resumes ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 814async function loadResumes() { 815 const page = document.getElementById('pageResumes'); 816 page.innerHTML = '<div style="text-align:center;padding:40px;color:var(--muted);">ÐагÑÑзка...</div>'; 817 try { 818 const params = new URLSearchParams({ limit: 20 }); 819 if (filters.search) params.set('search', filters.search); 820 if (filters.industry) params.set('category_slug', filters.industry); 821 if (filters.city) params.set('city', filters.city); 822 if (filters.type) params.set('employment_type', filters.type); 823 824 const res = await fetch(API + '/resumes?' + params); 825 const data = await res.json(); 826 const resumes = data.resumes || []; 827 const total = data.total || resumes.length; 828 829 if (!resumes.length) { 830 page.innerHTML = ` 831 <div class="empty"> 832 <h3>РезÑме пока неÑ</h3> 833 <p>СÑанÑÑе пеÑвÑм â ÑазмеÑÑиÑе ÑезÑме беÑплаÑно</p> 834 <button onclick="showTab('new')" style="margin-top:16px;padding:10px 24px;background:var(--orange);color:white;border:none;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;">РазмеÑÑиÑÑ ÑезÑме</button> 835 </div>`; 836 return; 837 } 838 839 const expMap = { no_exp: 'Ðез опÑÑа', '1-3': '1-3 года', '3-6': '3-6 леÑ', '6+': 'Ðолее 6 леÑ' }; 840 const typeMap = { full: 'ÐолнаÑ', part: 'ЧаÑÑиÑнаÑ', contract: 'ÐоговоÑ', remote: 'УдалÑнно' }; 841 842 page.innerHTML = ` 843 <div class="stats-row"><span>Ðайдено: <strong>${total}</strong> ÑезÑме</span></div> 844 <div class="jobs-list"> 845 ${resumes.map(r => ` 846 <div class="job-card" onclick="window.location='/resumes/${r.id}'"> 847 <div class="job-logo">${(r.name||'?')[0].toUpperCase()}</div> 848 <div class="job-body"> 849 <div class="job-title">${esc(r.title)}</div> 850 <div class="job-company">${esc(r.name)}</div> 851 <div class="job-meta">
852 ${r.city ? `<span class="job-badge badge-city">ð ${esc(r.city)}</span>` : ''} 853 ${r.experience ? `<span class="job-badge badge-type">${expMap[r.experience] || r.experience}</span>` : ''} 854 ${r.employment_type ? `<span class="job-badge badge-industry">${typeMap[r.employment_type] || r.employment_type}</span>` : ''} 855 </div> 856 </div> 857 <div class="job-right"> 858 ${r.salary_from ? `<div class="job-salary">Ð¾Ñ ${Number(r.salary_from).toLocaleString('ru-RU')} â½</div>` : '<div class="job-salary" style="font-size:13px;color:var(--muted);">Ðо договоÑÑнноÑÑи</div>'} 859 <div class="job-date">${timeAgo(r.created_at)}</div> 860 </div> 861 </div>`).join('')} 862 </div>`; 863 } catch(e) { 864 page.innerHTML = `<div class="empty"><h3>РезÑме пока неÑ</h3><p>РазмеÑÑиÑе пеÑвое ÑезÑме</p><button onclick="showTab('new')" style="margin-top:16px;padding:10px 24px;background:var(--orange);color:white;border:none;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;">РазмеÑÑиÑÑ ÑезÑме</button></div>`; 865 } 866} 867 868function openResume(r) { 869 const expMap = { no_exp: 'Ðез опÑÑа', '1-3': '1-3 года', '3-6': '3-6 леÑ', '6+': 'Ðолее 6 леÑ' }; 870 const typeMap = { full: 'ÐÐ¾Ð»Ð½Ð°Ñ Ð·Ð°Ð½ÑÑоÑÑÑ', part: 'ЧаÑÑиÑнаÑ', contract: 'ÐоговоÑ', remote: 'УдалÑнно' }; 871 872 let contactBlock = ''; 873 if (!currentUser) { 874 contactBlock = ` 875 <div style="background:#f8f9fc;border:1.5px solid var(--border);border-radius:10px;padding:20px;text-align:center;margin-top:16px;"> 876 <div style="font-size:14px;color:var(--muted);margin-bottom:12px;">ÐойдиÑе ÑÑÐ¾Ð±Ñ ÑвидеÑÑ ÐºÐ¾Ð½ÑакÑÑ ÑоиÑкаÑелÑ.<br><strong style="color:var(--orange)">7 дней Pro беÑплаÑно</strong> поÑле ÑегиÑÑÑаÑии.</div> 877 <a href="/listings" style="display:inline-block;padding:10px 24px;background:var(--navy);color:white;border-radius:8px;font-size:14px;font-weight:600;text-decoration:none;">ÐойÑи / ÐаÑегиÑÑÑиÑоваÑÑÑÑ</a> 878 </div>`; 879 } else if (r.phone || r.telegram_username) { 880 contactBlock = `<div style="margin-top:16px;display:flex;flex-direction:column;gap:10px;">`; 881 if (r.phone) contactBlock += `<a href="tel:${esc(r.phone)}" class="btn-contact">ð ÐозвониÑÑ: ${esc(r.phone)}</a>`; 882 if (r.telegram_username) contactBlock += `<a href="https://t.me/${esc(r.telegram_username.replace('@',''))}" target="_blank" class="btn-contact" style="background:var(--navy);">âï¸ Telegram: @${esc(r.telegram_username.replace('@',''))}</a>`; 883 contactBlock += `</div>`; 884 } 885 886 document.getElementById('modalContent').innerHTML = ` 887 <h2>${esc(r.title)}</h2> 888 <div class="detail-row"><span class="detail-key">СоиÑкаÑелÑ</span><span class="detail-val">${esc(r.name)}</span></div> 889 <div class="detail-row"><span class="detail-key">ÐоÑод</span><span class="detail-val">${esc(r.city || 'â')}</span></div> 890 <div class="detail-row"><span class="detail-key">ÐпÑÑ</span><span class="detail-val">${expMap[r.experience] || 'â'}</span></div> 891 <div class="detail-row"><span class="detail-key">ÐанÑÑоÑÑÑ</span><span class="detail-val">${typeMap[r.employment_type] || 'â'}</span></div> 892 ${r.salary_from ? `<div class="detail-row"><span class="detail-key">ÐжиданиÑ</span><span class="detail-val">Ð¾Ñ ${Number(r.salary_from).toLocaleString('ru-RU')} â½</span></div>` : ''} 893 ${r.about ? `<div class="detail-desc">${esc(r.about)}</div>` : ''} 894 ${r.description ? `<div class="detail-desc" style="margin-top:8px;">${esc(r.description)}</div>` : ''} 895 ${contactBlock} 896 <div style="margin-top:12px;text-align:center;"> 897 <button onclick="reportObject('resume',${r.id})" style="background:none;border:none;color:var(--muted);font-size:12px;cursor:pointer;font-family:inherit;text-decoration:underline;">ÐожаловаÑÑÑÑ</button> 898 </div>`; 899 document.getElementById('jobModal').classList.add('open'); 900} 901 902// ââ Load jobs âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 903async function loadJobs() { 904 const list = document.getElementById('jobsList'); 905 const empty = document.getElementById('emptyJobs'); 906 list.innerHTML = '<div style="text-align:center;padding:40px;color:var(--muted);">ÐагÑÑзка...</div>'; 907 908 try { 909 const params = new URLSearchParams({ limit: 20 }); 910 if (filters.search) params.set('search', filters.search); 911 if (filters.industry) params.set('category_slug', filters.industry); 912 if (filters.city) params.set('city', filters.city); 913 if (filters.type) params.set('employment_type', filters.type); 914 915 const res = await fetch(API + '/jobs?' + params); 916 const data = await res.json(); 917 jobs = data.jobs || data || []; 918 const total = data.total || jobs.length; 919 920 document.getElementById('statsText').innerHTML = `Ðайдено: <strong>${total}</strong> ваканÑий`; 921 if (total) document.getElementById('heroCount').textContent = total + ' ваканÑий'; 922 923 if (!jobs.length) { 924 list.innerHTML = ''; 925 empty.style.display = 'block'; 926 return; 927 } 928 empty.style.display = 'none'; 929 list.innerHTML = '';
930 jobs.forEach(j => list.appendChild(makeJobCard(j))); 931 } catch(e) { 932 // ÐоказÑваем демо-ваканÑии еÑли API не гоÑов 933 showDemoJobs(); 934 } 935} 936 937function makeJobCard(j) { 938 const div = document.createElement('div'); 939 div.className = 'job-card' + (j.is_urgent ? ' urgent' : ''); 940 div.onclick = () => window.location = '/jobs/' + j.id; 941 div.style.cursor = 'pointer'; 942 943 const salary = j.salary_from || j.salary_to 944 ? (j.salary_from ? 'Ð¾Ñ ' + Number(j.salary_from).toLocaleString('ru-RU') : '') + 945 (j.salary_to ? ' до ' + Number(j.salary_to).toLocaleString('ru-RU') + ' â½' : ' â½') 946 : 'Ðо договоÑÑнноÑÑи'; 947 948 const typeMap = { full: 'ÐолнаÑ', part: 'ЧаÑÑиÑнаÑ', contract: 'ÐоговоÑ', remote: 'УдалÑнно' }; 949 const initial = (j.company || j.title || 'Ð')[0].toUpperCase(); 950 951 div.innerHTML = ` 952 <div class="job-logo">${initial}</div> 953 <div class="job-body"> 954 <div class="job-title">${esc(j.title)}</div> 955 <div class="job-company">${esc(j.company || 'ÐÐ¾Ð¼Ð¿Ð°Ð½Ð¸Ñ Ð½Ðµ Ñказана')}</div> 956 <div class="job-meta"> 957 ${j.city ? `<span class="job-badge badge-city">ð ${esc(j.city)}</span>` : ''} 958 ${j.employment_type ? `<span class="job-badge badge-type">${typeMap[j.employment_type] || j.employment_type}</span>` : ''} 959 ${j.is_urgent ? '<span class="job-badge badge-urgent">СÑоÑно</span>' : ''} 960 </div> 961 </div> 962 <div class="job-right"> 963 <div class="job-salary">${salary}</div> 964 <div class="job-date">${timeAgo(j.created_at)}</div> 965 </div>`; 966 return div; 967} 968 969function openJob(j) { 970 const typeMap = { full: 'ÐÐ¾Ð»Ð½Ð°Ñ Ð·Ð°Ð½ÑÑоÑÑÑ', part: 'ЧаÑÑиÑнаÑ', contract: 'ÐоговоÑ', remote: 'УдалÑнно' }; 971 const salary = j.salary_from || j.salary_to 972 ? (j.salary_from ? 'Ð¾Ñ ' + Number(j.salary_from).toLocaleString('ru-RU') : '') + 973 (j.salary_to ? ' до ' + Number(j.salary_to).toLocaleString('ru-RU') + ' â½' : ' â½') 974 : 'Ðо договоÑÑнноÑÑи'; 975 976 let contactBlock = ''; 977 if (!currentUser) { 978 contactBlock = ` 979 <div style="background:#f8f9fc;border:1.5px solid var(--border);border-radius:10px;padding:20px;text-align:center;margin-top:16px;"> 980 <div style="font-size:14px;color:var(--muted);margin-bottom:12px;">ÐойдиÑе ÑÑÐ¾Ð±Ñ ÑвидеÑÑ ÐºÐ¾Ð½ÑакÑÑ ÑабоÑодаÑелÑ.<br><strong style="color:var(--orange)">7 дней Pro беÑплаÑно</strong> поÑле ÑегиÑÑÑаÑии.</div> 981 <a href="/listings" style="display:inline-block;padding:10px 24px;background:var(--navy);color:white;border-radius:8px;font-size:14px;font-weight:600;text-decoration:none;">ÐойÑи / ÐаÑегиÑÑÑиÑоваÑÑÑÑ</a> 982 </div>`; 983 } else if (j.phone || j.telegram_username) { 984 contactBlock = `<div style="margin-top:16px;display:flex;flex-direction:column;gap:10px;">`; 985 if (j.phone) contactBlock += `<a href="tel:${esc(j.phone)}" class="btn-contact">ð ÐозвониÑÑ: ${esc(j.phone)}</a>`; 986 if (j.telegram_username) contactBlock += `<a href="https://t.me/${esc(j.telegram_username.replace('@',''))}" target="_blank" class="btn-contact" style="background:var(--navy);">âï¸ ÐапиÑаÑÑ Ð² Telegram: @${esc(j.telegram_username.replace('@',''))}</a>`; 987 contactBlock += `</div>`; 988 } else { 989 contactBlock = `<div style="margin-top:16px;color:var(--muted);font-size:14px;text-align:center;">ÐонÑакÑÑ Ð½Ðµ ÑказанÑ</div>`; 990 } 991 992 document.getElementById('modalContent').innerHTML = ` 993 <h2>${esc(j.title)}</h2> 994 <div class="detail-row"><span class="detail-key">ÐомпаниÑ</span><span class="detail-val">${esc(j.company || 'Ðе Ñказана')}</span></div> 995 <div class="detail-row"><span class="detail-key">ÐоÑод</span><span class="detail-val">${esc(j.city || 'Ðе Ñказан')}</span></div> 996 <div class="detail-row"><span class="detail-key">ÐаÑплаÑа</span><span class="detail-val">${salary}</span></div> 997 <div class="detail-row"><span class="detail-key">ÐанÑÑоÑÑÑ</span><span class="detail-val">${typeMap[j.employment_type] || 'â'}</span></div> 998 ${j.description ? `<div class="detail-desc">${esc(j.description)}</div>` : ''} 999 ${contactBlock} 1000 <div style="margin-top:12px;text-align:center;"> 1001 <button onclick="reportObject('job',${j.id})" style="background:none;border:none;color:var(--muted);font-size:12px;cursor:pointer;font-family:inherit;text-decoration:underline;">ÐожаловаÑÑÑÑ</button> 1002 </div>`; 1003 1004 document.getElementById('jobModal').classList.add('open'); 1005} 1006 1007function closeModal() { 1008 document.getElementById('jobModal').classList.remove('open'); 1009} 1010 1011async function reportObject(type, id) { 1012 const reason = await uiPrompt('ÐÑиÑина жалобÑ:\n- Спам\n- Фейк\n- ÐеÑензÑÑное\n- ÐÑÑгое\n\nÐведиÑе пÑиÑинÑ:'); 1013 if (!reason) return; 1014 try { 1015 const res = await fetch(API + '/report', { 1016 method: 'POST', 1017 headers: { 'Content-Type': 'application/json', ...authHeaders() }, 1018 body: JSON.stringify({ type, object_id: id, reason }) 1019 }); 1020 const data = await res.json(); 1021 if (data.status === 'already_reported') { 1022 uiToast('ÐÑ Ñже жаловалиÑÑ Ð½Ð° ÑÑо обÑÑвление.'); 1023 } else { 1024 uiToast('Ðалоба оÑпÑавлена. ÐÑ ÑаÑÑмоÑÑим ÐµÑ Ð² ближайÑее вÑемÑ.'); 1025 } 1026 } catch(e) { 1027 uiToast('ÐÑибка оÑпÑавки жалобÑ. ÐопÑобÑйÑе позже.'); 1028 } 1029} 1030 1031// ââ Demo jobs (пока Ð½ÐµÑ Ð´Ð°Ð½Ð½ÑÑ ) âââââââââââââââââââââââââââââââââââââââââââââââ 1032function showDemoJobs() { 1033 const demoJobs = [ 1034 { id: 1, title: 'ÐекаÑÑ', company: 'ÐекаÑÐ½Ñ "Хлеб да СолÑ"', city: 'ÐоÑква', salary_from: 60000, salary_to: 80000, employment_type: 'full', is_urgent: true, created_at: new Date().toISOString() }, 1035 { id: 2, title: 'Ð¢ÐµÑ Ð½Ð¾Ð»Ð¾Ð³ Ñ Ð»ÐµÐ±Ð¾Ð¿ÐµÐºÐ°Ñного пÑоизводÑÑва', company: 'ÐÐРХлебозавод â3', city: 'СанкÑ-ÐеÑеÑбÑÑг', salary_from: 80000, salary_to: 120000, employment_type: 'full', created_at: new Date(Date.now()-86400000).toISOString() }, 1036 { id: 3, title: 'ÐÐµÑ Ð°Ð½Ð¸Ðº по ÑемонÑÑ Ð¿ÐµÐºÐ°Ñного обоÑÑдованиÑ', company: 'ЧаÑÑÐ½Ð°Ñ Ð¿ÐµÐºÐ°ÑнÑ', city: 'ÐÑаÑнодаÑ', salary_from: 70000, salary_to: 100000, employment_type: 'full', created_at: new Date(Date.now()-172800000).toISOString() }, 1037 { id: 4, title: 'ÐондиÑеÑ', company: 'ÐондиÑеÑÑÐºÐ°Ñ "Сладкий миÑ"', city: 'ÐкаÑеÑинбÑÑг', salary_from: 55000, salary_to: 75000, employment_type: 'full', created_at: new Date(Date.now()-259200000).toISOString() }, 1038 ]; 1039 jobs = demoJobs; 1040 document.getElementById('statsText').innerHTML = `Ðайдено: <strong>${demoJobs.length}</strong> ваканÑии`; 1041 document.getElementById('heroCount').textContent = demoJobs.length + ' ваканÑии'; 1042 document.getElementById('emptyJobs').style.display = 'none'; 1043 const list = document.getElementById('jobsList'); 1044 list.innerHTML = '';
1045 demoJobs.forEach(j => list.appendChild(makeJobCard(j))); 1046} 1047 1048// ââ Type chooser ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1049function chooseType(type) { 1050 document.getElementById('stepChoose').style.display = 'none'; 1051 document.getElementById('formVacancy').style.display = type === 'vacancy' ? 'block' : 'none'; 1052 document.getElementById('formResume').style.display = type === 'resume' ? 'block' : 'none'; 1053} 1054 1055function backToChoose() { 1056 document.getElementById('stepChoose').style.display = 'block'; 1057 document.getElementById('formVacancy').style.display = 'none'; 1058 document.getElementById('formResume').style.display = 'none'; 1059} 1060 1061// ââ Submit vacancy ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1062async function submitVacancy() { 1063 const company = document.getElementById('vCompany').value.trim(); 1064 const title = document.getElementById('vTitle').value.trim(); 1065 const industry = document.getElementById('vIndustry').value; 1066 const city = document.getElementById('vCity').value.trim(); 1067 const phone = document.getElementById('vPhone').value.trim(); 1068 const tg = document.getElementById('vTg').value.trim(); 1069 const errEl = document.getElementById('vacancyError'); 1070 1071 if (!company) { errEl.textContent = 'ÐведиÑе название компании'; errEl.style.display = 'block'; return; } 1072 if (!title) { errEl.textContent = 'ÐведиÑе должноÑÑÑ'; errEl.style.display = 'block'; return; } 1073 if (!industry) { errEl.textContent = 'ÐÑбеÑиÑе оÑÑаÑлÑ'; errEl.style.display = 'block'; return; } 1074 if (!city) { errEl.textContent = 'УкажиÑе гоÑод'; errEl.style.display = 'block'; return; } 1075 if (!phone && !tg) { errEl.textContent = 'УкажиÑе ÑпоÑоб ÑвÑзи'; errEl.style.display = 'block'; return; } 1076 1077 errEl.style.display = 'none'; 1078 const btn = document.querySelector('#formVacancy .btn-submit'); 1079 btn.disabled = true; btn.textContent = 'ÐÑбликÑемâ¦'; 1080 1081 try { 1082 const res = await fetch(API + '/jobs', { 1083 method: 'POST', 1084 headers: { 'Content-Type': 'application/json', ...authHeaders() }, 1085 body: JSON.stringify({ 1086 title, company, 1087 company_description: document.getElementById('vCompanyDesc').value.trim() || null, 1088 category_slug: industry, city, 1089 employment_type: document.getElementById('vType').value, 1090 salary_from: parseInt(document.getElementById('vSalaryFrom').value) || null, 1091 salary_to: parseInt(document.getElementById('vSalaryTo').value) || null, 1092 description: document.getElementById('vDesc').value.trim() || null, 1093 phone: phone || null, 1094 telegram_username: tg || null, 1095 }) 1096 }); 1097 if (!res.ok) throw new Error((await res.json().catch(()=>({}))).detail || 'ÐÑибка'); 1098 showSuccess('formVacancy', 'ÐаканÑÐ¸Ñ Ð¾Ð¿Ñбликована!'); 1099 } catch(e) { 1100 errEl.textContent = e.message; errEl.style.display = 'block'; 1101 btn.disabled = false; btn.textContent = 'ÐпÑбликоваÑÑ Ð²Ð°ÐºÐ°Ð½ÑÐ¸Ñ Ð±ÐµÑплаÑно'; 1102 } 1103} 1104 1105// ââ Submit resume âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1106async function submitResume() { 1107 const name = document.getElementById('rName').value.trim(); 1108 const title = document.getElementById('rTitle').value.trim(); 1109 const city = document.getElementById('rCity').value.trim(); 1110 const phone = document.getElementById('rPhone').value.trim(); 1111 const tg = document.getElementById('rTg').value.trim(); 1112 const errEl = document.getElementById('resumeError'); 1113 1114 if (!name) { errEl.textContent = 'ÐведиÑе имÑ'; errEl.style.display = 'block'; return; } 1115 if (!title) { errEl.textContent = 'ÐведиÑе желаемÑÑ Ð´Ð¾Ð»Ð¶Ð½Ð¾ÑÑÑ'; errEl.style.display = 'block'; return; } 1116 if (!city) { errEl.textContent = 'УкажиÑе гоÑод'; errEl.style.display = 'block'; return; } 1117 if (!phone && !tg) { errEl.textContent = 'УкажиÑе ÑпоÑоб ÑвÑзи'; errEl.style.display = 'block'; return; } 1118 1119 errEl.style.display = 'none'; 1120 const btn = document.querySelector('#formResume .btn-submit'); 1121 btn.disabled = true; btn.textContent = 'ÐÑбликÑемâ¦'; 1122 1123 try { 1124 const res = await fetch(API + '/resumes', { 1125 method: 'POST', 1126 headers: { 'Content-Type': 'application/json', ...authHeaders() }, 1127 body: JSON.stringify({ 1128 name, title, city, 1129 category_slug: document.getElementById('rIndustry').value || null, 1130 experience: document.getElementById('rExp').value, 1131 employment_type: document.getElementById('rType').value, 1132 salary_from: parseInt(document.getElementById('rSalary').value) || null, 1133 about: document.getElementById('rAbout').value.trim() || null, 1134 description: document.getElementById('rDesc').value.trim() || null, 1135 phone: phone || null, 1136 telegram_username: tg || null, 1137 }) 1138 }); 1139 if (!res.ok) throw new Error((await res.json().catch(()=>({}))).detail || 'ÐÑибка'); 1140 showSuccess('formResume', 'РезÑме опÑбликовано!'); 1141 } catch(e) { 1142 errEl.textContent = e.message; errEl.style.display = 'block'; 1143 btn.disabled = false; btn.textContent = 'ÐпÑбликоваÑÑ ÑезÑме беÑплаÑно'; 1144 } 1145} 1146 1147function showSuccess(formId, msg) { 1148 document.querySelector(`#${formId} .form-card`).innerHTML = ` 1149 <div style="text-align:center;padding:40px;"> 1150 <svg width="52" height="52" viewBox="0 0 24 24" fill="none" stroke="#059669" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="margin-bottom:16px;"><circle cx="12" cy="12" r="10"/><polyline points="8 12 11 15 16 9"/></svg> 1151 <h2 style="font-family:'Montserrat',sans-serif;font-weight:800;font-size:22px;color:var(--navy);margin-bottom:12px;">${msg}</h2> 1152 <p style="color:var(--muted);margin-bottom:24px;">ÐаÑе обÑÑвление Ñже видно ÑоиÑкаÑелÑм.</p> 1153 <button onclick="showTab('vacancies')" style="padding:12px 28px;background:var(--orange);color:white;border:none;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;">СмоÑÑеÑÑ Ð²Ð°ÐºÐ°Ð½Ñии â</button> 1154 </div>`; 1155} 1156 1157// ââ Search ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1158function doSearch() { 1159 filters = { 1160 search: document.getElementById('fSearch').value.trim(), 1161 industry: document.getElementById('fIndustry').value, 1162 city: document.getElementById('fCity').value.trim(), 1163 type: document.getElementById('fType').value, 1164 }; 1165 loadJobs(); 1166} 1167 1168// ââ Geo Autocomplete ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1169async function geoSuggest(input, dropId) { 1170 const q = input.value.trim(); 1171 const drop = document.getElementById(dropId); 1172 if (!drop) return; 1173 if (q.length < 2) { drop.classList.remove('open'); return; } 1174 clearTimeout(geoTimer); 1175 geoTimer = setTimeout(async () => { 1176 try { 1177 const res = await fetch(API + '/geo/suggest?q=' + encodeURIComponent(q)); 1178 const data = await res.json(); 1179 const items = data.suggestions || []; 1180 if (!items.length) { drop.classList.remove('open'); return; } 1181 drop.innerHTML = items.map(s => 1182 `<div class="geo-option" onclick="geoSelect('${esc(s.name)}','${input.id}','${dropId}')">${esc(s.name)}</div>` 1183 ).join(''); 1184 drop.classList.add('open'); 1185 } catch(e) {} 1186 }, 200); 1187} 1188 1189function geoSelect(val, inputId, dropId) { 1190 const input = document.getElementById(inputId); 1191 const drop = document.getElementById(dropId); 1192 if (input) input.value = val; 1193 if (drop) { drop.classList.remove('open'); } 1194} 1195 1196function geoKeydown(e, input, dropId) { 1197 const drop = document.getElementById(dropId); 1198 if (!drop || !drop.classList.contains('open')) return; 1199 const items = drop.querySelectorAll('.geo-option'); 1200 const active = drop.querySelector('.geo-option.active'); 1201 let idx = Array.from(items).indexOf(active); 1202 if (e.key === 'ArrowDown') { e.preventDefault(); if (active) active.classList.remove('active'); items[Math.min(idx+1,items.length-1)]?.classList.add('active'); } 1203 else if (e.key === 'ArrowUp') { e.preventDefault(); if (active) active.classList.remove('active'); items[Math.max(idx-1,0)]?.classList.add('active'); } 1204 else if (e.key === 'Enter' && active) { e.preventDefault(); geoSelect(active.textContent, input.id, dropId); } 1205 else if (e.key === 'Escape') drop.classList.remove('open'); 1206} 1207 1208document.addEventListener('click', e => { 1209 document.querySelectorAll('.geo-dropdown.open').forEach(d => { 1210 if (!d.previousElementSibling?.contains(e.target)) d.classList.remove('open'); 1211 }); 1212}); 1213 1214// ââ Utils âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1215function esc(s) { 1216 return String(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); 1217} 1218function timeAgo(iso) { 1219 if (!iso) return ''; 1220 const diff = (Date.now() - new Date(iso)) / 1000; 1221 if (diff < 3600) return Math.floor(diff/60) + ' мин назад'; 1222 if (diff < 86400) return Math.floor(diff/3600) + ' Ñ Ð½Ð°Ð·Ð°Ð´'; 1223 if (diff < 604800) return Math.floor(diff/86400) + ' дн назад'; 1224 return new Date(iso).toLocaleDateString('ru-RU'); 1225} 1226 1227// Init 1228const _newParam = new URLSearchParams(window.location.search).get('new'); 1229if (_newParam === 'vacancy') { showTab('new'); chooseType('vacancy'); } 1230else if (_newParam === 'resume') { showTab('new'); chooseType('resume'); } 1231else loadJobs(); 1232</script>
1232 1233</body> 1234</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.