1(function () { 2 function initCompare(root) { 3 const frame = root.classList.contains('hc-compare-v2__frame') 4 ? root 5 : root.querySelector('.hc-compare-v2__frame'); 6 7 const overlay = root.querySelector('.hc-compare-v2__overlay'); 8 const divider = root.querySelector('.hc-compare-v2__divider'); 9 10 if (!frame || !overlay || !divider) return; 11 if (frame.dataset.hcCompareReady === '1') return; 12 frame.dataset.hcCompareReady = '1'; 13 14 let isDragging = false; 15 let percent = 50; 16 17 function render() { 18 overlay.style.clipPath = 'inset(0 ' + (100 - percent) + '% 0 0)'; 19 overlay.style.webkitClipPath = 'inset(0 ' + (100 - percent) + '% 0 0)'; 20 divider.style.left = percent + '%'; 21 22 frame.classList.toggle('hc-compare-v2--hide-left-label', percent <= 20); 23 frame.classList.toggle('hc-compare-v2--hide-right-label', percent >= 80); 24 } 25 26 function setPosition(clientX) { 27 const rect = frame.getBoundingClientRect(); 28 if (!rect.width) return; 29 30 let next = ((clientX - rect.left) / rect.width) * 100; 31 next = Math.max(0, Math.min(100, next)); 32 percent = next; 33 render(); 34 } 35 36 function startMouseDrag(e) { 37 isDragging = true; 38 setPosition(e.clientX); 39 e.preventDefault(); 40 } 41 42 function moveMouseDrag(e) { 43 if (!isDragging) return; 44 setPosition(e.clientX); 45 e.preventDefault(); 46 } 47 48 function stopMouseDrag() { 49 isDragging = false; 50 } 51 52 function startTouchDrag(e) { 53 if (!e.touches || !e.touches.length) return; 54 isDragging = true; 55 setPosition(e.touches[0].clientX); 56 e.preventDefault(); 57 } 58 59 function moveTouchDrag(e) { 60 if (!isDragging || !e.touches || !e.touches.length) return; 61 setPosition(e.touches[0].clientX); 62 e.preventDefault(); 63 } 64 65 function stopTouchDrag() { 66 isDragging = false; 67 } 68 69 frame.addEventListener('mousedown', startMouseDrag); 70 divider.addEventListener('mousedown', startMouseDrag); 71 72 window.addEventListener('mousemove', moveMouseDrag); 73 window.addEventListener('mouseup', stopMouseDrag); 74 75 frame.addEventListener('touchstart', startTouchDrag, { passive: false }); 76 divider.addEventListener('touchstart', startTouchDrag, { passive: false }); 77 78 window.addEventListener('touchmove', moveTouchDrag, { passive: false }); 79 window.addEventListener('touchend', stopTouchDrag); 80 window.addEventListener('touchcancel', stopTouchDrag); 81 82 window.addEventListener('resize', render); 83 84 render(); 85 } 86 87 function boot() { 88 document.querySelectorAll('[data-hc-compare-v2]').forEach(initCompare); 89 } 90 91 if (document.readyState === 'loading') { 92 document.addEventListener('DOMContentLoaded', boot); 93 } else { 94 boot(); 95 } 96})(); 97 98(function () { 99 function getArticleLocale() { 100 const lang = (document.documentElement.lang || '').toLowerCase(); 101 const path = (window.location.pathname || '').toLowerCase(); 102 if (lang.startsWith('sk') || path.startsWith('/sk/')) return 'sk'; 103 if (lang.startsWith('hu') || path.startsWith('/hu/')) return 'hu'; 104 if (lang.startsWith('en') || path.startsWith('/en/')) return 'en'; 105 if (lang.startsWith('de') || path.startsWith('/de/')) return 'de'; 106 return 'cs'; 107 } 108 109 const articleLocale = getArticleLocale(); 110 const PC_CATALOG_URL = 'https://hellocomp-config-dates.vercel.app/api/pc-catalog'; 111 let catalogPromise; 112 113 function loadCatalog() { 114 if (!catalogPromise) { 115 catalogPromise = fetch(PC_CATALOG_URL, { signal: AbortSignal.timeout(55000) }) 116 .then(function (response) { 117 if (!response.ok) throw new Error('PC catalog request failed'); 118 return response.json(); 119 }) 120 .catch(function (error) { 121 catalogPromise = null; 122 throw error; 123 }); 124 } 125 return catalogPromise; 126 } 127 128 const PRODUCT_LIMIT_FALLBACK = 4; 129 130 function toNumber(value) { 131 if (value == null) return NaN; 132 133 const cleaned = String(value) 134 .replace(/\s+/g, '') 135 .replace(/ /gi, '') 136 .replace(/KÄ/gi, '') 137 .replace(/,/g, '.'); 138 139 const num = Number(cleaned.replace(/[^0-9.\-]/g, '')); 140 return Number.isFinite(num) ? num : NaN; 141 } 142 143 function formatPrice(value) { 144 const num = toNumber(value); 145 if (!Number.isFinite(num)) return ''; 146 147 const localeSettings = { 148 cs: { locale: 'cs-CZ', currency: 'CZK' }, 149 sk: { locale: 'sk-SK', currency: 'EUR' }, 150 hu: { locale: 'hu-HU', currency: 'HUF' }, 151 en: { locale: 'en-IE', currency: 'EUR' }, 152 de: { locale: 'de-DE', currency: 'EUR' } 153 }; 154 const settings = localeSettings[articleLocale] || localeSettings.cs; 155
156 return new Intl.NumberFormat(settings.locale, { 157 style: 'currency', 158 currency: settings.currency, 159 minimumFractionDigits: 0, 160 maximumFractionDigits: 0 161 }).format(Math.round(num)); 162 } 163 164 function cleanProductName(name) { 165 let out = String(name || '').trim(); 166 if (!out) return ''; 167 168 out = out 169 .replace(/\s*\/\s*[^-].*$/i, '') 170 .replace(/\s*-\s*SkÅÃÅ:.*$/i, '') 171 .replace(/\s*SkÅÃÅ:.*$/i, '') 172 .replace(/\s*SKÅÃÅ\s*:\s*.*$/i, '') 173 .replace(/\s*CASE\s*:\s*.*$/i, '') 174 .replace(/\s{2,}/g, ' ') 175 .trim(); 176 177 out = out 178 .replace(/\bHELLOCOMP\b/gi, 'HelloComp') 179 .replace(/\bAMD\b/g, 'AMD') 180 .replace(/\bINTEL\b/g, 'Intel') 181 .replace(/\bGAMER\b/g, 'GAMER') 182 .replace(/\bINDIVIDUAL\b/g, 'Individual') 183 .replace(/\bMAX\b/g, 'MAX') 184 .replace(/\bPRO\b/g, 'PRO') 185 .replace(/\bEXTREME\b/g, 'Extreme') 186 .replace(/\(?\bPOWERED BY ASUS\b\)?/gi, '(Powered by ASUS)'); 187 188 return out; 189 } 190 191 function productPreferenceScore(product) { 192 const haystack = [product.url, product.name, product.category, product.description] 193 .join(' ') 194 .toLowerCase(); 195 196 let score = 0; 197
198 if (haystack.includes('individual')) score += 100; 199 if (haystack.includes('powered by asus') || haystack.includes('powered-by-asus')) score += 90; 200 if (haystack.includes('/individual')) score += 60; 201 if (haystack.includes('/powered-by-asus')) score += 60; 202 if (haystack.includes('extreme')) score += 25; 203 if (haystack.includes('max')) score += 10; 204 205 if (/\bskÅÃÅ\b/i.test(product.name || '')) score -= 70; 206 if (/\bcase\b/i.test(product.name || '')) score -= 70; 207 208 const priceNum = toNumber(product.priceRaw || product.price); 209 if (Number.isFinite(priceNum) && priceNum >= 30000) score += 8; 210 211 return score; 212 } 213 214 function shuffleArray(items) { 215 const arr = items.slice(); 216 217 for (let i = arr.length - 1; i > 0; i -= 1) { 218 const j = Math.floor(Math.random() * (i + 1)); 219 const tmp = arr[i]; 220 arr[i] = arr[j]; 221 arr[j] = tmp; 222 } 223 224 return arr; 225 } 226 227 function productsFromCatalog(catalog) { 228 if (catalog?.version !== 2 || !Array.isArray(catalog.products)) { 229 throw new Error('Invalid PC catalog'); 230 } 231 const currency = catalog.languages?.find(function (language) { 232 return language.code === articleLocale; 233 })?.currencyCode || (articleLocale === 'cs' ? 'CZK' : articleLocale === 'hu' ? 'HUF' : 'EUR'); 234 const priceFromLabels = { cs: 'od', sk: 'od', hu: 'ettÅl:', en: 'from', de: 'ab' }; 235 236 return catalog.products 237 .filter(function (product) { return product.visible === true && product.orderable === true; }) 238 .map(function (product) { 239 const priceRaw = Number(product.prices?.[currency]); 240 if (!Number.isFinite(priceRaw) || priceRaw <= 0 || !product.name || !product.url) return null; 241 const url = new URL(product.url, 'https://www.hellocomp.cz'); 242 if (url.origin !== 'https://www.hellocomp.cz') return null; 243 url.pathname = url.pathname.replace(/^\/(?:cs|sk|hu|en|de)(?=\/)/, ''); 244 if (articleLocale !== 'cs') url.pathname = '/' + articleLocale + url.pathname; 245 return { 246 url: url.href, 247 name: cleanProductName(product.name), 248 priceRaw: priceRaw, 249 price: (product.priceFrom ? priceFromLabels[articleLocale] + ' ' : '') + formatPrice(priceRaw), 250 image: product.image || '', 251 category: 'gaming pc', 252 description: '' 253 }; 254 }) 255 .filter(Boolean) 256 .sort(function (a, b) { 257 const scoreDiff = productPreferenceScore(b) - productPreferenceScore(a); 258 if (scoreDiff !== 0) return scoreDiff; 259 const priceDiff = b.priceRaw - a.priceRaw; 260 if (priceDiff !== 0) return priceDiff; 261 return a.name.localeCompare(b.name, articleLocale); 262 }); 263 } 264 265 function articleImageUrl(source) { 266 try { 267 const url = new URL(source); 268 if (!/^cdn(?:-api)?\.myshoptet\.com$/.test(url.hostname) 269 || !url.pathname.startsWith('/usr/www.hellocomp.cz/user/shop/')) return source; 270 const originalPath = url.pathname.replace(/\/shop\/(?:detail|big)\//, '/shop/orig/'); 271 if (originalPath === url.pathname) return source; 272 const updatedAt = url.searchParams.get('ts'); 273 url.hostname = 'cdn.myshoptet.com'; 274 url.pathname = originalPath; 275 url.search = ''; 276 if (updatedAt) url.searchParams.set('v', updatedAt); 277 return url.href; 278 } catch { 279 return source; 280 } 281 } 282 283 function buildCard(product) { 284 const article = document.createElement('article'); 285 article.className = 'hc-article-products__item'; 286 287 const link = document.createElement('a'); 288 link.className = 'hc-article-products__link'; 289 link.href = product.url; 290 291 if (product.image) { 292 const imageWrap = document.createElement('div'); 293 imageWrap.className = 'hc-article-products__image'; 294 295 const image = document.createElement('img'); 296 // API thumbnails add a 4:3 white frame; originals fit the square cards. 297 image.src = articleImageUrl(product.image); 298 image.addEventListener('error', function () { 299 if (image.src !== product.image) image.src = product.image; 300 }, { once: true }); 301 image.alt = product.name; 302 image.loading = 'lazy'; 303 304 imageWrap.appendChild(image); 305 link.appendChild(imageWrap); 306 } 307 308 const content = document.createElement('div'); 309 content.className = 'hc-article-products__content'; 310 311 const title = document.createElement('h3'); 312 title.className = 'hc-article-products__title'; 313 title.textContent = product.name; 314 content.appendChild(title); 315 316 if (product.price) { 317 const price = document.createElement('div'); 318 price.className = 'hc-article-products__price'; 319 price.textContent = product.price; 320 content.appendChild(price); 321 } 322 323 const cta = document.createElement('span'); 324 cta.className = 'hc-article-products__cta'; 325 const locale = getArticleLocale(); 326 cta.textContent = locale === 'sk' ? 'ZobraziÅ¥ produkt' : locale === 'hu' ? 'Termék megtekintése' : locale === 'en' ? 'View product' : locale === 'de' ? 'Produkt ansehen' : 'Zobrazit produkt'; 327 content.appendChild(cta); 328 329 link.appendChild(content); 330 article.appendChild(link); 331 332 return article; 333 } 334 335 function renderProducts(root, items) { 336 const list = root.querySelector('[data-hc-article-products-list]'); 337 const empty = root.querySelector('[data-hc-article-products-empty]'); 338 const loading = root.querySelector('[data-hc-article-products-loading]'); 339 340 if (!list) return; 341 342 list.innerHTML = ''; 343 344 if (loading) loading.style.display = 'none'; 345 346 if (!items.length) { 347 if (empty) empty.style.display = ''; 348 return; 349 } 350 351 if (empty) empty.style.display = 'none'; 352
353 items.forEach(function (product) { 354 list.appendChild(buildCard(product)); 355 }); 356 } 357 358 function renderError(root) { 359 const loading = root.querySelector('[data-hc-article-products-loading]'); 360 const empty = root.querySelector('[data-hc-article-products-empty]'); 361 362 if (loading) loading.style.display = 'none'; 363 if (empty) empty.style.display = ''; 364 } 365 366 async function initArticleProducts(root) { 367 if (!root || root.dataset.hcArticleProductsReady === '1') return; 368 root.dataset.hcArticleProductsReady = '1'; 369 370 const limit = Math.max(1, Math.min(12, parseInt(root.getAttribute('data-limit') || String(PRODUCT_LIMIT_FALLBACK), 10) || PRODUCT_LIMIT_FALLBACK)); 371 372 try { 373 const parsedItems = productsFromCatalog(await loadCatalog()); 374 const priorityItems = parsedItems.filter(function (item) { 375 return productPreferenceScore(item) >= 90; 376 }); 377 const pool = priorityItems.length >= limit ? priorityItems : parsedItems; 378 const items = shuffleArray(pool).slice(0, limit); 379 380 renderProducts(root, items); 381 } catch (error) { 382 renderError(root); 383 } 384 } 385 386 function bootArticleProducts() { 387 document.querySelectorAll('[data-hc-article-products]').forEach(function (root) { 388 initArticleProducts(root); 389 }); 390 } 391 392 if (document.readyState === 'loading') { 393 document.addEventListener('DOMContentLoaded', bootArticleProducts); 394 } else { 395 bootArticleProducts(); 396 } 397})(); 398 399/* Homepage renders article cards without dates even when dates are enabled in Shoptet. 400 Reuse the already rendered blog listing as the source of truth. */ 401(function () { 402 function getLocale() { 403 const lang = (document.documentElement.lang || '').toLowerCase(); 404 const path = (window.location.pathname || '').toLowerCase(); 405 if (lang.startsWith('sk') || path.startsWith('/sk/')) return 'sk'; 406 if (lang.startsWith('hu') || path.startsWith('/hu/')) return 'hu'; 407 if (lang.startsWith('en') || path.startsWith('/en/')) return 'en'; 408 if (lang.startsWith('de') || path.startsWith('/de/')) return 'de'; 409 return 'cs'; 410 } 411 412 function normalisePath(url) { 413 try { 414 const parsed = new URL(url, window.location.origin); 415 return parsed.pathname.replace(/\/+$/, '/') || '/'; 416 } catch (error) { 417 return ''; 418 } 419 } 420 421 function initHomepageBlogDates() { 422 if (!document.body.classList.contains('type-index')) return; 423 424 const cards = Array.from(document.querySelectorAll('.homepage-blog-wrapper .news-item > a[href]')); 425 if (!cards.length) return; 426 427 const locale = getLocale(); 428 const blogPath = locale === 'cs' ? '/blog/' : '/' + locale + '/blog/'; 429 430 fetch(blogPath, { credentials: 'same-origin' }) 431 .then(function (response) { 432 if (!response.ok) throw new Error('Blog listing request failed'); 433 return response.text(); 434 }) 435 .then(function (html) { 436 const doc = new DOMParser().parseFromString(html, 'text/html'); 437 const datesByPath = new Map(); 438 439 doc.querySelectorAll('#newsWrapper .news-item').forEach(function (item) { 440 const link = item.querySelector('a[href]'); 441 const time = item.querySelector('time'); 442 const path = link ? normalisePath(link.getAttribute('href')) : ''; 443 if (path && time && time.textContent.trim()) { 444 datesByPath.set(path, { 445 text: time.textContent.trim(), 446 datetime: time.getAttribute('datetime') || '' 447 }); 448 } 449 }); 450 451 cards.forEach(function (card) { 452 const text = card.querySelector('.text'); 453 const title = text ? text.querySelector('.title') : null; 454 const date = datesByPath.get(normalisePath(card.getAttribute('href'))); 455 if (!text || !title || !date || text.querySelector('.hc-home-article-date')) return; 456 457 const time = document.createElement('time'); 458 time.className = 'hc-home-article-date'; 459 time.textContent = date.text; 460 if (date.datetime) time.dateTime = date.datetime; 461 title.insertAdjacentElement('afterend', time); 462 }); 463 }) 464 .catch(function () { 465 /* Homepage stays fully functional if the blog listing cannot be loaded. */ 466 }); 467 } 468 469 if (document.readyState === 'loading') { 470 document.addEventListener('DOMContentLoaded', initHomepageBlogDates); 471 } else { 472 initHomepageBlogDates(); 473 } 474})(); 475 476/* ============================== */ 477/* HOMEPAGE BLOG â TLACITKO /BLOG/ */ 478/* ============================== */ 479(function () { 480 function getCurrentLocale() { 481 const lang = (document.documentElement.getAttribute('lang') || '').toLowerCase(); 482 const path = (window.location.pathname || '').toLowerCase(); 483 484 if (lang.startsWith('hu') || path.startsWith('/hu/')) return 'hu'; 485 if (lang.startsWith('sk') || path.startsWith('/sk/')) return 'sk'; 486 if (lang.startsWith('en') || path.startsWith('/en/')) return 'en'; 487 if (lang.startsWith('de') || path.startsWith('/de/')) return 'de'; 488 return 'cs'; 489 } 490 491 function getHomepageBlogLinkLabel() { 492 const locale = getCurrentLocale(); 493 494 if (locale === 'hu') return 'Ãsszes cikk megtekintése'; 495 if (locale === 'sk') return 'ZobraziÅ¥ vÅ¡etky Älánky'; 496 if (locale === 'en') return 'View all articles'; 497 if (locale === 'de') return 'Alle Artikel'; 498 return 'Zobrazit vÅ¡echny Älánky'; 499 } 500 501 function injectHomepageBlogLink() { 502 const locale = getCurrentLocale(); 503 const wrappers = document.querySelectorAll('.type-index .blog-outer .homepage-blog-wrapper.blog-wrapper'); 504 if (!wrappers.length) return; 505
506 wrappers.forEach((wrapper) => { 507 if (wrapper.querySelector('.hc-blog-more-wrap')) return; 508 509 const title = wrapper.querySelector('.homepage-group-title'); 510 if (!title) return; 511 512 const moreWrap = document.createElement('p'); 513 moreWrap.className = 'hc-blog-more-wrap'; 514 515 const moreLink = document.createElement('a'); 516 moreLink.className = 'hc-blog-more-link'; 517 moreLink.href = locale === 'cs' ? '/blog/' : `/${locale}/blog/`; 518 moreLink.textContent = getHomepageBlogLinkLabel(); 519 520 moreWrap.appendChild(moreLink); 521 522 const newsItems = wrapper.querySelectorAll('.news-item'); 523 const cardsParent = newsItems.length ? newsItems[0].parentElement : null; 524 525 if (cardsParent && cardsParent !== wrapper) { 526 cardsParent.insertAdjacentElement('afterend', moreWrap); 527 } else if (newsItems.length) { 528 newsItems[newsItems.length - 1].insertAdjacentElement('afterend', moreWrap); 529 } else { 530 title.insertAdjacentElement('afterend', moreWrap); 531 } 532 }); 533 } 534 535 if (document.readyState === 'loading') { 536 document.addEventListener('DOMContentLoaded', injectHomepageBlogLink); 537 } else { 538 injectHomepageBlogLink(); 539 } 540})();
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.