PageSourceSearch

https://www.shalamar.eu/shop-data/shl-news-article.js?v=14

js shalamar.eu collected 2026-10-10 01:38:15 UTC 25,205 bytes, 558 lines download raw bytes

1/*
2 * Shalamar Foods — visual + structural styling for /news/* article pages
3 * ----------------------------------------------------------------------
4 * UpGates renders /news/<slug> articles in a basic .pg-txt > .text.text-site
5 * container with no typography hierarchy, no TOC, no callouts. This script
6 * raises the visual quality to ~aktin.cz level so the blog can carry SEO
7 * weight without looking amateur.
8 *
9 * Three layers, all client-side:
10 *
11 *   1. CSS injection — typography (17px / line-height 1.7), terracotta H2,
12 *      list spacing, max-width 720px, lead paragraph emphasis, CTA box for
13 *      /a/<slug>-recept internal links.
14 *
15 *   2. DOM enhancements (only on /news/<slug>, not on /news/ index):
16 *      - Auto-TOC if article has 4+ H2 (collapsible on mobile, sticky desktop)
17 *      - Reading time estimate (~N min) under H1
18 *      - Article schema.org JSON-LD for SEO
19 *      - Wrap /a/<slug>-recept anchors into a CTA card
20 *
21 *   3. Idempotent — safe to run multiple times. Watches for late-rendered
22 *      content (UpGates can hydrate after DOMContentLoaded).
23 *
24 * Path guard: only fires on /news/<slug>, /en/news/<slug>, /ch/news/<slug>.
25 * The /news/ index, homepage, product pages etc. are no-ops.
26 *
27 * Reference: aktin.cz article layout (2026-05-05).
28 */
29(function () {
30  'use strict';
31
32  // -------- Path guards --------
33  var ARTICLE_RE = /^\/(?:en\/|ch\/)?news\/[^\/]+\/?$/;
34  var LISTING_RE = /^\/(?:en\/|ch\/)?news\/?$/;
35  var IS_ARTICLE = ARTICLE_RE.test(location.pathname);
36  var IS_LISTING = LISTING_RE.test(location.pathname);
37  if (!IS_ARTICLE && !IS_LISTING) return;
38
39  var TERRACOTTA = '#7a2d1a';
40  var TERRACOTTA_LIGHT = '#fff7ee';
41  var TERRACOTTA_BORDER = '#D4571C';
42
43  // -------- Hero images per article slug --------
44  // Edit this map to add/swap top images. Stored on /shop-data/ via FTP root.
45  var HERO_IMAGES = {
46    'kicari-recept': {
47      src: '/shop-data/kicari-recept-1600.jpg',
48      alt: 'Kičari z basmati rýže a žluté mung dal v keramické misce, navrch ghí, kmín a zelená chilli',
49      caption: 'Kičari: basmati a žlutá mung dal z jednoho hrnce, navrch tadka z ghí a kmínu'
50    },
51    'butter-chicken-recept': {
52      src: '/shop-data/shl-news-butter-chicken.jpg',
53      alt: 'Butter chicken murgh makhani s krémovou rajčatovou omáčkou',
54      caption: 'Butter chicken (murgh makhani) — krémová rajčatová omáčka s tandoori kuřetem'
55    },
56    'dal-tadka-recept': {
57      src: '/shop-data/shl-news-dal-tadka.jpg',
58      alt: 'Žlutá toor dal s tadkou kmínu, česneku a sušeného chilli',
59      caption: 'Dal tadka — žlutá toor čočka s temperovaným kořením v ghí'
60    },
61    'pravy-pakistansky-biryani-pruvodce': {
62      src: '/shop-data/shl-news-biryani.jpg',
63      alt: 'Pakistánský chicken biryani s šafránovou rýží a sušenými švestkami aloo bukhara',
64      caption: 'Pákistánský chicken biryani — vrstvená basmati rýže, šafrán a aloo bukhara'
65    },
66    'jak-uvarit-basmati-ryzi': {
67      src: '/shop-data/shl-news-basmati.jpg?v=2',
68      alt: 'Uvařená basmati rýže v misce s oddělenými dlouhými zrny',
69      caption: 'Basmati rýže — uvařená správným poměrem 1:1,5 s oddělenými zrny'
70    },
71    'co-je-garam-masala': {
72      src: '/shop-data/shl-news-garam-masala.jpg',
73      alt: 'Směs koření garam masala — kardamom, hřebíček, skořice, kmín',
74      caption: 'Garam masala — směs kardamomu, hřebíčku, skořice, kmínu, koriandru a kardamomu'
75    },
76    'masala-chai-recept': {
77      src: '/shop-data/shl-news-masala-chai.jpg',
78      alt: 'Masala chai v hrnku — indický kořeněný čaj s mlékem',
79      caption: 'Masala chai — silný CTC čaj vařený s mlékem, kardamomem, zázvorem a skořicí'
80    },
81    'co-je-paneer-jak-ho-uvarit': {
82      src: '/shop-data/shl-news-paneer.jpg',
83      alt: 'Paneer — indický čerstvý sýr v omáčce s koriandrem',
84      caption: 'Paneer — čerstvý sýr z plnotučného mléka a citronové šťávy'
85    },
86    'jak-pouzivat-tamarind': {
87      src: '/shop-data/shl-news-tamarind.jpg',
88      alt: 'Tamarindová pasta a slisované bloky tamarindu',
89      caption: 'Tamarind v různých formách — pasta, slisované bloky, sušené lusky'
90    },
91    'biryani-vs-pulao': {
92      src: '/shop-data/shl-news-pulao.jpg',
93      alt: 'Pulao s hráškem v skleněné misce — rýžový pokrm vařený absorpční metodou',
94      caption: 'Pea pulao — rýže s hráškem vařená absorpční metodou, jednodušší alternativa biryani'
95    },
96    'tikka-masala-vs-tandoori-masala': {
97      src: '/shop-data/shl-news-tandoori.jpg',
98      alt: 'Tandoori kuře s limetkou na rustikálním dřevěném prkně',
99      caption: 'Tandoori kuře — marinováno v jogurtu a koření, pečené v hliněné peci'
100    },
101    '10-indickych-koreni-ktere-potrebujete': {
102      src: '/shop-data/shl-news-koreni.jpg',
103      alt: 'Sortiment indických koření v tradiční nádobě — kmín, kurkuma, kardamom, hřebíček',
104      caption: 'Indická koření — základ každé indické kuchyně od kmínu po garam masalu'
105    },
106    'halal-maso-pruvodce': {
107      src: '/shop-data/shl-news-halal.jpg',
108      alt: 'Čerstvé syrové maso na řeznickém pultu',
109      caption: 'Halal maso — vyžaduje specifický způsob porážky a certifikaci řeznictvím'
110    },
111    'aalu-methi-brambory-s-piskavici': {
112      src: '/shop-data/shl-news-aalu-methi.jpg',
113      alt: 'Aalu methi — brambory s pískavicí, severoindický pokrm',
114      caption: 'Aalu methi — brambory dušené s listovou pískavicí (methi)'
115    },
116    'zacatecnikuv-pruvodce-indickou-kuchyni': {
117      src: '/shop-data/shl-news-zacatecnik.jpg',
118      alt: 'Stůl plný indických pokrmů — průvodce pro začátečníky',
119      caption: 'Indická kuchyně — průvodce pro začátečníky se základními ingrediencemi'
120    },
121    'eid-recepty-sladkosti': {
122      src: '/shop-data/shl-news-eid.jpg',
123      alt: 'Eid sladkosti na zlatě barveném talíři',
124      caption: 'Eid sladkosti — sheer khurma, gulab jamun a další tradiční dezerty'
125    }
126  };
127
128  // -------- 1. CSS --------
129  function injectCSS() {
130    if (document.getElementById('shl-news-article-css')) return;
131    var css = [
132      // Article body container — wider reading column
133      'body[class*="page-news_id-"] article.ContentPage .pg-txt .text.text-site{',
134        'font-size:17px;line-height:1.7;color:#1a1a1a;',
135      '}',
136      'body[class*="page-news_id-"] article.ContentPage .pg-txt .max{',
137        'max-width:760px;',
138      '}',
139      // Headings
140      'body[class*="page-news_id-"] article.ContentPage .pg-hdr h1.ph-main{',
141        'font-size:2.1rem;line-height:1.25;font-weight:700;color:#1a1a1a;',
142      '}',
143      'body[class*="page-news_id-"] article.ContentPage .text.text-site h2{',
144        'font-size:1.55rem;line-height:1.3;font-weight:700;color:' + TERRACOTTA + ';',
145        'margin:2.2em 0 0.7em;padding-top:0.3em;',
146        'border-top:1px solid #e9ecef;',
147      '}',
148      'body[class*="page-news_id-"] article.ContentPage .text.text-site h2:first-child{',
149        'border-top:none;margin-top:0;padding-top:0;',
150      '}',
151      'body[class*="page-news_id-"] article.ContentPage .text.text-site h3{',
152        'font-size:1.25rem;line-height:1.35;font-weight:600;color:#1a1a1a;',
153        'margin:1.8em 0 0.5em;',
154      '}',
155      // Paragraphs — vzdušnější
156      'body[class*="page-news_id-"] article.ContentPage .text.text-site p{',
157        'margin:0 0 1.3em;',
158      '}',
159      // Lead paragraph (first paragraph after intro section)
160      'body[class*="page-news_id-"] article.ContentPage .text.text-site > p:first-child,',
161      'body[class*="page-news_id-"] article.ContentPage .text.text-site > div:first-child > p:first-child{',
162        'font-size:1.12em;line-height:1.65;color:#3a3a3a;',
163        'border-left:3px solid ' + TERRACOTTA + ';',
164        'padding:0.4em 0 0.4em 1.1em;margin-bottom:1.6em;',
165        'background:#fafafa;',
166      '}',
167      // Lists
168      'body[class*="page-news_id-"] article.ContentPage .text.text-site ul,',
169      'body[class*="page-news_id-"] article.ContentPage .text.text-site ol{',
170        'margin:0 0 1.3em;padding-left:1.6em;',
171      '}',
172      'body[class*="page-news_id-"] article.ContentPage .text.text-site li{',
173        'margin-bottom:0.5em;line-height:1.6;',
174      '}',
175      'body[class*="page-news_id-"] article.ContentPage .text.text-site ul li::marker{',
176        'color:' + TERRACOTTA + ';',
177      '}',
178      // Strong as inline keyword
179      'body[class*="page-news_id-"] article.ContentPage .text.text-site strong{',
180        'color:#1a1a1a;font-weight:600;',
181      '}',
182      // CTA box — wraps /a/<slug>-recept anchors transformed by JS
183      '.shl-news-cta{',
184        'display:flex;gap:14px;align-items:center;flex-wrap:wrap;',
185        'margin:1.8em 0;padding:18px 22px;',
186        'background:linear-gradient(135deg,' + TERRACOTTA_LIGHT + ' 0%,#ffe9d5 100%);',
187        'border-radius:12px;border-left:4px solid ' + TERRACOTTA_BORDER + ';',
188        'text-decoration:none;color:inherit;',
189        'transition:transform .15s,box-shadow .15s;',
190        'box-shadow:0 1px 4px rgba(212,87,28,.08);',
191      '}',
192      '.shl-news-cta:hover{',
193        'transform:translateY(-1px);box-shadow:0 4px 12px rgba(212,87,28,.16);',
194        'text-decoration:none;color:inherit;',
195      '}',
196      '.shl-news-cta-icon{',
197        'flex:0 0 auto;width:42px;height:42px;border-radius:50%;',
198        'background:' + TERRACOTTA + ';color:#fff;',
199        'display:flex;align-items:center;justify-content:center;',
200        'font-size:20px;font-weight:700;',
201      '}',
202      '.shl-news-cta-text{flex:1 1 auto;
202min-width:200px;}',
203      '.shl-news-cta-label{',
204        'font-size:0.78em;text-transform:uppercase;letter-spacing:0.5px;',
205        'color:' + TERRACOTTA + ';font-weight:600;margin-bottom:2px;',
206      '}',
207      '.shl-news-cta-title{font-size:1.08em;font-weight:600;color:#1a1a1a;}',
208      '.shl-news-cta-arrow{flex:0 0 auto;font-size:1.6em;color:' + TERRACOTTA + ';}',
209      // Reading-time meta
210      '.shl-news-meta{',
211        'display:flex;gap:18px;align-items:center;flex-wrap:wrap;',
212        'font-size:0.9em;color:#6c757d;margin-top:0.7em;',
213        'padding-top:0.7em;border-top:1px solid #f0eae3;',
214      '}',
215      '.shl-news-meta-item{display:inline-flex;align-items:center;gap:7px;}',
216      '.shl-meta-icon{',
217        'width:14px;height:14px;flex:0 0 14px;',
218        'fill:currentColor;opacity:0.65;',
219      '}',
220      // Hero image (figure under H1)
221      '.shl-news-hero{',
222        'margin:0 0 2.4em;padding:0;',
223        'max-width:760px;',
224      '}',
225      '.shl-news-hero img{',
226        'width:100%;height:auto;aspect-ratio:16/9;',
227        'object-fit:cover;display:block;',
228        'border-radius:8px;',
229        'box-shadow:0 12px 32px -8px rgba(122,45,26,0.18),0 2px 6px rgba(0,0,0,0.04);',
230      '}',
231      '.shl-news-hero figcaption{',
232        'margin-top:0.85em;font-size:0.86em;line-height:1.55;',
233        'color:#7a7268;font-style:italic;text-align:center;',
234        'font-family:Georgia,"Iowan Old Style","Palatino Linotype",serif;',
235      '}',
236      '@media (max-width:640px){',
237        '.shl-news-hero{margin-bottom:1.6em;}',
238        '.shl-news-hero img{aspect-ratio:4/3;border-radius:6px;}',
239        '.shl-news-hero figcaption{font-size:0.82em;}',
240      '}',
241      // TOC
242      '.shl-toc{',
243        'background:#fafafa;border:1px solid #e9ecef;border-radius:8px;',
244        'padding:16px 20px;margin:0 0 1.8em;',
245      '}',
246      '.shl-toc-title{',
247        'font-size:0.82em;font-weight:600;text-transform:uppercase;',
248        'letter-spacing:0.6px;color:#666;margin-bottom:10px;',
249      '}',
250      '.shl-toc ol{margin:0;padding:0;list-style:none;counter-reset:shl-toc-i;}',
251      '.shl-toc li{',
252        'counter-increment:shl-toc-i;padding:5px 0;',
253        'border-bottom:1px solid #f0f0f0;',
254      '}',
255      '.shl-toc li:last-child{border-bottom:none;}',
256      '.shl-toc li::before{',
257        'content:counter(shl-toc-i, decimal-leading-zero) ".";',
258        'color:' + TERRACOTTA + ';font-weight:600;margin-right:10px;',
259        'font-feature-settings:"tnum";',
260      '}',
261      '.shl-toc a{',
262        'color:#1a1a1a;text-decoration:none;',
263        'transition:color .12s;',
264      '}',
265      '.shl-toc a:hover{color:' + TERRACOTTA + ';}',
266      // /news listing — Aktin-style 2-column card grid (larger cards)
267      'body.page-news .artcl-wrap{',
268        'display:grid!important;',
269        'grid-template-columns:repeat(2,1fr)!important;',
270        'gap:36px 32px!important;',
271        'flex-direction:initial!important;',
272      '}',
273      '@media (max-width:900px){',
274        'body.page-news .artcl-wrap{grid-template-columns:1fr!important;}',
275      '}',
276      'body.page-news .artcl-wrap > hr,',
277      'body.page-news .artcl-wrap > br{display:none!important;}',
278      'body.page-news article.SNIitem{',
279        'display:flex!important;flex-direction:column!important;',
280        'gap:0!important;margin:0!important;padding:0!important;',
281        'background:#fff!important;border-radius:10px!important;',
282        'overflow:hidden!important;',
283        'box-shadow:0 1px 3px rgba(0,0,0,0.06),0 4px 12px rgba(122,45,26,0.04)!important;',
284        'transition:transform .18s,box-shadow .18s!important;',
285      '}',
286      'body.page-news article.SNIitem:hover{',
287        'transform:translateY(-2px)!important;',
288        'box-shadow:0 4px 16px rgba(0,0,0,0.08),0 12px 28px rgba(122,45,26,0.10)!important;',
289      '}',
290      'body.page-news article.SNIitem > figure{',
291        'flex:0 0 auto!important;width:100%!important;max-width:none!important;',
292        'height:auto!important;aspect-ratio:3/2!important;',
293        'margin:0!important;overflow:hidden!important;',
294        'padding:0!important;border:0!important;background:#f4ebe3!important;',
295        'border-radius:0!important;',
296      '}',
297      'body.page-news article.SNIitem > figure img{',
298        'width:100%!important;height:100%!important;',
299        'object-fit:cover!important;object-position:center 45%!important;',
300        'display:block!important;',
301        'padding:0!important;border:0!important;background:transparent!important;',
302        'border-radius:0!important;max-width:none!important;',
303      '}',
304      'body.page-news article.SNIitem > .a-i-txt,',
305      'body.page-news article.SNIitem >
305 div:not(:has(> img)):not(figure){',
306        'flex:1 1 auto!important;display:flex!important;flex-direction:column!important;',
307        'padding:24px 28px 26px!important;gap:10px!important;',
308      '}',
309      'body.page-news article.SNIitem h2{',
310        'font-size:1.4rem!important;line-height:1.3!important;',
311        'margin:0 0 8px!important;font-weight:700!important;',
312        'display:-webkit-box!important;-webkit-line-clamp:2!important;',
313        '-webkit-box-orient:vertical!important;overflow:hidden!important;',
314      '}',
315      'body.page-news article.SNIitem h2 a{color:#1a1a1a!important;text-decoration:none!important;}',
316      'body.page-news article.SNIitem .text{',
317        'font-size:1rem!important;line-height:1.6!important;color:#555!important;',
318        'display:-webkit-box!important;-webkit-line-clamp:3!important;',
319        '-webkit-box-orient:vertical!important;overflow:hidden!important;',
320        'margin:0!important;',
321      '}',
322      'body.page-news article.SNIitem .text p{margin:0!important;}',
323      '@media (max-width:640px){',
324        'body.page-news .artcl-wrap{',
325          'grid-template-columns:1fr!important;gap:20px!important;',
326        '}',
327        'body.page-news article.SNIitem > figure{height:180px!important;}',
328      '}',
329      // Mobile tweaks
330      '@media (max-width:640px){',
331        'body[class*="page-news_id-"] article.ContentPage .pg-hdr h1.ph-main{font-size:1.65rem;}',
332        'body[class*="page-news_id-"] article.ContentPage .text.text-site{font-size:16px;}',
333        'body[class*="page-news_id-"] article.ContentPage .text.text-site h2{font-size:1.35rem;}',
334        '.shl-news-cta{padding:14px 16px;gap:10px;}',
335        '.shl-news-cta-icon{width:36px;height:36px;font-size:17px;}',
336      '}'
337    ].join('');
338    var s = document.createElement('style');
339    s.id = 'shl-news-article-css';
340    s.textContent = css;
341    (document.head || document.documentElement).appendChild(s);
342  }
343
344  // -------- 2. Slugify (for anchor IDs) --------
345  function slugify(s) {
346    return (s || '')
347      .toLowerCase()
348      .normalize('NFD').replace(/[̀-ͯ]/g, '')
349      .replace(/[^a-z0-9]+/g, '-')
350      .replace(/^-+|-+$/g, '');
351  }
352
353  // -------- 3. Reading time --------
354  function injectReadingTime() {
355    if (document.querySelector('.shl-news-meta')) return;
356    var article = document.querySelector('article.ContentPage');
357    if (!article) return;
358    var hdr = article.querySelector('.pg-hdr .max') || article.querySelector('.pg-hdr');
359    if (!hdr) return;
360    var bodyText = (article.querySelector('.pg-txt') || article).innerText || '';
361    var words = bodyText.trim().split(/\s+/).filter(Boolean).length;
362    if (words < 100) return;
363    var minutes = Math.max(1, Math.round(words / 200));
364
365    var meta = document.createElement('div');
366    meta.className = 'shl-news-meta';
367    var iconClock = '<svg class="shl-meta-icon" aria-hidden="true" focusable="false"><use href="/images/icons/fa/solid.svg#clock"></use></svg>';
368    var iconDoc   = '<svg class="shl-meta-icon" aria-hidden="true" focusable="false"><use href="/images/icons/fa/solid.svg#file-lines"></use></svg>';
369    meta.innerHTML =
370      '<span class="shl-news-meta-item">' + iconClock + minutes + ' min čtení</span>' +
371      '<span class="shl-news-meta-item">' + iconDoc + words.toLocaleString('cs-CZ') + ' slov</span>';
372    hdr.appendChild(meta);
373  }
374
375  // -------- Hero image injection --------
376  function injectHero() {
377    if (document.querySelector('.shl-news-hero')) return;
378    var slug = location.pathname.replace(/\/$/, '').split('/').pop();
379    var hero = HERO_IMAGES[slug];
380    if (!hero) return;
381    var article = document.querySelector('article.ContentPage');
382    if (!article) return;
383    var header = article.querySelector('.pg-hdr');
384    var bodySection = article.querySelector('.pg-txt');
385    if (!header || !bodySection) return;
386
387    var fig = document.createElement('figure');
388    fig.className = 'shl-news-hero';
389    var html = '<img src="' + hero.src + '" alt="' + (hero.alt || '') + '" loading="eager" fetchpriority="high">';
390    if (hero.caption) html += '<figcaption>' + hero.caption + '</figcaption>';
391    fig.innerHTML = html;
392
393    // Insert at the top of the body section, inside .max wrapper if present
394    var maxWrap = bodySection.querySelector('.max');
395    if (maxWrap) {
396      maxWrap.insertBefore(fig, maxWrap.firstChild);
397    } else {
398      bodySection.insertBefore(fig, bodySection.firstChild);
399    }
400  }
401
402  // -------- 4. Auto TOC --------
403  function injectTOC() {
404    if (document.querySelector('.shl-toc')) return;
405    var textBox = document.querySelector('article.ContentPage .pg-txt .text.text-site');
406    if (!textBox) return;
407    var h2s = Array.from(textBox.querySelectorAll(':scope > h2, :scope > div > h2'));
408    if (h2s.length < 4) return;
409
410    var toc = document.createElement('nav');
411    toc.className = 'shl-toc';
412    toc.setAttribute('aria-label', 'Obsah článku');
413    var html = '<div class="shl-toc-title">Obsah článku</div><ol>';
414    h2s.forEach(function (h, i) {
415      var slug = h.id || slugify(h.textContent);
416      if (!slug) slug = 'h2-' + i;
417      h.id = slug;
418      // Add scroll-margin so the anchor doesn't hide under sticky header
419      h.style.scrollMarginTop = '80px';
420      html += '<li><a href="#' + slug + '">' + h.textContent.replace(/[<>]/g, '') + '</a></li>';
421    });
422    html += '</ol>';
423    toc.innerHTML = html;
424
425    // Insert before the first child of text-site
426    textBox.insertBefore(toc, textBox.firstChild);
427  }
428
429  // -------- 5. /a/<slug>-recept link → CTA box --------
430  function transformRecipeCTA() {
431    var textBox = document.querySelector('article.ContentPage .pg-txt .text.text-site');
432    if (!textBox) return;
433    var anchors = textBox.querySelectorAll('a[href^="/a/"][href$="-recept"], a[href^="/en/a/"][href$="-recipe"], a[href^="/ch/a/"]');
434    anchors.forEach(function (a) {
435      // Only transform if anchor's parent is a <p> and it's in the latter half of article
436      // Heuristic: skip if already wrapped or if it's inside a list
437      if (a.dataset.shlNewsCta === '1') return;
438      if (a.closest('.shl-news-cta')) return;
439      if (a.closest('li')) return;
440      var p = a.closest('p');
441      if (!p) return;
442
443      // Build CTA replacement
444      var cta = document.createElement('a');
445      cta.className = 'shl-news-cta';
446      cta.href = a.getAttribute('href');
447      cta.dataset.shlNewsCta = '1';
448      cta.innerHTML =
449        '<span class="shl-news-cta-icon" aria-hidden="true">→</span>' +
450        '<span class="shl-news-cta-text">' +
451          '<div class="shl-news-cta-label">Krok-za-krokem recept</div>' +
452          '<div class="shl-news-cta-title">' + (a.textContent || 'Otevřít recept') + '</div>' +
453        '</span>' +
454        '<span class="shl-news-cta-arrow" aria-hidden="true">›</span>';
455
456      // Replace the entire paragraph if it's just the link, else replace just the link
457      if ((p.textContent || '').trim() === (a.textContent || '').trim()) {
458        p.parentNode.replaceChild(cta, p);
459      } else {
460        // Wrap before the paragraph: drop the paragraph and insert CTA after, then keep paragraph minus the link
461        p.parentNode.insertBefore(cta, p.nextSibling);
462        a.remove();
463        // Clean up if paragraph is now empty
464        if (!(p.textContent || '').trim()) p.remove();
465      }
466    });
467  }
468
469  // -------- 6. Article schema.org JSON-LD --------
470  function injectArticleSchema() {
471    if (document.querySelector('script[data-shl-article-schema]')) return;
472    var h1 = document.querySelector('article.ContentPage h1');
473    if (!h1) return;
474    var title = (h1.textContent || '').trim();
475    if (!title) return;
476    var url = 'https://www.shalamar.eu' + location.pathname.replace(/\/$/, '');
477    // Find first image in the article body for image
478    var img = document.querySelector('article.ContentPage img');
479    var imgUrl = img ? img.src : 'https://www.shalamar.eu/_assets/img/logo.png';
480    // Try to get publish date from any visible time/date element
481    var publishDate = null;
482    var timeEl = document.querySelector('article.ContentPage time[datetime]');
483    if (timeEl) publishDate = timeEl.getAttribute('datetime');
484
485    var data = {
486      '@context': 'https://schema.org',
487      '@type': 'Article',
488      'headline': title,
489      'image': [imgUrl],
490      'author': { '@type': 'Organization', 'name': 'Shalamar Foods' },
491      'publisher': {
492        '@type': 'Organization',
493        'name': 'Shalamar Foods',
494        'logo': { '@type': 'ImageObject', 'url': 'https://www.shalamar.eu/_assets/img/logo.png' }
495      },
496      'mainEntityOfPage': { '@type': 'WebPage', '@id': url }
497    };
498    if (publishDate) data.datePublished = publishDate;
499
500    var s = document.createElement('script');
501    s.type = 'application/ld+json';
502    s.setAttribute('data-shl-article-schema', '1');
503    s.textContent = JSON.stringify(data);
504    document.head.appendChild(s);
505  }
506
507  // -------- Run --------
508  injectCSS();
509
510  // -------- Listing thumbnail injection --------
511  function injectListingThumbnails() {
512    if (!IS_LISTING) return;
513    var articles = document.querySelectorAll('article.SNIitem');
514    articles.forEach(function (art) {
515      // Skip if article already has a figure/img
516      if (art.querySelector('figure, img')) return;
517      // Find slug from any anchor inside
518      var a = art.querySelector('a[href*="/news/"]');
519      if (!a) return;
520      var slug = (a.getAttribute('href') || '').replace(/\/$/, '').split('/').pop();
521      var hero = HERO_IMAGES[slug];
522      if (!hero) return;
523
524      // Build figure matching UpGates' structure
525      var fig = document.createElement('figure');
526      fig.className = 'figure flex-shrink-0 cursor-pointer shl-news-listing-fig';
527      fig.setAttribute('data-href', a.getAttribute('href'));
528      fig.innerHTML = '<img src="' + hero.src + '" alt="' + (hero.alt || '') + '" loading="lazy" style="width:100%;max-width:280px;height:auto;aspect-ratio:4/3;object-fit:cover;border-radius:6px;">';
529      // Click forwarding
530      fig.addEventListener('click', function () { location.href = a.getAttribute('href'); });
531      // Insert as first child of article (UpGates puts figure first, then text div)
532      art.insertBefore(fig, art.firstChild);
533    });
534  }
535
536  function run() {
537    if (IS_ARTICLE) {
538      injectHero();
539      injectReadingTime();
540      injectTOC();
541      transformRecipeCTA();
542      injectArticleSchema();
543    }
544    if (IS_LISTING) {
545      injectListingThumbnails();
546    }
547  }
548
549  if (document.readyState === 'loading') {
550    document.addEventListener('DOMContentLoaded', run, { once: true });
551  } else {
552    run();
553  }
554
555  // Late-render fallback (UpGates sometimes hydrates after DOMContentLoaded)
556  setTimeout(run, 500);
557  setTimeout(run, 1500);
558})();

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.