1// Progressive enhancement of the preserved public /future/ comparison section. 2// Keep React-owned cards in place; add status, grouping and explanatory labels 3// only once their existing containers have hydrated. 4(() => { 5 'use strict'; 6 if (window.__avareangeCapabilities) return; 7 window.__avareangeCapabilities = true; 8 const data = {"reviewed":"2026-09-29","roadmapSource":"https://avareange.ru/roadmap","features":[{"id":"command","status":"released","labelRu":"ÐоманднÑй ÑенÑÑ","labelEn":"Command center","captionRu":"ÐÐ¾Ð¼Ð¿Ð°Ð½Ð¸Ñ â оÑÐ´ÐµÐ»Ñ â пÑиоÑиÑеÑÑ","captionEn":"Company â teams â priorities"},{"id":"chat","status":"released","labelRu":"LLM-анализ ÑообÑений","labelEn":"LLM message analysis","captionRu":"СообÑение â пÑизнаки ÑгÑÐ¾Ð·Ñ â дейÑÑвие","captionEn":"Message â warning signs â action"},{"id":"osint","status":"released","labelRu":"OSINT-аÑаки","labelEn":"OSINT-based attacks","captionRu":"ÐÑкÑÑÑÑе даннÑе â конÑекÑÑ â ÑÑÐµÐ±Ð½Ð°Ñ Ð°Ñака","captionEn":"Public data â context â training attack"},{"id":"profile","status":"released","labelRu":"ÐÑоÑÐ¸Ð»Ñ ÑиÑка ÑоÑÑÑдника","labelEn":"Employee risk profile","captionRu":"ÐейÑÑÐ²Ð¸Ñ ÑоÑÑÑдника â лиÑнÑй пÑоÑÐ¸Ð»Ñ ÑиÑка","captionEn":"Employee actions â individual risk profile"},{"id":"leak","status":"released","labelRu":"ÐониÑоÑинг ÑÑеÑек паÑолей","labelEn":"Password leak monitoring","captionRu":"УÑÑÑÐ½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑ â обнаÑÑжение ÑÑеÑки â Ñигнал","captionEn":"Account â detected leak â alert"},{"id":"agent","status":"released","stage":"DONE","target":null,"task":"c49e3ccc-f1bc-4d02-baf5-e5366af8f4cb","labelRu":"ÐÐ-Ð°Ð³ÐµÐ½Ñ Ð¾Ð±ÑÑениÑ","labelEn":"AI learning agent","captionRu":"ÐÑибка â пеÑÑоналÑнÑй ÑÑок â пÑовеÑка","captionEn":"Mistake â personal lesson â assessment","confirmedBy":"owner","confirmedAt":"2026-09-29"},{"id":"plugin","status":"released","stage":"DONE","target":null,"task":"57bc385d-ba27-4af1-bddd-2e4f43909e7b","labelRu":"Шаблон из ÑеалÑного пиÑÑма","labelEn":"Template from a real email","captionRu":"ÐиÑÑмо â Ñдаление ÑгÑоз â ÑÑебнÑй Ñаблон","captionEn":"Email â threat removal â training template","confirmedBy":"owner","confirmedAt":"2026-09-29"},{"id":"domain","status":"roadmap","stage":"PRIORITY","target":null,"task":"8d5fe9c3-86ac-4ff6-a81b-375377a77380","labelRu":"Ðнализ доменов в бÑаÑзеÑе","labelEn":"Browser domain analysis","captionRu":"ÐдÑÐµÑ ÑайÑа â пÑовеÑка â пÑедÑпÑеждение","captionEn":"Website address â check â warning"},{"id":"calendar","status":"roadmap","stage":"IN_PROGRESS","target":null,"task":"5ba1c3ed-1f2d-4770-a91c-f1adc28492c3","labelRu":"ÐалендаÑÑ Ñ ÐÐ","labelEn":"AI calendar","captionRu":"Цели ÐºÐ¾Ð¼Ð°Ð½Ð´Ñ â план â ÑобÑÑÐ¸Ñ Ð² календаÑе","captionEn":"Team goals â plan â calendar events"}]}; 9 const text = (en, ru, english) => en ? english : ru; 10 const element = (tag, className, content) => { 11 const el = document.createElement(tag); 12 el.className = className; 13 if (content) el.textContent = content; 14 return el; 15 }; 16 function steps(items) { 17 const el = element('div', 'cap-steps'); 18 items.forEach((item, i) => { 19 const step = element('div', 'cap-step'); 20 step.append(element('span', 'cap-step-number', String(i + 1).padStart(2, '0')), element('span', '', item)); 21 el.append(step); 22 }); 23 return el; 24 } 25 function visual(id, en) { 26 if (id === 'osint') return steps(en ? ['Public company data', 'A familiar work situation', 'A safe training attack'] : ['ÐÑкÑÑÑÑе даннÑе о компании', 'ÐÐ½Ð°ÐºÐ¾Ð¼Ð°Ñ ÑабоÑÐ°Ñ ÑиÑÑаÑиÑ', 'ÐезопаÑÐ½Ð°Ñ ÑÑÐµÐ±Ð½Ð°Ñ Ð°Ñака']); 27 if (id === 'agent') return steps(en ? ['Mistake in a simulation', 'A lesson on that topic', 'Follow-up asse
27ssment'] : ['ÐÑибка в ÑимÑлÑÑии', 'УÑок по Ñеме оÑибки', 'ÐовÑоÑÐ½Ð°Ñ Ð¿ÑовеÑка']); 28 if (id === 'plugin') return steps(en ? ['Reported suspicious email', 'Remove malicious elements', 'Prepare a training template'] : ['ÐодозÑиÑелÑное пиÑÑмо', 'Удаление опаÑнÑÑ ÑлеменÑов', 'ÐоÑовÑй ÑÑебнÑй Ñаблон']); 29 if (id === 'domain') { 30 const el = element('div', 'cap-browser'); 31 el.append(element('div', 'cap-address', 'company-login.example'), element('div', 'cap-domain-warning', text(en, 'ÐдÑÐµÑ Ð¿Ð¾Ñ Ð¾Ð¶ на подделкÑ', 'Possible lookalike domain')), element('div', 'cap-domain-reference', text(en, 'СÑавнение: company.example', 'Compare: company.example'))); 32 return el; 33 } 34 if (id === 'calendar') { 35 const el = element('div', 'cap-calendar'); 36 for (const [day, event] of (en ? [['MON','Simulation'],['WED','Debrief'],['FRI','Training']] : [['ÐÐ','СимÑлÑÑиÑ'],['СР','РазбоÑ'],['ÐТ','ÐбÑÑение']])) { 37 const row = element('div', 'cap-calendar-event'); 38 row.append(element('span', '', day), element('strong', '', event));el.append(row); 39 } 40 return el; 41 } 42 return null; 43 } 44 function groupHeader(en, planned, count) { 45 const el = element('li', 'cap-group-header' + (planned ? ' cap-group-header--planned' : '')); 46 el.dataset.capGroup = planned ? 'roadmap' : 'released'; 47 const header = element('div', 'cap-group-title'); 48 header.append(element('span', 'cap-count', String(count)), element('h3', '', planned ? text(en,'РдоÑожной каÑÑе','On the roadmap') : text(en,'Уже доÑÑÑпно','Available now'))); 49 el.append(header, element('p', '', planned ? text(en,'ÐÑдÑÑие возможноÑÑи. СÑаÑÑÑ Ð¸ плановÑй ÑÑок â на каждой каÑÑоÑке.','Future capabilities. Each card shows its status and planned timing.') : text(en,'ФÑнкÑии плаÑÑоÑмÑ, коÑоÑÑми Ñже можно полÑзоваÑÑÑÑ. ÐоÑÑÑпноÑÑÑ Ð·Ð°Ð²Ð¸ÑÐ¸Ñ Ð¾Ñ ÑаÑиÑа.','Platform capabilities available today. Availability depends on your plan.'))); 50 return el; 51 } 52 function enhance(section) { 53 const en = location.pathname.startsWith('/en/'); 54 const cards = [...section.querySelectorAll('.fx-bento-card')]; 55 if (cards.length !== data.features.length || !cards.every(card => data.features.some(f=>f.id===card.dataset.visual))) return; 56 const list = section.querySelector('.fx-bento'); 57 const ready = data.features.filter(f=>f.status==='released').length; 58 const planned = data.features.length-ready; 59 const heading = section.querySelector('#compare-title'); 60 if (heading) { 61 heading.replaceChildren(document.createTextNode(text(en,`${ready} возможноÑÑей Ñже доÑÑÑпнÑ.`,`${ready} capabilities available now.`)), element('span','cap-head-accent',text(en,`ÐÑÑ ${planned} â в доÑожной каÑÑе.`,`${planned} more on the roadmap.`))); 62 heading.classList.add('cap-heading'); 63 } 64 const summary = section.querySelector('.fx-exclusive-score'); 65 if (summary) { 66 summary.replaceChildren(); 67 const description = element('p','cap-summary',text(en,'ÐÑенка ÑиÑка, заÑиÑа и обÑÑение ÑоÑÑÑдников: дейÑÑвÑÑÑие ÑÑнкÑии и ÑледÑÑÑие Ñаги ÑазвиÑиÑ.','Risk assessment, protection and employee training: current capabilities and the next steps.')); 68 const link = element('a','cap-roadmap-jump',text(en,'ÐеÑейÑи к планам â','Explore planned features â')); 69 link.href='#capabilities-roadmap';summary.append(description,link); 70 } 71 list.prepend(groupHeader(en,false,ready)); 72 const firstPlanned = cards.find(c=>data.features.find(f=>f.id===c.dataset.visual).status==='roadmap').parentElement; 73 const roadmapHeader = groupHeader(en,true,planned);roadmapHeader.id='capabilities-roadmap'; 74 firstPlanned.before(roadmapHeader); 75 cards.forEach(card => { 76 const feature = data.features.find(f=>f.id===card.dataset.visual); 77 card.dataset.status=feature.status; 78 const cell = card.parentElement; 79 cell.classList.add('cap-cell');cell.dataset.capStatus=feature.status; 80 const released = feature.status==='released'; 81 const status = element('div','cap-status-row'); 82 const label = released ? text(en,'â Уже доÑÑÑпно','â Available now') : feature.stage==='PRIORITY' ? text(en,'ⷠРпÑиоÑиÑеÑе','â· Prioritized') : text(en,'â· Ð ÑазÑабоÑке','â· In development'); 83 status.append(element('span',released?'cap-status cap-status--ready':'cap-status cap-status--
83planned',label)); 84 if (!released) status.append(element('span','cap-target',feature.target ? text(en,'Ðлан: ','Target: ')+feature.target : text(en,'СÑок не обÑÑвлен','Timing not announced'))); 85 card.prepend(status); 86 card.querySelector('.fx-bento-roadmap')?.remove(); 87 const box = card.querySelector('.fx-bento-visual'); 88 const replacement = visual(feature.id,en); 89 if (replacement) box.replaceChildren(replacement); 90 const figure = element('figure','cap-figure'); 91 box.before(figure);figure.append(box,element('figcaption','cap-caption',text(en,feature.captionRu,feature.captionEn))); 92 const oldTitle = card.querySelector('h3'); 93 if (oldTitle) { 94 const title = element('h4','cap-card-title',text(en,feature.labelRu,feature.labelEn));oldTitle.replaceWith(title); 95 } 96 if (!released) { 97 const note = element('a','cap-roadmap-link',text(en,'СÑаÑÑÑ Ð² доÑожной каÑÑе â','View roadmap status â')); 98 note.href='/roadmap/';card.append(note); 99 } 100 }); 101 const footnote = section.querySelector('.fx-footnote'); 102 if (footnote) footnote.textContent=text(en,'Ð ÑÑавниÑелÑной ÑаблиÑе 42 кÑиÑеÑиÑ: 40 возможноÑÑей доÑÑÑпнÑ, еÑÑ 2 â в доÑожной каÑÑе. ÐÑо оÑделÑÐ½Ð°Ñ Ð¾Ñенка, а не ÑиÑло ÑÑнкÑий вÑÑе. ÐллÑÑÑÑаÑии показÑваÑÑ ÑÑенаÑии ÑабоÑÑ. ÐлановÑе ÑÑнкÑии еÑÑ Ð½Ðµ доÑÑÑпнÑ; ÑÑоки могÑÑ Ð¼ÐµÐ½ÑÑÑÑÑ.','The comparison table covers 42 criteria: 40 available capabilities and 2 on the roadmap. This is a separate measure, not the number of capabilities above. Illustrations show example workflows. Planned features are not yet available; timing may change.'); 103 // Keep the compact roadmap summary consistent with the two owner-confirmed releases. 104 const pulse=document.querySelector('.fx-pulse-head'); 105 if (pulse) { 106 const readyCount=pulse.querySelector('div[data-tone="safe"] dt'); 107 const workingCount=pulse.querySelector('div[data-tone="ion"] dt'); 108 if (readyCount && workingCount && readyCount.textContent==='18' && workingCount.textContent==='12') { 109 readyCount.textContent='20';workingCount.textContent='10'; 110 pulse.querySelector('.fx-pulse-bar [data-tone="safe"]').style.flexGrow='20'; 111 pulse.querySelector('.fx-pulse-bar [data-tone="ion"]').style.flexGrow='10'; 112 } 113 } 114 section.dataset.capabilitiesEnhanced='true'; 115 } 116 let retry; 117 function mount() { 118 clearTimeout(retry); 119 const section=document.querySelector('#compare.fx-exclusive'); 120 if (!section || section.dataset.capabilitiesEnhanced) return; 121 const cards=[...section.querySelectorAll('.fx-bento-card')]; 122 if (!cards.length) return; 123 if (window.__next_f && !cards.every(card=>Object.keys(card).some(key=>key.startsWith('__reactFiber$')))) {retry=setTimeout(mount,100);return;} 124 enhance(section); 125 } 126 function start(){mount();new MutationObserver(mount).observe(document.body,{childList:true,subtree:true});}
127 if(document.readyState==='complete')start();else window.addEventListener('load',start,{once:true}); 128})();
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.