1// assets/reel.js 2(function(){ 3 const $ = (s, r=document)=>r.querySelector(s); 4 const $$ = (s, r=document)=>[...r.querySelectorAll(s)]; 5 const YT_HOVER = 'autoplay=1&mute=1&playsinline=1&controls=0&modestbranding=1&rel=0&iv_load_policy=3&cc_load_policy=0&fs=0&disablekb=1'; 6 7 const ytId = (src)=>{ 8 try{ 9 const u=new URL(src,location.origin); 10 if(u.hostname.includes('youtu.be')) return u.pathname.split('/').pop(); 11 if(u.searchParams.get('v')) return u.searchParams.get('v'); 12 const p=u.pathname.split('/'); const i=p.findIndex(x=>['embed','shorts','watch'].includes(x)); 13 return (i>=0 && p[i+1])?p[i+1]:p.pop(); 14 }catch{ return src.split('/').pop(); } 15 }; 16 17 function ensureHit(thumb, label='à pne video'){ 18 let hit = thumb.querySelector('.reel-hit'); 19 if (!hit){ 20 hit = document.createElement('button'); 21 hit.className = 'reel-hit'; 22 hit.type = 'button'; 23 hit.setAttribute('aria-label', label); 24 thumb.appendChild(hit); 25 } 26 return hit; 27 } 28 29 function mountLocalHover(thumb, src, poster){ 30 if(thumb.__mount) return; 31 const v=document.createElement('video'); 32 v.src=src; if(poster) v.poster=poster; 33 v.muted=true; v.playsInline=true; v.loop=true; v.preload='metadata'; 34 v.addEventListener('canplay', ()=>{ thumb.classList.add('is-ready'); v.play().catch(()=>{}); }, {once:true}); 35 thumb.appendChild(v); 36 thumb.__mount=v; 37 } 38 39 function mountYTHover(thumb, src){ 40 if(thumb.__mount) return; 41 const id=ytId(src); 42 const ifr=document.createElement('iframe'); 43 ifr.src=`https://www.youtube.com/embed/${id}?${YT_HOVER}`; 44 ifr.allow='autoplay; encrypted-media; picture-in-picture'; 45 ifr.setAttribute('allowfullscreen',''); 46 ifr.addEventListener('load', ()=>{ thumb.classList.add('is-ready'); }, {once:true}); 47 thumb.appendChild(ifr); 48 thumb.__mount=ifr; 49 } 50 51 function unmountHover(thumb){ 52 if(!thumb.__mount) return; 53 thumb.__mount.pause?.(); 54 thumb.__mount.remove(); 55 thumb.__mount=null; 56 thumb.classList.remove('is-ready'); 57 } 58 59 const root = $('[data-reel-carousel]'); if(!root) return; 60 61 // Pause lokale videoer når kort er utenfor view 62 const io = new IntersectionObserver(ents=>{ 63 ents.forEach(e=>{ 64 const v=e.target.querySelector('video'); 65 if(!v) return; 66 e.isIntersecting ? v.play().catch(()=>{}) : v.pause(); 67 }); 68 },{threshold:.8}); 69 70 // Bind hover + legg klikk-lag (klikk håndteres av reel-modal.js via event bubbling) 71 $$('.reel-item', root).forEach(card=>{ 72 io.observe(card); 73 const thumb = card.querySelector('.reel-thumb'); if(!thumb) return; 74 ensureHit(thumb, 'Spill av i fullskjerm'); 75 76 if (window.matchMedia('(pointer:fine)').matches){ 77 thumb.addEventListener('mouseenter', ()=>{ 78 const {type, src, poster}=card.dataset; 79 if(type==='local') mountLocalHover(thumb, src, poster); 80 else if(type==='youtube') mountYTHover(thumb, src); 81 }); 82 thumb.addEventListener('mouseleave', ()=> unmountHover(thumb)); 83 } 84 }); 85})(); 86 87// Piler 88document.addEventListener('DOMContentLoaded', ()=>{ 89 const carousels = document.querySelectorAll('[data-reel-carousel]'); 90 carousels.forEach(car => { 91 const list = car.querySelector('.reel-list'); 92 if (!list) return; 93 94 // lag piler 95 const makeBtn = (dir) => { 96 const b = document.createElement('button'); 97 b.className = `reel-arrow ${dir}`; 98 b.type = 'button'; 99 b.innerHTML = dir === 'prev' 100 ? '<svg viewBox="0 0 24 24"><path fill="currentColor" d="M15.4 3.4 9 9.8 15.4 16.2 14 17.6 6.4 10 14 2.4z"/></svg>' 101 : '<svg viewBox="0 0 24 24"><path fill="currentColor" d="M8.6 3.4 15 9.8 8.6 16.2 10 17.6 17.6 10 10 2.4z"/></svg>'; 102 return b; 103 }; 104 105 const prev = makeBtn('prev'); 106 const next = makeBtn('next'); 107 car.appendChild(prev); 108 car.appendChild(next); 109 110 const scrollStep = () => list.clientWidth * 0.8; 111 112 prev.addEventListener('click', ()=> list.scrollBy({ left: -scrollStep(), behavior: 'smooth' })); 113 next.addEventListener('click', ()=> list.scrollBy({ left: scrollStep(), behavior: 'smooth' })); 114 }); 115});
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.