PageSourceSearch

https://www.adelavshistorie.cz/app.js

js adelavshistorie.cz collected 2026-10-09 00:50:37 UTC 19,348 bytes, 380 lines download raw bytes

1/* ============================================================
2   ⚙️  CONFIG — EDIT EVERYTHING HERE. One place, propagates everywhere.
3   ============================================================ */
4var CONFIG = {
5  /* Headline numbers — you said these have grown. Update them here. */
6  stats: {
7    followers:    "48 300",     // sledujících
8    views:        "18 M",       // zhlédnutí za 4 měsíce
9    engagement:   "6,8 %",      // průměrný engagement
10    waitlist:     "500+",       // na waitlistu / čekačce
11    seatsTvurce:   "0",         // volná místa v tarifu Tvůrce — VYPRODÁNO
12    seatsFeedback: "0",         // volná místa v tarifu Tvůrce + Feedback — VYPRODÁNO
13    seatsMentor:   "0"          // místa v Mentoring 1:1 — VYPRODÁNO
14  },
15
16  /* Honest countdown — set to the EXACT moment sales open + 72 hours (CET/CEST).
17     Example: opening 2026-07-06T18:00 → deadline 2026-07-09T18:00:00+02:00. */
18  deadline: "2026-07-09T18:00:00+02:00",
19
20  /* Stripe Payment Links — paste the 3 URLs. Empty = button stays a safe placeholder. */
21  stripe: {
22    tvurce:   "",   // AI Tvůrce — VYPRODÁNO (payment link odstránený)
23    feedback: "",   // AI Čaroděj — VYPRODÁNO (payment link odstránený)
24    mentor:   ""    // 1:1 Mentoring — VYPRODÁNO (payment link odstránený)
25  },
26
27  /* Social profile URLs (canonical, no tracking params). */
28  social: {
29    instagram: "https://www.instagram.com/adela.vs.historie/",
30    tiktok:    "https://www.tiktok.com/@adela.vs.historie",
31    youtube:   "https://www.youtube.com/@adela.vs.historie"
32  },
33
34  /* Currency. Default shown on load; 'auto' would need geo — kept manual + persistent. */
35  currency: { default: "czk" },
36
37  /* IG-safe analytics. Vercel Web Analytics is wired automatically when deployed on
38     Vercel (see the insights <script> in index.html). Plausible/GA4/Meta pixel are
39     picked up automatically if you paste their snippet into <head>. */
40  analyticsId: ""
41};
42
43/* Vercel Web Analytics bootstrap (safe no-op anywhere else). */
44window.va = window.va || function(){ (window.vaq = window.vaq || []).push(arguments); };
45
46(function(){
47  "use strict";
48  var reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
49  var $  = function(s,c){return (c||document).querySelector(s);};
50  var $$ = function(s,c){return Array.prototype.slice.call((c||document).querySelectorAll(s));};
51
52  /* ---------- 0. ANALYTICS (IG-webview-safe; degrades to no-op) ---------- */
53  function track(event, props){
54    try{
55      // Vercel Web Analytics custom events
56      if(window.va) window.va('event', {name:event, data:props||{}});
57      // Plausible
58      if(window.plausible) window.plausible(event, {props:props||{}});
59      // GA4 (gtag)
60      if(window.gtag) window.gtag('event', event, props||{});
61      // Meta Pixel
62      if(window.fbq) window.fbq('trackCustom', event, props||{});
63      // Always available for debugging / your own collector:
64      (window.dataLayer = window.dataLayer || []).push(Object.assign({event:event}, props||{}));
65    }catch(e){/* never let tracking break the page in the IG webview */}
66  }
67  window.__track = track;
68
69  /* ---------- 1. INJECT CONFIG VALUES ---------- */
70  // stats
71  Object.keys(CONFIG.stats).forEach(function(k){
72    $$('[data-stat="'+k+'"]').forEach(function(el){
73      // skip the count-up element's initial text (handled in §6) but still set it as fallback
74      el.textContent = CONFIG.stats[k];
75    });
76  });
77  // social links
78  $$('[data-social]').forEach(function(a){
79    var url = CONFIG.social[a.getAttribute('data-social')];
80    if(url) a.setAttribute('href', url);
81  });
82  // stripe buy buttons
83  $$('[data-buy]').forEach(function(a){
84    var url = CONFIG.stripe[a.getAttribute('data-buy')];
85    if(url){ a.setAttribute('href', url); a.removeAttribute('aria-disabled'); }
86    else { a.setAttribute('aria-disabled','true'); a.setAttribute('title','Doplň Stripe odkaz v CONFIG'); }
87    a.addEventListener('click', function(){ track('cta_buy', {tier:a.getAttribute('data-buy')}); });
88  });
89  // generic CTAs (anchors to pricing)
90  $$('[data-cta]').forEach(function(a){
91    a.addEventListener('click', function(){ track('cta_click', {where:a.getAttribute('data-cta')}); });
92  });
93
94  /* ---------- 2. STICKY NAV: hide on scroll-down, show on scroll-up (rAF-throttled) ---------- */
95  var nav = $('#nav'), lastY = window.pageYOffset, navTick=false;
96  window.addEventListener('scroll', function(){
97    if(navTick) return; navTick=true;
98    requestAnimationFrame(function(){
99      var y = window.pageYOffset;
100      if(y > lastY && y > 200) nav.classList.add('hide'); else nav.classList.remove('hide');
101      lastY = y; navTick=false;
102    });
103  }, {passive:true});
104
105  /* ---------- 3. MOBILE MENU ---------- */
106  var burger=$('#burger'), sheet=$('#sheet');
107  function setMenu(open){
108    sheet.classList.toggle("open",open);
109    sheet.setAttribute("aria-hidden",String(!open));
110    burger.setAttribute("aria-expanded",String(open));
111    document.body.style.overflow=open?"hidden":"";
112  }
113  burger.addEventListener("click",function(){setMenu(!sheet.classList.contains("open"));});
114  $$("[data-close]").forEach(function(a){ a.addEventListener("click",function(){setMenu(false);}); });
115
116  /* ---------- 4. ACCORDIONS (modules + faq) ---------- */
117  function wireAccordion(sel){
118    $$(sel).forEach(function(item){
119      var head=item.querySelector("button");
120      head.addEventListener("click",function(){
121        var open = item.classList.toggle("open");
122        head.setAttribute("aria-expanded", String(open));
123        if(open) track('accordion_open',{section:sel,label:(head.textContent||'').trim().slice(0,40)});
124        setTimeout(measureDoc,450); /* perf(P2.3): akordeón mení výšku dokumentu (transition .4s) — obnov scroll-depth cache po rozbalení */
125      });
126    });
127  }
128  wireAccordion("[data-mod]");
129  wireAccordion("[data-faq]");
130  var firstMod=$("[data-mod]");
131  if(firstMod){ firstMod.classList.add("open"); firstMod.querySelector('button').setAttribute('aria-expanded','true'); }
132
133  /* ---------- 5. SCROLL REVEALS ---------- */
134  if("IntersectionObserver" in window && !reduce){
135    var io=new IntersectionObserver(function(entries){
136      entries.forEach(function(e){ if(e.isIntersecting){ e.target.classList.add("is-visible"); io.unobserve(e.target);} });
137    },{threshold:0.14, rootMargin:"0px 0px -8% 0px"});
138    $$(".reveal").forEach(function(el){io.observe(el);});
139  } else {
140    $$(".reveal").forEach(function(el){el.classList.add("is-visible");});
141  }
142
143  /* ---------- 6. COUNT-UP for the followers stat ---------- */
144  var counted=false, cEl=$("[data-count]");
145  function parseNum(s){ return parseInt(String(s).replace(/[^\d]/g,''),10)||0; }
146  function runCount(){
147    if(counted||!cEl) return; counted=true;
148    var finalText = CONFIG.stats.followers, target = parseNum(finalText);
149    if(reduce||!target){ cEl.textContent=finalText; return; }
150    var start=null, dur=1400;
151    function step(ts){ if(!start)start=ts; var p=Math.min((ts-start)/dur,1);
152      var val=Math.floor((1-Math.pow(1-p,3))*target);
153      cEl.textContent=val.toLocaleString("cs-CZ");
154      if(p<1) requestAnimationFrame(step); else cEl.textContent=finalText;
155    }
156    requestAnimationFrame(step);
157  }
158  if("IntersectionObserver" in window && cEl){
159    var io2=new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting){runCount();io2.disconnect();}});},{threshold:.6});
160    io2.observe(cEl);
161  } else { runCount(); }
162
163  /* ---------- 7. STICKY MOBILE CTA after hero ----------
164     Hysteréza: show fire až 80 px ZA hranicou (rozšírený root), hide pri návrate na hranicu = 80px dead zone, žiadne blikanie.
165     Footer sentinel: keď je footer vo viewporte, bar sa schová, aby neprekrýval záverečný obsah. */
166  var bar=$('#stickybar'), heroEnd=$('#heroEnd'), footerEl=$('.footer'), barShown=false, pastHero=false, atFooter=false;
167  function syncBar(){
168    var show = pastHero && !atFooter;
169    bar.classList.toggle("show", show);
170    if(show && !barShown){ barShown=true; track('sticky_cta_shown'); }
171  }
172  if("IntersectionObserver" in window && heroEnd){
173    var ioShow=new IntersectionObserver(function(es){
174      es.forEach(function(e){ if(!e.isIntersecting && e.boundingClientRect.top<0){ pastHero=true; syncBar(); } });
175    },{threshold:0, rootMargin:"80px 0px 0px 0px"});
176    var ioHide=new IntersectionObserver(function(es){
177      es.forEach(function(e){ if(e.isIntersecting){ pastHero=false; syncBar(); } });
178    },{threshold:0});
179    ioShow.observe(heroEnd); ioHide.observe(heroEnd);
180    if(footerEl){
181      var ioFoot=new IntersectionObserver(function(es){
182        es.forEach(function(e){ atFooter=e.isIntersecting; syncBar(); });
183      },{threshold:0});
184      ioFoot.observe(footerEl);
185    }
186  }
187
188  /* ---------- 8. CURRENCY TOGGLE (CZK / EUR), persistent ---------- */
189  var curBtns=$$("[data-cur]");
190  function setCur(cur){
191    try{ localStorage.setItem('adela_cur', cur); }catch(e){}
192    curBtns.forEach(function(b){b.classList.toggle("active",b.getAttribute("data-cur")===cur);});
193    // price/value cells with a <small> sub-line
194    $$("[data-"+cur+"]").forEach(function(el){
195      var v=el.getAttribute("data-"+cur);
196      if(el.querySelector("small")) el.firstChild.nodeValue=v; else el.textContent=v;
197    });
198    // plain text swaps (e.g. sticky bar)
199    $$("[data-"+cur+"-text]").forEach(function(el){ el.textContent=el.getAttribute("data-"+cur+"-text"); });
200  }
201  curBtns.forEach(function(b){b.addEventListener("click",function(){ setCur(b.getAttribute("data-cur")); track('currency_switch',{cur:b.getAttribute('data-cur')}); });});
202  var savedCur; try{ savedCur=localStorage.getItem('adela_cur'); }catch(e){}
203  setCur(savedCur || CONFIG.currency.default || 'czk');
204
205  /* ---------- 9. COUNTDOWN (honest) + closed state ---------- */
206  var cd=$('#countdown'), DEADLINE=new Date(CONFIG.deadline), finalSec=$('#finalCta');
207  function pad(n){return (n<10?"0":"")+n;}
208  function setCd(d,h,m,s){
209    cd.querySelector('[data-cd="d"]').textContent=d;
210    cd.querySelector('[data-cd="h"]').textContent=h;
211    cd.querySelector('[data-cd="m"]').textContent=m;
212    cd.querySelector('[data-cd="s"]').textContent=s;
213  }
214  function tick(){
215    var diff=DEADLINE-Date.now();
216    if(diff<=0){ setCd("00","00","00","00"); finalSec.classList.add('closed'); return false; }
217    var s=Math.floor(diff/1000);
218    setCd(pad(Math.floor(s/86400)),pad(Math.floor(s%86400/3600)),pad(Math.floor(s%3600/60)),pad(s%60));
219    return true;
220  }
221  if(cd && !isNaN(DEADLINE.getTime())){ if(tick()) var cdTimer=setInterval(function(){ if(!tick()) clearInterval(cdTimer); },1000); }
222
223  /* ---------- 10. VIDEO MODAL (facade + focus trap + restore) ---------- */
224  var modal=$('#videoModal'), closeBtn=$('#closeVideo'), modalBox=$('#modalBox'), lastFocus=null;
225  /* Bunny Stream VSL — iframe se vytvoří až po otevření (nic se netáhne při načtení stránky). */
226  var VSL_SRC="https://player.mediadelivery.net/embed/697464/49b44806-c767-43ae-9b7c-9d1597f2e743?autoplay=true&muted=false&loop=false&preload=true&responsive=true";
227  function mountVsl(){
228    if(!modalBox || modalBox.querySelector('iframe')) return;
229    var f=document.createElement('iframe');
230    f.src=VSL_SRC;
231    f.setAttribute('allow','autoplay; encrypted-media; picture-in-picture; fullscreen');
232    f.setAttribute('allowfullscreen','true');
233    f.setAttribute('referrerpolicy','strict-origin-when-cross-origin');
234    f.setAttribute('title','Adéla: Virální s AI — video');
235    modalBox.appendChild(f);
236    track('vsl_play');
237  }
238  function unmountVsl(){ if(modalBox){ var f=modalBox.querySelector('iframe'); if(f) f.remove(); } }
239  function openModal(){ lastFocus=document.activeElement; modal.classList.add("open"); document.body.style.overflow="hidden"; mountVsl(); closeBtn.focus(); track('video_open'); }
240  function closeModal(){ modal.classList.remove("open"); document.body.style.overflow=""; unmountVsl(); if(lastFocus) lastFocus.focus(); }
241  ["openVideo","openVideo2"].forEach(function(id){var el=$('#'+id);
242    if(el){ el.addEventListener("click",openModal); el.addEventListener("keydown",function(e){if(e.key==="Enter"||e.key===" "){e.preventDefault();openModal();}});}});
243  closeBtn.addEventListener("click",closeModal);
244  modal.addEventListener("click",function(e){ if(e.target===modal) closeModal(); });
245  document.addEventListener("keydown",function(e){ if(e.key==="Escape"){ closeModal(); setMenu(false);} });
246
247  /* ---------- 11. "Více" cloud reveal ---------- */
248  $('#moreBtn').addEventListener("click",function(){
249    var p=$(".proof"); if(p) p.scrollIntoView({behavior:reduce?"auto":"smooth"});
250  });
251
252  /* ---------- 12. (parallax odstránený — layout read/write na scroll = jank; skill §8.4 ho zakazuje) ---------- */
253
254  /* ---------- 13. SCROLL DEPTH tracking (50% / 90%) — cached výška, žiadny forced reflow v scroll callbacku ---------- */
255  var depthHits={}, docH=0, depthTick=false;
256  function measureDoc(){ docH=document.documentElement.scrollHeight; }
257  measureDoc();
258  window.addEventListener('resize', measureDoc, {passive:true});
259  window.addEventListener('scroll', function(){
260    if(depthTick) return; depthTick=true;
261    requestAnimationFrame(function(){
262      var sc=(window.pageYOffset+window.innerHeight)/(docH||1)*100;
263      [50,90].forEach(function(t){ if(sc>=t && !depthHits[t]){ depthHits[t]=true; track('scroll_depth',{percent:t}); } });
264      depthTick=false;
265    });
266  }, {passive:true});
267
268  /* ---------- 14. HERO ENTRANCE ---------- */
269  requestAnimationFrame(function(){ $('#hero').classList.add('in'); });
270
271  /* ---------- 14b. perf(P0.2): pauza nekonečných animácií (marquee, skydrift) mimo viewportu ---------- */
272  var ioAnim=new IntersectionObserver(function(es){
273    es.forEach(function(e){ e.target.classList.toggle('anim-paused', !e.isIntersecting); });
274  },{threshold:0});
275  $$('.band').forEach(function(b){ ioAnim.observe(b); });
276  ioAnim.observe($('#hero'));
277
278  /* ---------- 15. PRÁVNÍ MODÁLY — nativní <dialog>, deep-link přes hash ---------- */
279  var legalDlg=$('#legalDialog'), legalTitle=$('#legalTitle'), legalBody=$('#legalBody');
280  var LEGAL_SLUGS=['obchodni-podminky','ochrana-soukromi','zasady-cookies'];
281  var legalInvoker=null, legalPushed=false;
282  function legalOpen(slug, push){
283    var tpl=document.getElementById('tpl-'+slug);
284    if(!tpl || !legalDlg || !legalDlg.showModal) return;
285    legalInvoker=document.activeElement;
286    legalTitle.textContent=tpl.getAttribute('data-title');
287    legalBody.innerHTML='';
288    legalBody.appendChild(tpl.content.cloneNode(true));
289    legalBody.scrollTop=0;
290    legalDlg.showModal();
291    legalPushed=false;
292    if(push!==false && location.hash!=='#'+slug){ history.pushState({legal:slug},'','#'+slug); legalPushed=true; }
293    track('legal_open',{doc:slug});
294  }
295  if(legalDlg && legalDlg.showModal){
296    $$('[data-legal]').forEach(function(a){
297      a.addEventListener('click',function(e){ e.preventDefault(); legalOpen(a.getAttribute('data-legal')); });
298    });
299    $('#legalClose').addEventListener('click',function(){ legalDlg.close(); });
300    legalDlg.addEventListener('click',function(e){ if(e.target===legalDlg) legalDlg.close(); }); // klik na backdrop
301    legalDlg.addEventListener('close',function(){
302      // hash uklidit jen když zavření nepřišlo z back buttonu (tam už je hash pryč)
303      if(LEGAL_SLUGS.indexOf(location.hash.slice(1))>-1){
304        if(legalPushed){ history.back(); } else { history.replaceState(null,'',location.pathname+location.search); }
305      }
306      legalPushed=false;
307      if(legalInvoker && legalInvoker.focus) legalInvoker.focus();
308    });
309    window.addEventListener('popstate',function(){
310      var h=location.hash.slice(1);
311      if(legalDlg.open && LEGAL_SLUGS.indexOf(h)===-1){ legalPushed=false; legalDlg.close(); }
312      else if(!legalDlg.open && LEGAL_SLUGS.indexOf(h)>-1){ legalOpen(h,false); }
313    });
314    // deep-link: sdílená URL otevře modal hned po načtení
315    var h0=location.hash.slice(1);
316    if(LEGAL_SLUGS.indexOf(h0)>-1) legalOpen(h0,false);
317  }
318
319  /* ---------- 16. COOKIE CONSENT — opt-in, reject stejně snadný, gating budoucích skriptů ---------- */
320  var CB_KEY='cookie-consent-v1', CB_VERSION=1; // při změně znění zásad zvyš verzi → lišta se ukáže znovu
321  var cb=$('#cookiebar');
322  function cbRead(){
323    try{ var v=JSON.parse(localStorage.getItem(CB_KEY));
323 return (v && v.version===CB_VERSION) ? v : null; }
324    catch(e){ return null; }
325  }
326  function cbApply(c){
327    /* GATING: ne-nezbytné skripty se vkládají VÝHRADNĚ tady, až po souhlasu.
328       Vercel Analytics je cookieless (bez souhlasu OK). Až přibude GA4/Meta Pixel:
329       if(c.analytics){ ...inject GA4 script... }
330       if(c.marketing){ ...inject Meta Pixel... } */
331    if(typeof window.gtag==='function'){ // Google Consent Mode v2 hook
332      window.gtag('consent','update',{
333        analytics_storage:c.analytics?'granted':'denied',
334        ad_storage:c.marketing?'granted':'denied',
335        ad_user_data:c.marketing?'granted':'denied',
336        ad_personalization:c.marketing?'granted':'denied'
337      });
338    }
339  }
340  function cbHide(){
341    cb.hidden=true; cb.classList.remove('cfg');
342    $('#cbSave').hidden=true; $('#cbCfg').setAttribute('aria-expanded','false');
343    document.documentElement.classList.remove('consent-pending'); // sticky bar zpět
344  }
345  function cbShow(withPanel){
346    var c=cbRead();
347    if(c){ $('#cbFunctional').checked=!!c.functional; $('#cbAnalytics').checked=!!c.analytics; $('#cbMarketing').checked=!!c.marketing; }
348    if(withPanel){ cb.classList.add('cfg'); $('#cbSave').hidden=false; $('#cbCfg').setAttribute('aria-expanded','true'); }
349    cb.hidden=false;
350    document.documentElement.classList.add('consent-pending'); // lišta má přednost před sticky CTA barem
351  }
352  function cbStore(functional,analytics,marketing){
353    var c={necessary:true,functional:!!functional,analytics:!!analytics,marketing:!!marketing,ts:new Date().toISOString(),version:CB_VERSION};
354    try{ localStorage.setItem(CB_KEY,JSON.stringify(c)); }catch(e){}
355    cbApply(c); cbHide();
356    track('cookie_consent',{functional:c.functional,analytics:c.analytics,marketing:c.marketing});
357  }
358  if(cb){
359    $('#cbAccept').addEventListener('click',function(){ cbStore(true,true,true); });
360    $('#cbReject').addEventListener('click',function(){ cbStore(false,false,false); });
361    $('#cbClose').addEventListener('click',function(){ cbStore(false,false,false); }); // křížek = odmítnutí, NE tichý souhlas
362    $('#cbCfg').addEventListener('click',function(){
363      var on=cb.classList.toggle('cfg');
364      $('#cbSave').hidden=!on; this.setAttribute('aria-expanded',on?'true':'false');
365    });
366    $('#cbSave').addEventListener('click',function(){
367      cbStore($('#cbFunctional').checked,$('#cbAnalytics').checked,$('#cbMarketing').checked);
368    });
369    var cs=$('#openCookieSettings');
370    if(cs) cs.addEventListener('click',function(e){ e.preventDefault(); cbShow(true); });
371    window.cookieConsent={
372      get:cbRead,
373      has:function(cat){ var c=cbRead(); return cat==='necessary' ? true : !!(c && c[cat]); },
374      open:function(){ cbShow(true); }
375    };
376    var stored=cbRead();
377    if(stored){ cbApply(stored); } else { cbShow(false); }
378  }
379
380})();

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.