PageSourceSearch

https://www.hellocomp.cz/user/documents/blog.js?v=102

js hellocomp.cz collected 2026-10-04 20:24:39 UTC 18,364 bytes, 540 lines download raw bytes

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(/&nbsp;/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.