PageSourceSearch

https://homeopathic.com/wp-content/plugins/hes-homepage-plugin/assets/site.js

js homeopathic.com collected 2026-10-01 11:44:22 UTC 20,730 bytes, 330 lines download raw bytes

1/* HES — inner-page behaviour: cart, filters, search, tabs, forms. */
2(function(){
3  var ROOT = document.body.getAttribute('data-root') || '';
4  var LS_KEY = 'hes_cart_v1';
5  function $(s, c){ return (c||document).querySelector(s); }
6  function $$(s, c){ return [].slice.call((c||document).querySelectorAll(s)); }
7  function money(n){ return '$' + (Math.round(n*100)/100).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ','); }
8  function esc(s){ return String(s==null?'':s).replace(/[&<>"']/g, function(c){ return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]; }); }
9
10  /* ---------------- cart store (per-browser, works without a server) ---------------- */
11  var mem = {};
12  function load(){ try{ return JSON.parse(localStorage.getItem(LS_KEY)) || {}; }catch(e){ return mem; } }
13  function save(c){ mem = c; try{ localStorage.setItem(LS_KEY, JSON.stringify(c)); }catch(e){} paintCount(); }
14  function count(c){ c = c || load(); var n = 0; for(var k in c) n += c[k].q; return n; }
15  function subtotal(c){ c = c || load(); var t = 0; for(var k in c) t += c[k].q * c[k].p; return t; }
16  function paintCount(bump){
17    var n = count();
18    $$('.cartlink b').forEach(function(b){ b.textContent = n; });
19    if(bump) $$('.cartlink').forEach(function(a){ a.classList.remove('bump'); void a.offsetWidth; a.classList.add('bump'); });
20  }
21  function add(item, q){
22    var c = load();
23    if(c[item.s]) c[item.s].q += q; else { item.q = q; c[item.s] = item; }
24    save(c); paintCount(true); toast(item, q);
25  }
26  window.HESCart = {load:load, save:save, add:add, count:count, subtotal:subtotal};
27  paintCount();
28
29  /* ---------------- toast ---------------- */
30  var toastEl, toastT;
31  function toast(item, q){
32    if(!toastEl){
33      toastEl = document.createElement('div'); toastEl.className = 'toast'; toastEl.setAttribute('role','status');
34      document.body.appendChild(toastEl);
35    }
36    toastEl.innerHTML = '<span class="ti"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m4 12 5.5 5.5L20 7"/></svg></span>' +
37      '<span class="tt"><b>Added to cart' + (q > 1 ? ' ×' + q : '') + '</b><span>' + esc(item.n) + '</span></span>' +
38      '<a href="' + ROOT + 'cart.html">View cart</a>';
39    toastEl.classList.add('show');
40    clearTimeout(toastT); toastT = setTimeout(function(){ toastEl.classList.remove('show'); }, 3600);
41  }
42
43  /* ---------------- add-to-cart buttons ---------------- */
44  document.addEventListener('click', function(e){
45    var b = e.target.closest('[data-add]'); if(!b) return;
46    e.preventDefault();
47    var item = {s:b.dataset.slug, n:b.dataset.name, p:parseFloat(b.dataset.price)||0, i:b.dataset.img||'', u:b.dataset.url||'', t:b.dataset.type||'book', v:b.dataset.cv||'cv0', a:b.dataset.auth||''};
48    var q = 1, qi = b.closest('[data-buy]'); qi = qi && qi.querySelector('.qty input');
49    if(qi) q = Math.max(1, parseInt(qi.value, 10) || 1);
50    add(item, q);
51    b.classList.add('added');
52    var old = b.innerHTML;
53    if(!b.dataset.keep){ b.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m4 12 5.5 5.5L20 7"/></svg>
53' + (b.classList.contains('btn') ? ' Added' : ''); }
54    setTimeout(function(){ b.classList.remove('added'); if(!b.dataset.keep) b.innerHTML = old; }, 1600);
55    if(b.dataset.go) location.href = ROOT + b.dataset.go;
56  });
57
58  /* qty steppers */
59  document.addEventListener('click', function(e){
60    var b = e.target.closest('.qty button'); if(!b) return;
61    var inp = b.parentNode.querySelector('input');
62    var v = (parseInt(inp.value, 10) || 1) + (b.dataset.d === '-' ? -1 : 1);
63    inp.value = Math.max(parseInt(inp.min || 1, 10), Math.min(99, v));
64    inp.dispatchEvent(new Event('change', {bubbles:true}));
65  });
66
67  /* ---------------- product thumbnails for cart/search (image or generated cover) ---------------- */
68  function thumb(it){
69    var t = it.t || 'book', v = it.v || 'cv0';
70    if(it.i) return thumbCover(it, t, v) + '<img src="' + esc(it.i) + '" alt="" loading="lazy" onerror="this.remove()">';
71    return thumbCover(it, t, v);
72  }
73  function thumbCover(it, t, v){
74    if(t === 'med') return '<span class="cover med ' + v + '"><span class="cap"></span><span class="lab"><span class="ct">' + esc(it.n) + '</span></span></span>';
75    if(t === 'av') return '<span class="cover av ' + v + '"><span class="play"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M7 4.5v15l13-7.5Z"/></svg></span><span class="ct">' + esc(it.n) + '</span></span>';
76    return '<span class="cover ' + v + '"><span class="ct">' + esc(it.n) + '</span></span>';
77  }
78
79  /* ---------------- category page: filter + sort ---------------- */
80  var grid = $('#pgrid');
81  if(grid){
82    var cards = $$('.pcard', grid), qIn = $('#pfilter'), sortSel = $('#psort'), cnt = $('#pcount'), none = $('#pnone');
83    function apply(){
84      var q = (qIn && qIn.value || '').trim().toLowerCase(), shown = 0;
85      cards.forEach(function(c){
86        var hit = !q || c.dataset.search.indexOf(q) > -1;
87        c.style.display = hit ? '' : 'none'; if(hit) shown++;
88      });
89      var mode = sortSel ? sortSel.value : 'featured';
90      var sorted = cards.slice().sort(function(a, b){
91        if(mode === 'low') return (+a.dataset.price || 1e9) - (+b.dataset.price || 1e9);
92        if(mode === 'high') return (+b.dataset.price) - (+a.dataset.price);
93        if(mode === 'az') return a.dataset.name.localeCompare(b.dataset.name);
94        if(mode === 'sale') return (+b.dataset.save) - (+a.dataset.save);
95        return (+a.dataset.idx) - (+b.dataset.idx);
96      });
97      sorted.forEach(function(c){ grid.appendChild(c); });
98      if(none){ grid.appendChild(none); none.style.display = shown ? 'none' : ''; }
99      if(cnt) cnt.textContent = shown;
100    }
101    if(qIn) qIn.addEventListener('input', apply);
102    if(sortSel) sortSel.addEventListener('change', apply);
103  }
104
105  /* ---------------- tabs ---------------- */
106  $$('.tabs').forEach(function(t){
107    var btns = $$('.tabbtns button', t), panels = $$('.tabpanel', t);
108    btns.forEach(function(b, i){
109      b.addEventListener('click', function(){
110        btns.forEach(function(x, j){ x.setAttribute('aria-selected', j === i ? 'true' : 'false'); panels[j].hidden = j !== i; });
111      });
112    });
113  });
114
115  /* ---------------- segmented (account login/register) ---------------- */
116  $$('.segs').forEach(function(s){
117    var btns = $$('button', s);
118    btns.forEach(function(b){
119      b.addEventListener('click', function(){
120        btns.forEach(function(x){ x.setAttribute('aria-selected', x === b ? 'true' : 'false'); var p = document.getElementById(x.dataset.panel); if(p) p.hidden = x !== b; });
121      });
122    });
123    if(/[?&]tab=register/.test(location.search)){ var r = btns.filter(function(b){ return b.dataset.panel === 'p-register'; })[0]; if(r) r.click(); }
124  });
125
126  /* ---------------- article filters (chips + text) ---------------- */
127  var ag = $('#agrid');
128  if(ag){
129    var acards = $$('[data-cat]', ag), chips = $$('.chip[data-filter]'), af = $('#artfilter'), acnt = $('#acount'), cur = 'all';
130    function aapply(){
131      var q = (af && af.value || '').trim().toLowerCase(), n = 0;
132      acards.forEach(function(c){
133        var hit = (cur === 'all' || c.dataset.cat === cur) && (!q || c.dataset.title.indexOf(q) > -1);
134        c.style.display = hit ? '' : 'none'; if(hit) n++;
135      });
136      if(acnt) acnt.textContent = n;
137      var none = $('#anone'); if(none) none.style.display = n ? 'none' : '';
138    }
139    chips.forEach(function(ch){
140      ch.addEventListener('click', function(){
141        cur = ch.dataset.filter;
142        chips.forEach(function(x){ x.setAttribute('aria-pressed', x === ch ? 'true' : 'false'); });
143        aapply();
144      });
145    });
146    if(af) af.addEventListener('input', aapply);
147  }
148
149  /* ---------------- links filter ---------------- */
150  var lf = $('#linkfilter');
151  if(lf){
152    lf.addEventListener('input', function(){
153      var q = lf.value.trim().toLowerCase();
154      $$('.lsec').forEach(function(sec){
155        var n = 0;
156        $$('.lk', sec).forEach(function(l){ var hit = !q || l.textContent.toLowerCase().indexOf(q) > -1; l.style.display = hit ? '' : 'none'; if(hit) n++; });
157        sec.style.display = n ? '' : 'none';
158      });
159    });
160  }
161
162  /* ---------------- info-page TOC highlight + progress ---------------- */
163  (function(){
164    var links = $$('.pg-toc a, .toc a');
165    if(!links.length) return;
166    var secs = links.map(function(a){ return document.getElementById(a.getAttribute('href').slice(1)); });
167    var bar = $('#pg-progress'), main = $('.pg-main') || $('.prose');
168    function onScroll(){
169      var y = window.scrollY + 150, cur = 0;
170      secs.forEach(function(s, i){ if(s && s.getBoundingClientRect().top + window.scrollY <= y) cur = i; });
171      links.forEach(function(a, i){ a.classList.toggle('on', i === cur); });
172      if(bar && main){
173        var r = main.getBoundingClientRect(), total = r.height - window.innerHeight * .6;
174        bar.style.width = Math.max(0, Math.min(100, (-r.top + 120) / Math.max(1, total) * 100)) + '%';
175      }
176    }
177    window.addEventListener('scroll', onScroll, {passive:true}); onScroll();
178  })();
179
180  /* ---------------- share (copy link) ---------------- */
181  /* share links: filled with this page's own address */
182  $$('[data-share]').forEach(function(a){
183    a.href = a.getAttribute('data-share').replace('{u}', encodeURIComponent(location.href)).replace('{t}', encodeURIComponent(document.title));
184  });
185  /* product gallery: thumbnails swap the main photo */
186  $$('.pd-gthumbs button').forEach(function(b){
187    b.addEventListener('click', function(){
188      var fr = document.querySelector('.pd-frame img'); if(fr) fr.src = b.getAttribute('data-full');
189      $$('.pd-gthumbs button').forEach(function(x){ x.classList.toggle('on', x === b); });
190    });
191  });
192  $$('[data-copy]').forEach(function(b){
193    b.addEventListener('click', function(){
194      var done = function(){ b.setAttribute('title', 'Link copied!'); b.style.background = 'var(--green)'; b.style.color = '#fff'; };
195      if(navigator.clipboard) navigator.clipboard.writeText(location.href).then(done, done); else done();
196    });
197  });
198
199  /* ---------------- demo forms (newsletter, contact, account) ---------------- */
200  $$('form[data-demo]').forEach(function(f){
201    f.addEventListener('submit', function(e){
202      e.preventDefault();
203      if(!f.checkValidity()){ f.reportValidity(); return; }
204      var ok = document.getElementById(f.dataset.demo);
205      f.style.display = 'none';
206      if(ok){ ok.hidden = false; ok.scrollIntoView({behavior:'smooth', block:'center'}); }
207    });
208  });
209
210  /* ---------------- CART PAGE ---------------- */
211  var cartRoot = $('#cart-root');
212  function shipCost(dest, sub){
213    if(!sub) return 0;
214    if(dest === 'ca') return 35; if(dest === 'intl') return 48;
215    return 8.5;
216  }
217  function renderCart(){
218    if(!cartRoot) return;
219    var c = load(), keys = Object.keys(c);
220    if(!keys.length){
221      cartRoot.innerHTML = '<div class="cartbox"><div class="cart-empty"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="20" r="1.3"/><circle cx="19" cy="20" r="1.3"/><path d="M2 3h3l2.4 11.6a1.8 1.8 0 0 0 1.8 1.4h8.6a1.8 1.8 0 0 0 1.8-1.4L21.5 7H6"/></svg><h2>Your cart is empty</h2><p>Browse our books, medicines, kits and e-courses — everything is hand-picked by Dana Ullman and our team.</p><a class="btn btn-green" href="' + ROOT + 'shop.html">Visit the store <em>&rarr;</em></a></div></div>';
222      $('#cart-summary') && ($('#cart-summary').style.display = 'none');
223      return;
224    }
225    $('#cart-summary') && ($('#cart-summary').style.display = '');
226    var rows = keys.map(function(k){
227      var it = c[k];
228      return '<div class="citem" data-k="' + esc(k) + '"><a class="th" href="' + ROOT + esc(it.u) + '">' + thumb(it) + '</a>' +
229        '<div><a class="nm" href="' + ROOT + esc(it.u) + '">' + esc(it.n) + '</a><div class="row"><span class="qty sm"><button type="button" data-d="-" aria-label="Decrease">&minus;</button><input type="number" min="1" max="99" value="' + it.q + '" aria-label="Quantity"><button type="button" data-d="+" aria-label="Increase">+</button></span><span class="unit">' + money(it.p) + ' each</span><button class="rm" type="button">Remove</button></div></div>' +
230        '<div class="lt">' + money(it.p * it.q) + '</div></div>';
231    }).join('');
232    cartRoot.innerHTML = '<div class="cartbox"><div class="hd"><h2>Your cart <span class="muted" style="font-size:16px">(' + count(c) + ')</span></h2><button type="button" id="cart-clear">Clear cart</button></div>' + rows + '</div>';
233    paintSummary();
234  }
235  function paintSummary(){
236    var s = subtotal(), dest = ($('#ship-to') || {}).value || 'us', sh = shipCost(dest, s);
237    var set = function(id, v){ var el = document.getElementById(id); if(el) el.textContent = v; };
238    set('sum-sub', money(s)); set('sum-ship', s ? money(sh) + (dest === 'us' ? '' : ' (est.)') : '—'); set('sum-total', money(s + sh));
239    set('sum-count', count());
240  }
241  if(cartRoot){
242    renderCart();
243    cartRoot.addEventListener('change', function(e){
244      var inp = e.target.closest('.citem input'); if(!inp) return;
245      var k = inp.closest('.citem').dataset.k, c = load();
246      c[k].q = Math.max(1, Math.min(99, parseInt(inp.value, 10) || 1)); save(c); renderCart();
247    });
248    cartRoot.addEventListener('click', function(e){
249      if(e.target.closest('.rm')){ var k = e.target.closest('.citem').dataset.k, c = load(); delete c[k]; save(c); renderCart(); }
250      if(e.target.id === 'cart-clear'){ save({}); renderCart(); }
251    });
252    var st = $('#ship-to'); if(st) st.addEventListener('change', paintSummary);
253  }
254
255  /* ---------------- CHECKOUT PAGE ---------------- */
256  var co = $('#checkout-lines');
257  if(co){
258    var c = load(), keys = Object.keys(c);
259    if(!keys.length){ co.innerHTML = '<p class="muted">Your cart is empty. <a href="' + ROOT + 'shop.html" style="color:var(--green);font-weight:700">Visit the store &rarr;</a></p>'; }
260    else co.innerHTML = keys.map(function(k){ var it = c[k]; return '<div class="ln"><span>' + esc(it.n) + ' <span class="muted">×' + it.q + '</span></span><b>' + money(it.p * it.q) + '</b></div>'; }).join('');
261    var dsel = $('#co-country');
262    function coTotals(){
263      var s = subtotal(), d = dsel ? dsel.value : 'us', sh = shipCost(d, s);
264      $('#co-sub').textContent = money(s); $('#co-ship').textContent = s ? money(sh) : '—'; $('#co-total').textContent = money(s + sh);
265    }
266    if(dsel) dsel.addEventListener('change', coTotals); coTotals();
267    var cf = $('#checkout-form');
268    if(cf) cf.addEventListener('submit', function(e){
269      e.preventDefault(); if(!cf.checkValidity()){ cf.reportValidity(); return; }
270      var num = 'HES-' + String(Date.now()).slice(-6);
271      $('#co-num').textContent = num;
272      $('#co-wrap').hidden = true; $('#co-done').hidden = false; save({}); window.scrollTo({top:0, behavior:'smooth'});
273    });
274  }
275
276  /* ---------------- SEARCH PAGE ---------------- */
277  var sr = $('#search-results');
278  if(sr && window.HES_DATA){
279    var params = new URLSearchParams(location.search), q = (params.get('s') || '').trim();
280    var inp = $('#q-page'); if(inp) inp.value = q;
281    var P = HES_DATA.products, A = HES_DATA.articles;
282    var SYN = {'teething':'teeth|infant|baby|child|chamomilla','hay fever':'allerg|sinus','potency':'potenc|30c|200c|kit','family kit':'family|kit','flu':'flu|influenz|oscillo','cold':'cold|flu|sinus','sleep':'sleep|calm|stress','stress':'stress|calm|rescue|emotional','injury':'arnica|traumeel|injur|sprain|musculoskeletal|pain','pain':'pain|arnica|traumeel|relief','women':'women|woman|pregnan|menopause|cyclease','children':'child|infant|kids|baby','allergy':'allerg|sinus','digestion':'indigestion|acidil|digest|nux'};
283    function terms(q){
284      var ql = q.toLowerCase(), extra = [];
285      for(var k in SYN){ if(ql.indexOf(k) > -1) extra = extra.concat(SYN[k].split('|')); }
286      return {words: ql.split(/\s+/).filter(Boolean), extra: extra};
287    }
288    function score(text, t){
289      text = text.toLowerCase(); var s = 0;
290      t.words.forEach(function(w){ if(text.indexOf(w) > -1) s += 3; });
291      if(t.words.length > 1 && text.indexOf(t.words.join(' ')) > -1) s += 4;
292      t.extra.forEach(function(w){ if(text.indexOf(w) > -1) s += 1; });
293      return s;
294    }
295    var prodHits = [], artHits = [];
296    if(q){
297      var t = terms(q);
298      prodHits = P.map(function(p){ return [score(p.n + ' ' + p.a + ' ' + p.k, t), p]; }).filter(function(x){ return x[0] > 0; }).sort(function(a, b){ return b[0] - a[0]; }).map(function(x){ return x[1]; });
299      artHits = A.map(function(a){ return [score(a.t + ' ' + a.c, t), a]; }).filter(function(x){ return x[0] > 0; }).sort(function(a, b){ return b[0] - a[0]; }).map(function(x){ return x[1]; });
300    }
301    var head = $('#search-head');
302    if(head) head.innerHTML = q ? 'Results for <i>&ldquo;' + esc(q) + '&rdquo;</i>' : 'Search the <i>store &amp; library</i>';
303    var sub = $('#search-sub');
304    if(sub) sub.textContent = q ? (prodHits.length + ' products and ' + artHits.length + ' articles found') : 'Search 1,000+ medicines, books, kits, e-courses and free articles.';
305    function pcard(p){
306      var price = p.p ? '<span class="kprice"><span class="now">' + money(p.p) + '</span>' + (p.r && p.r > p.p ? '<span class="was">' + money(p.r) + '</span>' : '') + '</span>' : '<span class="kprice"><span class="now" style="font-size:13px">Ask us</span></span>';
307      return '<article class="pcard"><a class="pimg2" href="' + ROOT + p.u + '" tabindex="-1" aria-hidden="true">' + thumb(p) + '</a><div class="pbody"><a class="ptitle" href="' + ROOT + p.u + '">' + esc(p.n) + '</a><span class="pauth">' + esc(p.a || p.cl) + '</span><div class="pfoot">' + price +
308        (p.p ? '<button class="addbtn" type="button" data-add data-slug="' + esc(p.s) + '" data-name="' + esc(p.n) + '" data-price="' + p.p + '" data-img="' + esc(p.i) + '" data-url="' + esc(p.u) + '" data-type="' + p.t + '" data-cv="' + p.v + '" aria-label="Add to cart"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="20" r="1.3"/><circle cx="19" cy="20" r="1.3"/><path d="M2 3h3l2.4 11.6a1.8 1.8 0 0 0 1.8 1.4h8.6a1.8 1.8 0 0 0 1.8-1.4L21.5 7H6"/></svg></button>' : '') + '</div></div></article>';
309    }
310    function acard(a){
311      return '<a class="acard2" href="' + (a.x ? a.u : ROOT + a.u) + '"' + (a.x ? ' target="_blank" rel="noopener"' : '') + '><span class="ab"><span class="cat">' + esc(a.c) + '</span><h3>' + esc(a.t) + '</h3><span class="go">' + (a.x ? 'Read on homeopathic.com ↗' : 'Read article &rarr;') + '</span></span></a>';
312    }
313    var tabs = $$('.srtabs .chip'), mode = 'all';
314    function paint(){
315      var html = '';
316      if(!q){ html = '<div class="empty-res"><b>What are you looking for?</b>Try a remedy, a symptom, an author or a book title — for example <a href="?s=arnica" style="color:var(--green);font-weight:700">Arnica</a>, <a href="?s=teething" style="color:var(--green);font-weight:700">teething</a> or <a href="?s=Ullman" style="color:var(--green);font-weight:700">Ullman</a>.</div>'; sr.innerHTML = html; return; }
317      if(mode !== 'articles'){
318        html += '<h2 style="font-size:28px;font-weight:400;margin:6px 0 18px">Products <span class="muted" style="font-size:16px">(' + prodHits.length + ')</span></h2>';
319        html += prodHits.length ? '<div class="pgrid2">' + prodHits.slice(0, mode === 'all' ? 24 : 200).map(pcard).join('') + '</div>' : '<div class="empty-res"><b>No products matched.</b>Try a broader word, or browse the <a href="' + ROOT + 'shop.html" style="color:var(--green);font-weight:700">full store</a>.</div>';
320      }
321      if(mode !== 'products'){
322        html += '<h2 style="font-size:28px;font-weight:400;margin:40px 0 18px">Free articles <span class="muted" style="font-size:16px">(' + artHits.length + ')</span></h2>';
323        html += artHits.length ? '<div class="agrid">' + artHits.slice(0, mode === 'all' ? 9 : 200).map(acard).join('') + '</div>' : '<div class="empty-res"><b>No articles matched.</b>Browse all <a href="' + ROOT + 'articles.html" style="color:var(--green);font-weight:700">150+ free articles</a>.</div>';
324      }
325      sr.innerHTML = html;
326    }
327    tabs.forEach(function(ch){ ch.addEventListener('click', function(){ mode = ch.dataset.mode; tabs.forEach(function(x){ x.setAttribute('aria-pressed', x === ch ? 'true' : 'false'); }); paint(); }); });
328    paint();
329  }
330})();

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.