1(() => { 2 const root = document.documentElement; 3 const body = document.body; 4 const sharedHeader = document.querySelector('[data-header]'); 5 if (sharedHeader) { 6 sharedHeader.querySelector('.brand img')?.classList.add('brand-logo'); 7 const solutionsItem = sharedHeader.querySelector('.desktop-nav > .nav-item:first-child'); 8 const solutionsTrigger = solutionsItem?.querySelector('button'); 9 solutionsItem?.classList.add('nav-item-mega'); 10 solutionsTrigger?.classList.add('nav-link-button'); 11 const oldSolutionChevron = solutionsTrigger?.querySelector(':scope > span[aria-hidden="true"]'); 12 if (oldSolutionChevron) { 13 oldSolutionChevron.outerHTML = '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="m4 6 4 4 4-4"/></svg>'; 14 } 15 sharedHeader.querySelectorAll('.mega-grid').forEach(grid => grid.classList.add('mega-menu-grid')); 16 sharedHeader.querySelectorAll('.mega-menu-glow').forEach(glow => glow.remove()); 17 const aboutTrigger = sharedHeader.querySelector('.nav-item-about > button'); 18 aboutTrigger?.classList.add('nav-link-button'); 19 const oldAboutChevron = aboutTrigger?.querySelector(':scope > span[aria-hidden="true"]'); 20 if (oldAboutChevron) { 21 oldAboutChevron.outerHTML = '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="m4 6 4 4 4-4"/></svg>'; 22 } 23 const headerRobot = sharedHeader.querySelector('.mega-feature-robot'); 24 const headerRobotIcon = sharedHeader.querySelector('.mega-robot-icon img'); 25 [headerRobot, headerRobotIcon].forEach(image => { 26 if (image instanceof HTMLImageElement) { 27 image.src = '/robots/robot-general-brain-clean.png'; 28 image.width = 900; 29 image.height = 720; 30 } 31 }); 32 const themeButton = sharedHeader.querySelector('[data-theme-toggle]'); 33 if (themeButton && !themeButton.querySelector('svg')) { 34 themeButton.innerHTML = '<svg class="icon-sun" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.42 1.42M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.42-1.42M17.66 6.34l1.41-1.41"/></svg><svg class="icon-moon" viewBox="0 0 24 24" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z"/></svg>'; 35 } 36 } 37 const header = document.querySelector('[data-header]'); 38 const menuToggle = document.querySelector('[data-menu-toggle]'); 39 const mobileMenu = document.querySelector('[data-mobile-menu]'); 40 const themeToggle = document.querySelector('[data-theme-toggle]'); 41 const navItem = document.querySelector('.nav-item'); 42 const navTrigger = document.querySelector('.nav-menu-trigger'); 43 const mobileSticky = document.querySelector('[data-mobile-sticky]'); 44 const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)'); 45 const mobileViewport = window.matchMedia('(max-width: 760px)'); 46 const systemTheme = window.matchMedia('(prefers-color-scheme: light)'); 47 const english = root.lang === 'en'; 48 root.classList.add('has-js'); 49 let lastFocused = null; 50 let stickyDismissed = false; 51 52 const setAttribution = scope => { 53 const params = new URLSearchParams(window.location.search); 54 scope.querySelectorAll('input[name="page_url"]').forEach(input => { input.value = window.location.href; }); 55 scope.querySelectorAll('input[name="source_campaign"]').forEach(input => { input.value = params.get('utm_campaign') || params.get('source') || ''; }); 56 }; 57 const emitConversion = (action, extra = {}) => { 58 const detail = { action, path: window.location.pathname, language: root.lang || 'sr', ...extra }; 59 window.dispatchEvent(new CustomEvent('cybercompany:conversion', { detail })); 60 if (Array.isArray(window.dataLayer)) window.dataLayer.push({ event: 'cybercompany_conversion', ...detail }); 61 }; 62 setAttribution(document); 63 64 document.addEventListener('invalid', event => { 65 if (event.target.matches('input, select, textarea')) event.target.setAttribute('aria-invalid', 'true'); 66 }, true); 67 document.addEventListener('input', event => { 68 if (event.target.matches('input, select, textarea') && event.target.checkValidity()) event.target.removeAttribute('aria-invalid'); 69 }); 70 document.addEventListener('change', event => { 71 if (event.target.matches('input, select, textarea') && event.target.checkValidity()) event.target.removeAttribute('aria-invalid'); 72 }); 73 74 try { stickyDismissed = sessionStorage.getItem('cc-product-sticky-dismissed') === 'true'; } catch {} 75 76 const hasThemeOverride = () => { 77 const queryTheme = new URLSearchParams(window.location.search).get('theme'); 78 if (queryTheme === 'light' || queryTheme === 'dark') return true; 79 try { 80 const saved = localStorage.getItem('cc-theme'); 81 return saved === 'light' || saved === 'dark'; 82 } catch { 83 return false;
84 } 85 }; 86 const setTheme = (theme, persist = true) => { 87 root.dataset.theme = theme; 88 if (persist) { 89 try { localStorage.setItem('cc-theme', theme); } catch {} 90 } 91 themeToggle?.setAttribute('aria-label', theme === 'dark' 92 ? (english ? 'Switch to light theme' : 'Prebaci na svetlu temu') 93 : (english ? 'Switch to dark theme' : 'Prebaci na tamnu temu')); 94 }; 95 setTheme(root.dataset.theme === 'light' ? 'light' : 'dark', false); 96 themeToggle?.addEventListener('click', () => setTheme(root.dataset.theme === 'dark' ? 'light' : 'dark')); 97 systemTheme.addEventListener?.('change', event => { 98 if (!hasThemeOverride()) setTheme(event.matches ? 'light' : 'dark', false); 99 }); 100 101 const updateSticky = () => { 102 const visible = mobileViewport.matches && !stickyDismissed && window.scrollY > Math.min(520, window.innerHeight * .62) && !body.classList.contains('is-locked'); 103 mobileSticky?.classList.toggle('is-visible', visible); 104 mobileSticky?.setAttribute('aria-hidden', String(!visible)); 105 if (mobileSticky) mobileSticky.inert = !visible; 106 body.classList.toggle('has-mobile-sticky', visible); 107 }; 108 const updateChrome = () => { 109 header?.classList.toggle('is-scrolled', window.scrollY > 16); 110 updateSticky(); 111 }; 112 updateChrome(); 113 window.addEventListener('scroll', updateChrome, { passive: true }); 114 window.addEventListener('resize', updateSticky, { passive: true }); 115 116 document.querySelector('[data-sticky-close]')?.addEventListener('click', () => { 117 stickyDismissed = true; 118 try { sessionStorage.setItem('cc-product-sticky-dismissed', 'true'); } catch {} 119 updateSticky(); 120 }); 121 122 const closeMenu = () => { 123 menuToggle?.setAttribute('aria-expanded', 'false'); 124 mobileMenu?.classList.remove('is-open'); 125 mobileMenu?.setAttribute('aria-hidden', 'true'); 126 if (mobileMenu) mobileMenu.inert = true; 127 header?.classList.remove('is-menu-open'); 128 }; 129 menuToggle?.addEventListener('click', () => { 130 const open = menuToggle.getAttribute('aria-expanded') !== 'true'; 131 menuToggle.setAttribute('aria-expanded', String(open)); 132 mobileMenu?.classList.toggle('is-open', open); 133 mobileMenu?.setAttribute('aria-hidden', String(!open)); 134 if (mobileMenu) mobileMenu.inert = !open; 135 header?.classList.toggle('is-menu-open', open); 136 }); 137 mobileMenu?.querySelectorAll('a').forEach(link => link.addEventListener('click', closeMenu)); 138 139 navTrigger?.addEventListener('click', () => { 140 const open = navTrigger.getAttribute('aria-expanded') !== 'true'; 141 navTrigger.setAttribute('aria-expanded', String(open)); 142 navItem?.classList.toggle('is-open', open); 143 }); 144 document.addEventListener('click', event => { 145 if (navItem && !navItem.contains(event.target)) { 146 navItem.classList.remove('is-open'); 147 navTrigger?.setAttribute('aria-expanded', 'false'); 148 } 149 }); 150 151 const openOverlay = element => { 152 if (!element) return; 153 setAttribution(element); 154 lastFocused = document.activeElement; 155 element.inert = false; 156 element.classList.add('is-open'); 157 element.setAttribute('aria-hidden', 'false'); 158 body.classList.add('is-locked'); 159 updateSticky(); 160 window.setTimeout(() => element.querySelector('input,select,textarea,button,[href]')?.focus(), 60); 161 }; 162 const resetOverlayForm = overlay => { 163 overlay?.querySelectorAll('[data-overlay-form]').forEach(form => { 164 form.reset(); 165 form.hidden = false; 166 }); 167 overlay?.querySelectorAll('[data-overlay-success]').forEach(success => { success.hidden = true; }); 168 }; 169 const closeOverlay = element => { 170 if (!element?.classList.contains('is-open')) return; 171 element.classList.remove('is-open'); 172 element.setAttribute('aria-hidden', 'true'); 173 element.inert = true; 174 window.setTimeout(() => resetOverlayForm(element), 210); 175 if (!document.querySelector('.modal-backdrop.is-open,.preview-lightbox.is-open')) { 176 body.classList.remove('is-locked'); 177 updateSticky(); 178 if (lastFocused instanceof HTMLElement) lastFocused.focus(); 179 } 180 }; 181 182 document.querySelectorAll('[data-modal-open]').forEach(button => button.addEventListener('click', () => { 183 closeMenu(); 184 const modalName = button.dataset.modalOpen || 'consultation'; 185 const modal = document.querySelector(`[data-modal="${modalName}"]`); 186 openOverlay(modal); 187 })); 188 document.querySelectorAll('[data-modal-close]').forEach(button => button.addEventListener('click', () => closeOverlay(button.closest('[data-modal]')))); 189 document.querySelectorAll('[data-modal]').forEach(modal => modal.addEventListener('click', event => { if (event.target === modal) closeOverlay(modal); })); 190 document.querySelectorAll('[data-overlay-form]').forEach(form => form.addEventListener('submit', event => { 191 event.preventDefault(); 192 emitConversion('form-submit', { form: 'consultation' }); 193 const success = form.parentElement?.querySelector('[data-overlay-success]'); 194 form.hidden = true; 195 if (success) { 196 success.hidden = false;
197 success.focus(); 198 } 199 })); 200 201 document.querySelectorAll('[data-main-form]').forEach(form => form.addEventListener('submit', event => { 202 event.preventDefault(); 203 emitConversion('form-submit', { form: 'main-contact' }); 204 const success = form.querySelector('[data-main-success]'); 205 if (success) { 206 success.hidden = false; 207 success.focus(); 208 } 209 })); 210 211 const caseDataNode = document.querySelector('#case-study-data'); 212 let caseStudies = []; 213 try { caseStudies = JSON.parse(caseDataNode?.textContent || '[]'); } catch {} 214 const caseModal = document.querySelector('[data-case-modal]'); 215 const setCaseText = (selector, value) => { const node = caseModal?.querySelector(selector); if (node) node.textContent = value || ''; }; 216 const fillCaseList = (selector, items) => { 217 const list = caseModal?.querySelector(selector); if (!list) return; 218 list.replaceChildren(...items.map(item => { const li = document.createElement('li'); li.textContent = item; return li; })); 219 }; 220 const openCaseStudy = slug => { 221 const item = caseStudies.find(entry => entry.slug === slug); if (!item || !caseModal) return; 222 caseModal.style.setProperty('--case-accent', item.accent); 223 setCaseText('[data-case-symbol]', item.symbol); setCaseText('[data-case-category]', item.category); setCaseText('[data-case-client]', item.client); setCaseText('[data-case-industry]', item.industry); setCaseText('[data-case-title]', item.title); setCaseText('[data-case-summary]', item.summary); 224 const logo = caseModal.querySelector('[data-case-logo]'); 225 if (logo) { logo.src = item.logo || ''; logo.alt = `${item.client} logo`; logo.hidden = !item.logo; } 226 fillCaseList('[data-case-challenge]', item.challenge); fillCaseList('[data-case-approach]', item.approach); fillCaseList('[data-case-outcomes]', item.outcomes); 227 const proof = item.metrics.length ? item.metrics : item.highlights.map((label, index) => ({ value: `0${index + 1}`, label })); 228 const metrics = caseModal.querySelector('[data-case-metrics]'); 229 if (metrics) metrics.replaceChildren(...proof.map(metric => { const span = document.createElement('span'); const strong = document.createElement('strong'); const small = document.createElement('small'); strong.textContent = metric.value; small.textContent = metric.label; span.append(strong, small); return span; })); 230 const note = caseModal.querySelector('[data-case-note]'); if (note) { note.textContent = item.note || ''; note.hidden = !item.note; } 231 const link = caseModal.querySelector('[data-case-link]'); if (link) link.href = item.path; 232 openOverlay(caseModal); 233 }; 234 document.querySelectorAll('[data-case-open]').forEach(button => button.addEventListener('click', () => openCaseStudy(button.dataset.caseOpen))); 235 document.querySelectorAll('[data-case-filter]').forEach(button => button.addEventListener('click', () => { 236 const category = button.dataset.caseFilter; document.querySelectorAll('[data-case-filter]').forEach(filter => filter.setAttribute('aria-pressed', String(filter === button))); 237 let visible = 0; document.querySelectorAll('[data-case-card]').forEach(card => { const show = category === 'all' || card.dataset.caseCategory === category; card.hidden = !show; if (show) visible += 1; }); 238 const empty = document.querySelector('[data-case-empty]'); if (empty) empty.hidden = visible > 0; 239 })); 240 caseModal?.querySelector('[data-case-close]')?.addEventListener('click', () => closeOverlay(caseModal)); 241 caseModal?.addEventListener('click', event => { if (event.target === caseModal) closeOverlay(caseModal); }); 242 caseModal?.querySelector('[data-modal-open]')?.addEventListener('click', () => closeOverlay(caseModal)); 243 244 document.querySelectorAll('[data-carousel]').forEach(carousel => { 245 const slides = [...carousel.querySelectorAll('[data-carousel-slide]')]; 246 const dots = [...carousel.querySelectorAll('[data-carousel-dot]')]; 247 const pauseButton = carousel.querySelector('[data-carousel-pause]'); 248 let index = 0; 249 let paused = reducedMotion.matches; 250 let timer = null; 251 252 const show = next => { 253 index = (next + slides.length) % slides.length;
254 slides.forEach((slide, slideIndex) => { 255 const active = slideIndex === index; 256 slide.classList.toggle('is-active', active); 257 slide.setAttribute('aria-hidden', String(!active)); 258 }); 259 dots.forEach((dot, dotIndex) => { 260 const active = dotIndex === index; 261 dot.classList.toggle('is-active', active); 262 dot.setAttribute('aria-selected', String(active)); 263 }); 264 }; 265 const schedule = () => { 266 window.clearInterval(timer); 267 if (!paused && slides.length > 1) timer = window.setInterval(() => show(index + 1), 5200); 268 }; 269 carousel.querySelector('[data-carousel-prev]')?.addEventListener('click', () => { show(index - 1); schedule(); }); 270 carousel.querySelector('[data-carousel-next]')?.addEventListener('click', () => { show(index + 1); schedule(); }); 271 dots.forEach((dot, dotIndex) => dot.addEventListener('click', () => { show(dotIndex); schedule(); })); 272 pauseButton?.addEventListener('click', () => { 273 paused = !paused; 274 pauseButton.textContent = paused ? (english ? 'â¶ RESUME' : 'â¶ NASTAVI') : (english ? 'â ¡ PAUSE' : 'â ¡ PAUZA'); 275 pauseButton.setAttribute('aria-label', paused ? (english ? 'Resume automatic rotation' : 'Nastavi automatsku promenu') : (english ? 'Pause automatic rotation' : 'Pauziraj automatsku promenu')); 276 schedule(); 277 }); 278 carousel.addEventListener('keydown', event => { 279 if (event.key === 'ArrowLeft') { event.preventDefault(); show(index - 1); schedule(); } 280 if (event.key === 'ArrowRight') { event.preventDefault(); show(index + 1); schedule(); } 281 if (event.key === ' ') { event.preventDefault(); pauseButton?.click(); } 282 }); 283 show(0); 284 schedule(); 285 }); 286 287 const lightbox = document.querySelector('[data-lightbox]'); 288 const lightboxBody = lightbox?.querySelector('[data-lightbox-body]'); 289 const lightboxLabel = lightbox?.querySelector(':scope > span'); 290 document.querySelectorAll('[data-preview-open]').forEach(trigger => trigger.addEventListener('click', event => { 291 if (event.target.closest?.('.carousel-controls')) return; 292 const activeSlide = trigger.querySelector('.app-slide.is-active') || trigger.querySelector('.app-slide'); 293 if (!activeSlide || !lightboxBody) return; 294 lightboxBody.replaceChildren(activeSlide.cloneNode(true)); 295 if (lightboxLabel) lightboxLabel.textContent = activeSlide.classList.contains('app-slide-screenshot') 296 ? (english ? 'Available demo application view' : 'Prikaz dostupne demo aplikacije') 297 : (english ? 'Clearly labelled illustrative view' : 'Jasno oznaÄen ilustrativni prikaz'); 298 openOverlay(lightbox); 299 })); 300 301 document.querySelectorAll('[data-product-tour]').forEach(tour => { 302 const dataNode = tour.querySelector('[data-product-tour-data]'); 303 let items = []; 304 try { items = JSON.parse(dataNode?.textContent || '[]'); } catch {} 305 const tabs = [...tour.querySelectorAll('[data-product-tour-tab]')]; 306 const media = tour.querySelector('[data-product-tour-media]'); 307 const image = tour.querySelector('[data-product-tour-image]'); 308 const kicker = tour.querySelector('[data-product-tour-kicker]'); 309 const title = tour.querySelector('[data-product-tour-title]'); 310 const description = tour.querySelector('[data-product-tour-description]'); 311 const points = tour.querySelector('[data-product-tour-points]'); 312 313 const showItem = index => { 314 const item = items[index]; 315 if (!item) return;
316 tabs.forEach((tab, tabIndex) => { 317 const active = tabIndex === index; 318 tab.classList.toggle('is-active', active); 319 tab.setAttribute('aria-selected', String(active)); 320 tab.tabIndex = active ? 0 : -1; 321 }); 322 if (media) { 323 media.dataset.productTourZoom = item.src; 324 media.setAttribute('aria-label', `${english ? 'Enlarge' : 'UveÄaj'} ${item.label}`); 325 } 326 if (image) { 327 image.src = item.src; 328 image.alt = item.alt; 329 } 330 if (kicker) kicker.textContent = item.kicker; 331 if (title) title.textContent = item.title; 332 if (description) description.textContent = item.description; 333 if (points) points.replaceChildren(...item.points.map(point => { 334 const listItem = document.createElement('li'); 335 listItem.textContent = point; 336 return listItem; 337 })); 338 }; 339 340 tabs.forEach((tab, index) => { 341 tab.addEventListener('click', () => showItem(index)); 342 tab.addEventListener('keydown', event => { 343 if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return; 344 event.preventDefault(); 345 const direction = event.key === 'ArrowRight' ? 1 : -1; 346 const nextIndex = (index + direction + tabs.length) % tabs.length; 347 showItem(nextIndex); 348 tabs[nextIndex]?.focus(); 349 }); 350 }); 351 showItem(0); 352 }); 353 354 document.querySelectorAll('[data-illustrative-tour]').forEach(tour => { 355 const tabs = [...tour.querySelectorAll('[data-illustrative-tour-tab]')]; 356 const panels = [...tour.querySelectorAll('[data-illustrative-tour-panel]')]; 357 const showPanel = index => {
358 tabs.forEach((tab, tabIndex) => { 359 const active = tabIndex === index; 360 tab.classList.toggle('is-active', active); 361 tab.setAttribute('aria-selected', String(active)); 362 tab.tabIndex = active ? 0 : -1; 363 }); 364 panels.forEach((panel, panelIndex) => { 365 const active = panelIndex === index; 366 panel.hidden = !active; 367 panel.classList.toggle('is-active', active); 368 }); 369 }; 370 tabs.forEach((tab, index) => { 371 tab.addEventListener('click', () => showPanel(index)); 372 tab.addEventListener('keydown', event => { 373 if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return; 374 event.preventDefault(); 375 const next = (index + (event.key === 'ArrowRight' ? 1 : -1) + tabs.length) % tabs.length; 376 showPanel(next); 377 tabs[next]?.focus(); 378 }); 379 }); 380 showPanel(0); 381 }); 382 383 document.querySelectorAll('[data-package-browser]').forEach(browser => { 384 const tabs = [...browser.querySelectorAll('[data-package-tab]')]; 385 const panels = [...browser.querySelectorAll('[data-package-panel]')]; 386 const showPackage = id => {
387 tabs.forEach(tab => { 388 const active = tab.dataset.packageTab === id; 389 tab.classList.toggle('is-active', active); 390 tab.setAttribute('aria-selected', String(active)); 391 tab.tabIndex = active ? 0 : -1; 392 }); 393 panels.forEach(panel => { 394 const active = panel.dataset.packagePanel === id; 395 panel.hidden = !active; 396 panel.classList.toggle('is-active', active); 397 }); 398 }; 399 tabs.forEach((tab, index) => { 400 tab.addEventListener('click', () => showPackage(tab.dataset.packageTab)); 401 tab.addEventListener('keydown', event => { 402 if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return; 403 event.preventDefault(); 404 const next = (index + (event.key === 'ArrowRight' ? 1 : -1) + tabs.length) % tabs.length; 405 showPackage(tabs[next].dataset.packageTab); 406 tabs[next].focus(); 407 }); 408 }); 409 }); 410 411 document.querySelectorAll('.package-tier-grid').forEach(grid => { 412 const packageDetails = [...grid.querySelectorAll('.package-tier > details')]; 413 let syncingPackageDetails = false; 414 packageDetails.forEach(detail => detail.addEventListener('toggle', () => { 415 if (syncingPackageDetails) return; 416 syncingPackageDetails = true; 417 packageDetails.forEach(item => { item.open = detail.open; }); 418 window.requestAnimationFrame(() => { syncingPackageDetails = false; }); 419 })); 420 }); 421 422 document.querySelectorAll('[data-track]').forEach(element => element.addEventListener('click', () => { 423 emitConversion(element.dataset.track); 424 })); 425 426 document.querySelectorAll('[data-product-tour-zoom], [data-product-mobile-zoom]').forEach(trigger => trigger.addEventListener('click', () => { 427 const source = trigger.dataset.productTourZoom || trigger.dataset.productMobileZoom; 428 const mobileView = Boolean(trigger.dataset.productMobileZoom); 429 if (!source || !lightboxBody) return; 430 const slide = document.createElement('article'); 431 slide.className = `app-slide app-slide-screenshot is-active${mobileView ? ' is-mobile-shot' : ''}`; 432 const preview = document.createElement('img'); 433 preview.src = source; 434 preview.alt = trigger.querySelector('img')?.alt || (english ? 'Enlarged demo application view' : 'UveÄani prikaz demo aplikacije'); 435 slide.append(preview); 436 lightboxBody.replaceChildren(slide); 437 if (lightboxLabel) lightboxLabel.textContent = mobileView 438 ? (english ? 'Mobile application view' : 'Mobilni prikaz aplikacije') 439 : (english ? 'Available demo application view' : 'Prikaz dostupne demo aplikacije'); 440 openOverlay(lightbox); 441 })); 442 document.querySelector('[data-lightbox-close]')?.addEventListener('click', () => closeOverlay(lightbox)); 443 lightbox?.addEventListener('click', event => { if (event.target === lightbox) closeOverlay(lightbox); }); 444 445 document.querySelectorAll('[data-accordion]').forEach(accordion => accordion.querySelectorAll('.faq-item > button').forEach(button => button.addEventListener('click', () => { 446 const item = button.closest('.faq-item'); 447 const open = !item.classList.contains('is-open'); 448 accordion.querySelectorAll('.faq-item').forEach(other => { 449 other.classList.remove('is-open'); 450 other.querySelector(':scope > button')?.setAttribute('aria-expanded', 'false'); 451 }); 452 if (open) { 453 item.classList.add('is-open'); 454 button.setAttribute('aria-expanded', 'true'); 455 } 456 }))); 457 458 document.querySelectorAll('[data-assessment]').forEach(assessment => { 459 const steps = [...assessment.querySelectorAll('[data-assessment-step]')]; 460 const progress = assessment.querySelector('[data-assessment-progress]'); 461 const count = assessment.querySelector('[data-assessment-count]'); 462 const result = assessment.querySelector('[data-assessment-result]'); 463 let stepIndex = 0; 464 const showStep = index => { 465 stepIndex = Math.max(0, Math.min(steps.length - 1, index)); 466 steps.forEach((step, current) => step.classList.toggle('is-active', current === stepIndex)); 467 if (progress) progress.style.width = `${(stepIndex / steps.length) * 100}%`; 468 if (count) count.textContent = `${stepIndex} od ${steps.length} oblasti`; 469 steps[stepIndex]?.querySelector('input')?.focus(); 470 }; 471 const showResult = () => { 472 const score = steps.reduce((total, step) =>
472 total + Number(step.querySelector('input:checked')?.value || 0), 0); 473 steps.forEach(step => step.classList.remove('is-active')); 474 if (!result) return; 475 result.hidden = false; 476 const scoreNode = result.querySelector('[data-assessment-score]'); 477 const titleNode = result.querySelector('[data-assessment-title]'); 478 const copyNode = result.querySelector('[data-assessment-copy]'); 479 const linksNode = result.querySelector('[data-assessment-links]'); 480 if (scoreNode) scoreNode.textContent = `${score} / 10`; 481 if (titleNode) titleNode.textContent = score >= 8 ? 'SluÄaj primene je spreman za pilot pripremu.' : score >= 5 ? 'Postoji osnova, ali nekoliko preduslova treba razjasniti.' : 'Prvi korak treba da bude ureÄenje problema, procesa ili izvora.'; 482 if (copyNode) copyNode.textContent = score >= 8 ? 'SledeÄi korak je razgovor o obimu, integracijama, rizicima i merilu uspeha.' : score >= 5 ? 'Pre pilota definiÅ¡ite vlasnika teme, odobrene izvore i granice ljudske potvrde.' : 'PoÄnite procenom ili kratkom AI strategijom pre izbora proizvoda.'; 483 if (progress) progress.style.width = '100%'; 484 if (count) count.textContent = `${steps.length} od ${steps.length} oblasti`; 485 if (linksNode) { 486 const recommendation = score >= 8 ? ['/kako-radi/implementacija/', 'Pripremite pilot'] : score >= 5 ? ['/kako-radi/integracije/', 'Pripremite izvore i integracije'] : ['/ai-resenja/ai-strategija/', 'PoÄnite AI strategijom']; 487 linksNode.innerHTML = `<button class="button button-primary" type="button" data-modal-open="consultation">Zakažite AI konsultaciju</button><a class="button button-secondary" href="${recommendation[0]}">${recommendation[1]}</a>`; 488 linksNode.querySelector('[data-modal-open]')?.addEventListener('click', () => openOverlay(document.querySelector('[data-modal="consultation"]'))); 489 } 490 result.focus(); 491 }; 492 assessment.querySelectorAll('[data-assessment-next]').forEach((button, index) => button.addEventListener('click', () => { 493 const selected = steps[index]?.querySelector('input:checked'); 494 if (!selected) { 495 steps[index]?.querySelector('label')?.focus?.(); 496 return; 497 } 498 if (index === steps.length - 1) showResult(); else showStep(index + 1); 499 })); 500 assessment.querySelectorAll('[data-assessment-prev]').forEach((button, index) => button.addEventListener('click', () => showStep(index - 1))); 501 showStep(0); 502 }); 503 504 const trapFocus = (event, overlay) => { 505 const focusable = [...overlay.querySelectorAll('button:not([disabled]),a[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])')].filter(element => !element.hidden && element.offsetParent !== null); 506 if (!focusable.length) return; 507 const first = focusable[0]; 508 const last = focusable[focusable.length - 1]; 509 if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); } 510 else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); } 511 }; 512 document.addEventListener('keydown', event => { 513 const openOverlayElement = document.querySelector('.modal-backdrop.is-open,.preview-lightbox.is-open'); 514 if (event.key === 'Tab' && openOverlayElement) trapFocus(event, openOverlayElement); 515 if (event.key !== 'Escape') return; 516 if (openOverlayElement) closeOverlay(openOverlayElement); 517 closeMenu(); 518 navItem?.classList.remove('is-open'); 519 navTrigger?.setAttribute('aria-expanded', 'false'); 520 }); 521 522 document.querySelectorAll('[data-chat-journey]').forEach(journey => { 523 const steps = [...journey.querySelectorAll('[data-chat-step]')]; 524 const messages = [...journey.querySelectorAll('[data-chat-message]')]; 525 const observations = [...journey.querySelectorAll('[data-chat-observation]')]; 526 const selectStep = index => { 527 steps.forEach((step, stepIndex) => { 528 const active = stepIndex === index; 529 step.classList.toggle('is-active', active); 530 step.setAttribute('aria-selected', String(active)); 531 });
532 messages.forEach((message, messageIndex) => message.classList.toggle('is-active', messageIndex === index)); 533 observations.forEach((observation, observationIndex) => { 534 const active = observationIndex === index; 535 observation.hidden = !active; 536 observation.classList.toggle('is-active', active); 537 }); 538 }; 539 steps.forEach((step, index) => { 540 step.addEventListener('click', () => selectStep(index)); 541 step.addEventListener('mouseenter', () => { 542 if (window.matchMedia('(hover: hover)').matches) selectStep(index); 543 }); 544 }); 545 messages.forEach((message, index) => message.addEventListener('mouseenter', () => { 546 if (window.matchMedia('(hover: hover)').matches) selectStep(index); 547 })); 548 }); 549 550 document.querySelectorAll('[data-chat-admin]').forEach(admin => { 551 const tabs = [...admin.querySelectorAll('[data-chat-admin-tab]')]; 552 const panels = [...admin.querySelectorAll('[data-chat-admin-panel]')]; 553 const selectPanel = name => { 554 tabs.forEach(tab => { 555 const active = tab.dataset.chatAdminTab === name; 556 tab.classList.toggle('is-active', active); 557 tab.setAttribute('aria-selected', String(active)); 558 }); 559 panels.forEach(panel => { 560 const active = panel.dataset.chatAdminPanel === name; 561 panel.hidden = !active; 562 panel.classList.toggle('is-active', active); 563 }); 564 };
565 tabs.forEach(tab => tab.addEventListener('click', () => selectPanel(tab.dataset.chatAdminTab))); 566 }); 567 568 const packageHelpTargets = [...document.querySelectorAll('[data-package-help]')]; 569 if (packageHelpTargets.length) { 570 const tooltip = document.createElement('div'); 571 tooltip.className = 'package-help-tooltip'; 572 tooltip.setAttribute('role', 'tooltip'); 573 tooltip.setAttribute('aria-hidden', 'true'); 574 body.appendChild(tooltip); 575 let activeHelp = null; 576 const placeHelp = target => { 577 const bounds = target.getBoundingClientRect(); 578 tooltip.style.left = '12px'; 579 tooltip.style.top = '12px'; 580 const tooltipBounds = tooltip.getBoundingClientRect(); 581 let left = bounds.left + bounds.width / 2 - tooltipBounds.width / 2; 582 left = Math.max(12, Math.min(left, window.innerWidth - tooltipBounds.width - 12)); 583 let top = bounds.top - tooltipBounds.height - 10; 584 if (top < 12) top = Math.min(window.innerHeight - tooltipBounds.height - 12, bounds.bottom + 10); 585 tooltip.style.left = `${Math.round(left)}px`; 586 tooltip.style.top = `${Math.round(top)}px`; 587 }; 588 const showHelp = target => { 589 activeHelp = target; 590 tooltip.textContent = target.dataset.packageHelp || ''; 591 tooltip.setAttribute('aria-hidden', 'false'); 592 tooltip.classList.add('is-visible'); 593 placeHelp(target); 594 }; 595 const hideHelp = target => { 596 if (activeHelp !== target) return; 597 tooltip.classList.remove('is-visible'); 598 tooltip.setAttribute('aria-hidden', 'true'); 599 activeHelp = null; 600 }; 601 packageHelpTargets.forEach(target => { 602 target.addEventListener('mouseenter', () => showHelp(target)); 603 target.addEventListener('mouseleave', () => hideHelp(target)); 604 target.addEventListener('focusin', () => showHelp(target)); 605 target.addEventListener('focusout', () => hideHelp(target)); 606 }); 607 window.addEventListener('resize', () => activeHelp && placeHelp(activeHelp), { passive: true }); 608 window.addEventListener('scroll', () => activeHelp && placeHelp(activeHelp), { passive: true, capture: true }); 609 } 610 611 const contextualRobotAssets = { 612 '#7C3AED': '/robots/robot-ceo-kpi-clean.png', 613 '#6B21A8': '/robots/robot-ceo-kpi-clean.png', 614 '#FACC15': '/robots/robot-cfo-finance-clean.png', 615 '#EAB308': '/robots/robot-cfo-finance-clean.png', 616 '#22C55E': '/robots/robot-sales-funnel-clean.png', 617 '#EC4899': '/robots/robot-marketing-campaign-clean.png', 618 '#84CC16': '/robots/robot-hr-people-clean.png', 619 '#3B82F6': '/robots/robot-legal-contract-clean.png', 620 '#F97316': '/robots/robot-it-diagnostics-clean.png', 621 '#14B8A6': '/robots/robot-procurement-suppliers-clean.png', 622 '#64748B': '/robots/robot-manufacturing-line-clean.png', 623 '#94A3B8': '/robots/robot-cybercore-core-clean-v2.png', 624 '#6D28D9': '/robots/robot-cybercore-core-clean-v2.png', 625 '#06B6D4': '/robots/robot-customer-success-clean.png' 626 }; 627 const assistantRouteRobotAssets = { 628 '/ai-resenja/ai-asistenti/ai-ceo-asistent/': '/robots/robot-ceo-kpi-clean.png', 629 '/ai-resenja/ai-asistenti/ai-cfo-asistent/': '/robots/robot-cfo-finance-clean.png', 630 '/ai-resenja/ai-asistenti/ai-prodavac/': '/robots/robot-sales-funnel-clean.png', 631 '/ai-resenja/ai-asistenti/ai-marketing-asistent/': '/robots/robot-marketing-campaign-clean.png', 632 '/ai-resenja/ai-asistenti/ai-hr-asistent/': '/robots/robot-hr-people-clean.png', 633 '/ai-resenja/ai-asistenti/ai-pravni-asistent/': '/robots/robot-legal-contract-clean.png', 634 '/ai-resenja/ai-asistenti/ai-it-servisni-asistent/': '/robots/robot-it-diagnostics-clean.png', 635 '/ai-resenja/ai-asistenti/ai-nabavka/': '/robots/robot-procurement-suppliers-clean.png', 636 '/ai-resenja/ai-asistenti/ai-proizvodnja/': '/robots/robot-manufacturing-line-clean.png', 637 '/ai-resenja/ai-asistenti/ai-customer-success/': '/robots/robot-customer-success-clean.png', 638 '/ai-resenja/ai-asistenti/cybercore/': '/robots/robot-cybercore-core-clean-v2.png' 639 }; 640 const contextualRobotRules = [ 641 ['.page-chatbot-detail,.chatbot-portfolio,.chatbot-hero-visual,.chatbot-journey,.chatbot-handoff-scene,.chatbot-handoff-warm,.chat-admin-shell,.chat-story', '/robots/robot-chatbot-conversation-clean-v2.png'], 642 ['.ai-assessment-experience,.assessment-report-simulation,.assessment-ribbon,.assessment-hero-visual', '/robots/robot-assessment-radar-clean-v2.png'], 643 ['.strategy-hero-visual,.strategy-opportunity-scene,.strategy-governance-scene', '/robots/robot-strategy-roadmap-clean.png'], 644 ['.package-choice-scene,.package-browser,.packages-hero-visual', '/robots/robot-assessment-radar-clean-v2.png'], 645 ['.case-hero-visual,.page-case-study,.related-proof-section', '/robots/robot-report-dashboard-clean.png'], 646 ['.how-story-section,.how-hero-visual', '/robots/robot-general-brain-clean.png'],
647 ['.solution-entry-section,.solution-context-strategy', '/robots/robot-strategy-roadmap-clean.png'], 648 ['.hero-ai-world', '/robots/robot-general-brain-clean.png'] 649 ]; 650 const motionTypeForRobot = source => { 651 const entries = [ 652 ['ceo-kpi', 'kpi'], ['cfo-finance', 'finance'], ['sales-funnel', 'funnel'], 653 ['marketing-campaign', 'campaign'], ['hr-people', 'people'], ['legal-contract', 'legal'], 654 ['it-diagnostics', 'diagnostics'], ['customer-success', 'customer'], 655 ['procurement-suppliers', 'procurement'], ['manufacturing-line', 'manufacturing'], 656 ['cybercore-core', 'core'], ['cybercore-brain', 'brain'], ['general-brain', 'brain'], ['chatbot-conversation', 'chat'], 657 ['assessment-radar', 'assessment'], ['strategy-roadmap', 'strategy'], ['report-dashboard', 'report'] 658 ]; 659 return entries.find(([token]) => source.includes(token))?.[1] || 'general'; 660 }; 661 const accentRobotFor = image => { 662 const accentScope = image.closest('[style*="--accent"]') || body; 663 const accent = getComputedStyle(accentScope).getPropertyValue('--accent').trim().toUpperCase(); 664 return contextualRobotAssets[accent]; 665 }; 666 const contextualRobotFor = image => { 667 if (image.matches('.mega-feature-robot')) return '/robots/robot-general-brain-clean.png'; 668 const linkedPath = image.closest('a[href]')?.getAttribute('href')?.replace(/^\/en(?=\/)/, ''); 669 if (linkedPath && assistantRouteRobotAssets[linkedPath]) return assistantRouteRobotAssets[linkedPath]; 670 for (const [selector, source] of contextualRobotRules) { 671 if (image.closest(selector)) return source; 672 } 673 const route = window.location.pathname.replace(/^\/en(?=\/)/, ''); 674 const normalizedRoute = route.endsWith('/') ? route : `${route}/`; 675 if (assistantRouteRobotAssets[normalizedRoute]) return assistantRouteRobotAssets[normalizedRoute]; 676 if (route.startsWith('/ai-resenja/ai-cetbotovi/')) return '/robots/robot-chatbot-conversation-clean-v2.png'; 677 if (route === '/ai-resenja/ai-strategija/' || route === '/ai-resenja/ai-strategija') return '/robots/robot-strategy-roadmap-clean.png'; 678 if (route === '/paketi/' || route === '/paketi') return '/robots/robot-assessment-radar-clean-v2.png'; 679 if (route === '/procena-ai-spremnosti/' || route === '/procena-ai-spremnosti') return '/robots/robot-assessment-radar-clean-v2.png'; 680 if (route.startsWith('/studije-slucaja/')) return '/robots/robot-report-dashboard-clean.png'; 681 if (route === '/kako-radi/' || route === '/kako-radi') return '/robots/robot-general-brain-clean.png'; 682 return accentRobotFor(image) || '/robots/robot-general-brain-clean.png'; 683 }; 684 document.querySelectorAll('img[src$="/ai-ceo-robot.webp"],img[src="/ai-ceo-robot.webp"]').forEach(image => { 685 if (!(image instanceof HTMLImageElement)) return; 686 const source = contextualRobotFor(image); 687 image.src = source; 688 if (image.closest('.mega-menu-v2')) return; 689 image.removeAttribute('width'); 690 image.removeAttribute('height'); 691 const wrapper = image.parentElement; 692 if (!wrapper) return; 693 wrapper.classList.add('has-contextual-robot'); 694 wrapper.style.setProperty('--robot-image', `url("${source}")`); 695 image.closest('.catalog-bot')?.style.setProperty('--robot-image', `url("${source}")`); 696 wrapper.dataset.robotMotion = motionTypeForRobot(source); 697 if (!wrapper.querySelector(':scope > .contextual-robot-motion')) { 698 const motion = document.createElement('span'); 699 motion.className = 'contextual-robot-motion'; 700 motion.setAttribute('aria-hidden', 'true'); 701 wrapper.appendChild(motion); 702 } 703 }); 704 document.querySelectorAll('img[src*="/robots/robot-"]').forEach(image => { 705 if (image.hasAttribute('data-robot-preserve')) return; 706 if (image.closest('.mega-menu-v2')) return; 707 const resolvedSource = contextualRobotFor(image); 708 if (resolvedSource && image.getAttribute('src') !== resolvedSource) image.src = resolvedSource; 709 const wrapper = image.parentElement; 710 if (!wrapper) return; 711 wrapper.classList.add('has-contextual-robot'); 712 wrapper.style.setProperty('--robot-image', `url("${resolvedSource || image.getAttribute('src')}")`); 713 image.closest('.catalog-bot')?.style.setProperty('--robot-image', `url("${resolvedSource || image.getAttribute('src')}")`); 714 image.closest('.assistant-card')?.style.setProperty('--robot-image', `url("${resolvedSource || image.getAttribute('src')}")`); 715 image.closest('.identity-robot-card')?.style.setProperty('--identity-robot-image', `url("${resolvedSource || image.getAttribute('src')}")`); 716 wrapper.dataset.robotMotion ||= motionTypeForRobot(resolvedSource || image.getAttribute('src') || ''); 717 if (!wrapper.querySelector(':scope > .contextual-robot-motion')) { 718 const motion = document.createElement('span'); 719 motion.className = 'contextual-robot-motion'; 720 motion.setAttribute('aria-hidden', 'true'); 721 wrapper.appendChild(motion); 722 } 723 }); 724 725 const touchCards = [...document.querySelectorAll([ 726 '.assistant-bot-card', '.portfolio-ceo', '.portfolio-core', '.chatbot-product-card', '.context-card', 727 '.assistant-flow-grid article', '.journey-steps article', '.solution-grid > a', '.evidence-grid article', 728 '.role-grid article', '.resource-grid a', '.trust-grid article', '.related-grid > a', 729 '.next-step-grid a', '.scope-grid article', '.blog-grid >
729 a', '.bullet-grid article', '.landing-flow-card', 730 '.assistant-architecture a', '.network-hotspot' 731 ].join(','))]; 732 const coarsePointer = window.matchMedia('(hover: none) and (pointer: coarse)'); 733 const closeTouchCards = except => touchCards.forEach(card => { 734 if (card !== except) card.classList.remove('is-touch-active'); 735 }); 736 touchCards.forEach(card => { 737 card.classList.add('touch-card'); 738 card.addEventListener('click', event => { 739 if (!coarsePointer.matches || event.target.closest('button,input,select,textarea')) return; 740 if (!card.classList.contains('is-touch-active')) { 741 event.preventDefault(); 742 closeTouchCards(card); 743 card.classList.add('is-touch-active'); 744 } else if (!(card instanceof HTMLAnchorElement)) { 745 card.classList.remove('is-touch-active'); 746 } 747 }); 748 }); 749 document.addEventListener('pointerdown', event => { 750 if (!coarsePointer.matches || event.target.closest('.touch-card')) return; 751 closeTouchCards(); 752 }, { passive: true }); 753 754 const revealTargets = [...document.querySelectorAll('main section > .container,.catalog-card,.assistant-flow-grid article,.journey-steps article,.landing-flow-card')] 755 .filter(element => !element.parentElement?.classList.contains('native-blog-index') && !element.closest('.native-blog-article')); 756 revealTargets.forEach((element, index) => { 757 element.classList.add('cc-reveal', `cc-reveal-delay-${index % 3}`); 758 }); 759 if (reducedMotion.matches || !('IntersectionObserver' in window)) { 760 revealTargets.forEach(element => element.classList.add('is-visible')); 761 } else { 762 const revealObserver = new IntersectionObserver(entries => { 763 entries.forEach(entry => { 764 if (!entry.isIntersecting) return; 765 entry.target.classList.add('is-visible'); 766 revealObserver.unobserve(entry.target); 767 }); 768 }, { rootMargin: '0px 0px -8% 0px', threshold: .08 }); 769 revealTargets.forEach(element => revealObserver.observe(element)); 770 } 771 772 document.querySelectorAll('[data-tilt]').forEach(element => { 773 element.addEventListener('pointermove', event => { 774 if (reducedMotion.matches || event.pointerType === 'touch') return; 775 const bounds = element.getBoundingClientRect(); 776 const x = (event.clientX - bounds.left) / bounds.width - .5; 777 const y = (event.clientY - bounds.top) / bounds.height - .5; 778 element.style.setProperty('--rx', `${(-y * 3.5).toFixed(2)}deg`); 779 element.style.setProperty('--ry', `${(x * 4.5).toFixed(2)}deg`); 780 }); 781 element.addEventListener('pointerleave', () => { 782 element.style.setProperty('--rx', '0deg'); 783 element.style.setProperty('--ry', '0deg'); 784 }); 785 }); 786 787 document.querySelectorAll('[data-year]').forEach(node => { node.textContent = String(new Date().getFullYear()); }); 788})();
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.