1(function () { 2 if (customElements.get('site-carousel')) return; 3 4 var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; 5 6 class SiteCarousel extends HTMLElement { 7 connectedCallback() { 8 // --- 1. СÑиÑÑваем ÑÐ»Ð°Ð¹Ð´Ñ Ð¸Ð· light DOM (Ñо, ÑÑо положил ÑедакÑоÑ/XSLT) --- 9 // ÐоддеÑживаÑÑÑÑ: <a><img></a>, <a><video></a>, <video> без ÑÑÑлки 10 var nodes = Array.prototype.slice.call(this.children).filter(function (el) { 11 var t = el.tagName.toLowerCase(); 12 return t === 'a' || t === 'video'; 13 }); 14 15 this._slides = nodes.map(function (el) { 16 var isLink = el.tagName.toLowerCase() === 'a'; 17 var media = isLink ? el.querySelector('video, img') : el; 18 if (!media) return null; 19 var isVideo = media.tagName.toLowerCase() === 'video'; 20 var interval = parseInt(el.getAttribute('data-interval') || 21 media.getAttribute('data-interval'), 10); 22 return { 23 type: isVideo ? 'video' : 'image', 24 href: isLink ? (el.getAttribute('href') || '') : '', 25 target: isLink ? (el.getAttribute('target') || '_self') : '_self', 26 rel: isLink ? (el.getAttribute('rel') || '') : '', 27 src: media.getAttribute('src') || '', 28 poster: media.getAttribute('poster') || '', 29 thumb: media.getAttribute('thumb') || media.getAttribute('poster') || '', 30 alt: media.getAttribute('alt') || media.getAttribute('data-alt') || 31 (isLink ? (el.getAttribute('aria-label') || '') : ''), 32 interval: isNaN(interval) ? null : interval 33 }; 34 }).filter(function (s) { return s && s.src; }); 35 36 if (!this._slides.length) return; 37 38 this._interval = parseInt(this.getAttribute('interval'), 10); 39 if (isNaN(this._interval)) this._interval = 5000; 40 this._perView = parseInt(this.getAttribute('thumbs'), 10) || 4; 41 this._index = 0; 42 this._timer = null; 43 this._videos = {}; // Ð¸Ð½Ð´ÐµÐºÑ Ñлайда -> <video> в shadow DOM 44 this._soundOn = false; 45 46 // Ñважение к ÑиÑÑемной наÑÑÑойке "менÑÑе движениÑ" 47 this._reduceMotion = window.matchMedia && 48 window.matchMedia('(prefers-reduced-motion: reduce)').matches; 49 50 this._render(); 51 this._bind(); 52 this._updateSound(); 53 this._start(); 54 55 this.style.setProperty('--sc-hidden', '0'); // Ñлаг, ÑÑо JS оÑÑабоÑал 56 } 57 58 disconnectedCallback() { this._stop(); } 59 60 // ----------------------------------------------------------------- // 61 _render() { 62 var root = this.attachShadow({ mode: 'open' }); 63 64 var slidesHtml = this._slides.map(function (s, i) { 65 var tag = s.href ? 'a' : 'div'; 66 var attrs = 'class="slide' + (i === 0 ? ' is-active' : '') + '" ' 67 + 'data-i="' + i + '" aria-hidden="' + (i === 0 ? 'false' : 'true') + '"'; 68 if (s.href) { 69 attrs += ' href="' + esc(s.href) + '" target="' + esc(s.target) + '"' 70 + (s.rel ? ' rel="' + esc(s.rel) + '"' : ''); 71 } 72 73 var inner = s.type === 'video' 74 ? '<video src="' + esc(s.src) + '" ' 75 + (s.poster ? 'poster="' + esc(s.poster) + '" ' : '') 76 + (s.alt ? 'aria-label="' + esc(s.alt) + '" ' : '') 77 + 'preload="metadata" playsinline muted disablepictureinpicture></video>' 78 : '<img src="' + esc(s.src) + '" alt="' + esc(s.alt) + '" ' 79 + 'loading="' + (i === 0 ? 'eager' : 'lazy') + '" draggable="false">'; 80 81 return '<' + tag + ' ' + attrs + '>' + inner + '</' + tag + '>'; 82 }).join(''); 83 84 var thumbsHtml = this._slides.map(function (s, i) { 85 return '' 86 + '<button class="thumb' + (i === 0 ? ' is-active' : '') 87 + (s.type === 'video' ? ' is-video' : '') + '" ' 88 + 'type="button" data-i="' + i + '" '
89 + 'aria-label="ÐеÑейÑи к ÑÐ»Ð°Ð¹Ð´Ñ ' + (i + 1) + '">' 90 + '<img src="' + esc(s.thumb || BLANK) + '" alt="" loading="lazy" draggable="false">' 91 + (s.type === 'video' 92 ? '<span class="play" aria-hidden="true">' 93 + '<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg></span>' 94 : '') 95 + '</button>'; 96 }).join(''); 97 98 root.innerHTML = this._css(this._perView) 99 + '<div class="sc" tabindex="0" aria-roledescription="каÑÑÑелÑ" aria-label="СлайдеÑ">' 100 + '<div class="stage">' 101 + '<div class="track">' + slidesHtml + '</div>' 102 + '<button class="nav prev" type="button" aria-label="ÐÑедÑдÑÑий Ñлайд">' 103 + '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 5l-7 7 7 7"/></svg>' 104 + '</button>' 105 + '<button class="nav next" type="button" aria-label="СледÑÑÑий Ñлайд">' 106 + '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5l7 7-7 7"/></svg>' 107 + '</button>' 108 + '<button class="sound" type="button" aria-label="ÐклÑÑиÑÑ Ð·Ð²Ñк" hidden>' 109 + '<svg class="ic-off" viewBox="0 0 24 24" aria-hidden="true">' 110 + '<path d="M4 9v6h4l5 4V5L8 9H4zm12.5 3l2.5-2.5-1-1L15.5 11 13 8.5l-1 1 2.5 2.5L12 14.5l1 1 2.5-2.5 2.5 2.5 1-1L16.5 12z"/>' 111 + '</svg>' 112 + '<svg class="ic-on" viewBox="0 0 24 24" aria-hidden="true">' 113 + '<path d="M4 9v6h4l5 4V5L8 9H4zm12-1.5v9a4.5 4.5 0 0 0 0-9z"/>' 114 + '</svg>' 115 + '</button>' 116 + '</div>' 117 + '<div class="thumbs" role="tablist" aria-label="ÐиниаÑÑÑÑ">' + thumbsHtml + '</div>' 118 + '</div>'; 119 120 this._track = root.querySelector('.track'); 121 this._slideEls = root.querySelectorAll('.slide'); 122 this._thumbBar = root.querySelector('.thumbs'); 123 this._thumbEls = root.querySelectorAll('.thumb'); 124 this._stage = root.querySelector('.stage'); 125 this._soundBtn = root.querySelector('.sound'); 126 } 127 128 // ----------------------------------------------------------------- // 129 _bind() { 130 var self = this, root = this.shadowRoot; 131 132 root.querySelector('.prev').addEventListener('click', function () { self.prev(true); }); 133 root.querySelector('.next').addEventListener('click', function () { self.next(true); }); 134 135 this._soundBtn.addEventListener('click', function (e) { 136 e.preventDefault(); 137 self._toggleSound(); 138 }); 139 140 Array.prototype.forEach.call(this._thumbEls, function (btn) { 141 btn.addEventListener('click', function () { 142 self.go(parseInt(btn.getAttribute('data-i'), 10), true); 143 }); 144 }); 145 146 // --- видео-ÑÐ»Ð°Ð¹Ð´Ñ --- 147 Array.prototype.forEach.call(this._slideEls, function (el, i) { 148 var v = el.querySelector('video'); 149 if (!v) return; 150 self._videos[i] = v; 151 152 v.muted = true; // ÑÑавим ÑвойÑÑвом: инаÑе авÑозапÑÑк блокиÑÑеÑÑÑ 153 v.playsInline = true; 154 v.setAttribute('playsinline', ''); 155 if (self._slides.length === 1) v.loop = true; 156 157 // клик по видео не должен «пÑоглаÑÑваÑÑÑÑ», еÑли Ñлайд â ÑÑÑлка: бÑаÑÐ·ÐµÑ Ñам оÑкÑÐ¾ÐµÑ <a> 158 v.addEventListener('ended', function () { 159 if (document.hidden || self._slides.length < 2) return; 160 self.next(false); 161 }); 162 v.addEventListener('error', function () { 163 // видео не загÑÑзилоÑÑ â не залипаем на Ñлайде 164 self._timeout(self._slides[i].interval || self._interval); 165 }); 166 167 if (!self._slides[i].thumb) self._grabThumb(i, v); 168 }); 169 170 // паÑза пÑи наведении (на веÑÑ ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÐµÐ½Ñ: Ñлайд, ÑÑÑелки, миниаÑÑÑÑ) 171 var rootEl = root.querySelector('.sc'); 172 rootEl.addEventListener('mouseenter', function () { self._stop(); }); 173 rootEl.addEventListener('mouseleave', function () { self._start(); }); 174 175 // клавиаÑÑÑа (когда ÑокÑÑ Ð½Ð° каÑÑÑели) 176 rootEl.addEventListener('keydown', function (e) { 177 if (e.key === 'ArrowLeft') { self.prev(true); e.preventDefault(); } 178 if (e.key === 'ArrowRight') { self.next(true); e.preventDefault(); } 179 }); 180 181 // Ñвайп на ÑаÑ-ÑÑÑÑойÑÑÐ²Ð°Ñ 182 var x0 = null; 183 this._stage.addEventListener('touchstart', function (e) { 184 x0 = e.touches[0].clientX; self._stop(); 185 }, { passive: true });
186 this._stage.addEventListener('touchend', function (e) { 187 if (x0 === null) return; 188 var dx = e.changedTouches[0].clientX - x0; 189 if (Math.abs(dx) > 40) { dx < 0 ? self.next(true) : self.prev(true); } 190 x0 = null; 191 self._start(); 192 }, { passive: true }); 193 194 // паÑза, когда вкладка не акÑивна 195 document.addEventListener('visibilitychange', function () { 196 document.hidden ? self._stop() : self._start(); 197 }); 198 } 199 200 // ----------------- пÑблиÑÐ½Ð°Ñ Ð½Ð°Ð²Ð¸Ð³Ð°ÑÐ¸Ñ ----------------- // 201 next(user) { this.go((this._index + 1) % this._slides.length, user); } 202 prev(user) { this.go((this._index - 1 + this._slides.length) % this._slides.length, user); } 203 204 go(i, user) { 205 if (i === this._index || isNaN(i)) return; 206 207 var prev = this._videos[this._index]; 208 if (prev) { prev.pause(); try { prev.currentTime = 0; } catch (e) {} } 209 210 this._slideEls[this._index].classList.remove('is-active'); 211 this._slideEls[this._index].setAttribute('aria-hidden', 'true'); 212 this._thumbEls[this._index].classList.remove('is-active'); 213 214 this._index = i; 215 216 this._slideEls[i].classList.add('is-active'); 217 this._slideEls[i].setAttribute('aria-hidden', 'false'); 218 this._thumbEls[i].classList.add('is-active'); 219 this._scrollThumbIntoView(i); 220 this._updateSound(); 221 222 this._start(); // ÑаймеÑ/видео вÑегда пеÑезапÑÑкаÑÑÑÑ Ð¿Ð¾Ð´ новÑй Ñлайд 223 } 224 225 _scrollThumbIntoView(i) { 226 var t = this._thumbEls[i], bar = this._thumbBar; 227 if (!t) return; 228 var bw = bar.getBoundingClientRect(), tr = t.getBoundingClientRect(); 229 bar.scrollTo({ 230 left: bar.scrollLeft + (tr.left - bw.left) - (bw.width / 2) + (tr.width / 2), 231 behavior: 'smooth' 232 }); 233 } 234 235 // ----------------- ÑÐ°Ð¹Ð¼ÐµÑ Ð¸ воÑпÑоизведение ----------------- // 236 _start() { 237 this._stop(); 238 if (this._reduceMotion) return; 239 240 var self = this, 241 s = this._slides[this._index], 242 v = this._videos[this._index]; 243 244 if (v) { 245 v.muted = !this._soundOn; 246 var p = v.play(); 247 if (p && p.catch) { 248 p.catch(function () { // авÑозапÑÑк заблокиÑован â кÑÑÑим по ÑаймеÑÑ 249 self._timeout(s.interval || self._interval); 250 }); 251 } 252 // еÑли Ñ Ð²Ð¸Ð´ÐµÐ¾-Ñлайда Ñвно задан data-interval â он ÑабоÑÐ°ÐµÑ ÐºÐ°Ðº огÑаниÑиÑÐµÐ»Ñ 253 if (s.interval) this._timeout(s.interval); 254 return; 255 } 256 257 this._timeout(s.interval != null ? s.interval : this._interval); 258 } 259 260 _timeout(ms) { 261 if (!(ms > 0)) return; 262 var self = this; 263 if (this._timer) clearTimeout(this._timer); 264 this._timer = setTimeout(function () { self.next(false); }, ms); 265 } 266 267 _stop() { 268 if (this._timer) { clearTimeout(this._timer); this._timer = null; } 269 var v = this._videos[this._index]; 270 if (v) v.pause(); 271 } 272 273 // ----------------- звÑк ----------------- // 274 _toggleSound() { 275 this._soundOn = !this._soundOn; 276 var v = this._videos[this._index]; 277 if (v) { 278 v.muted = !this._soundOn; 279 if (this._soundOn) { v.volume = 1; var p = v.play(); if (p && p.catch) p.catch(function () {}); } 280 } 281 this._updateSound(); 282 } 283 284 _updateSound() { 285 var isVideo = !!this._videos[this._index]; 286 this._soundBtn.hidden = !isVideo; 287 this._soundBtn.classList.toggle('is-on', this._soundOn); 288 this._soundBtn.setAttribute('aria-label', this._soundOn ? 'ÐÑклÑÑиÑÑ Ð·Ð²Ñк' : 'ÐклÑÑиÑÑ Ð·Ð²Ñк'); 289 if (isVideo) this._videos[this._index].muted = !this._soundOn; 290 } 291 292 // ----------------- миниаÑÑÑа из пеÑвого кадÑа видео ----------------- // 293 _grabThumb(i, v) { 294 var self = this, done = false; 295 296 function shoot() { 297 if (done || !v.videoWidth || !v.videoHeight) return; 298 done = true; 299 try { 300 var c = document.createElement('canvas'); 301 c.width = 320; 302 c.height = Math.round(320 * v.videoHeight / v.videoWidth); 303 c.getContext('2d').drawImage(v, 0, 0, c.width, c.height); 304 var img = self._thumbEls[i] && self._thumbEls[i].querySelector('img'); 305 if (img) img.src = c.toDataURL('image/jpeg', 0.75); 306 } catch (e) { /* ÐºÐ°Ð½Ð²Ð°Ñ Â«Ð¸ÑпоÑÑен» кÑоÑÑ-доменнÑм видео â оÑÑавлÑем заглÑÑÐºÑ */ } 307 } 308 309 v.addEventListener('loadedmetadata', function () { 310 try { if (!v.currentTime) v.currentTime = 0.1; } catch (e) {} 311 }); 312 v.addEventListener('seeked', shoot); 313 v.addEventListener('loadeddata', shoot); 314 } 315 316 // ----------------------------------------------------------------- // 317 _css(perView) { 318 // gap = 8px; видимÑÑ Ð¼Ð¸Ð½Ð¸Ð°ÑÑÑ = perView 319 return '<style>' 320 + ':host{all:initial;display:block;contain:content;}' /* оÑÑезаем наÑледование извне */ 321 + '*,*::before,*::after{box-sizing:border-box;}' 322 + '.sc{font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;'
323 + 'color:#1a1a1a;line-height:1.4;letter-spacing:normal;outline:none;}' 324 + '.sc:focus-visible{outline:3px solid #2563eb;outline-offset:4px;border-radius:18px;}' 325 326 /* --- ÑÑена --- */ 327 + '.stage{position:relative;border-radius:10px;overflow:hidden;' 328 + 'border:2px solid #ced4da;' 329 + 'background:#e9ecef;aspect-ratio:16/9;}' 330 + '.track{position:absolute;inset:0;}' 331 + '.slide{position:absolute;inset:0;display:block;opacity:0;visibility:hidden;' 332 + 'transition:opacity .55s ease;text-decoration:none;}' 333 + '.slide.is-active{opacity:1;visibility:visible;}' 334 + '.slide img,.slide video{display:block;width:100%;height:100%;object-fit:contain;' 335 + 'border:0;border-radius:0;filter:none;max-width:none;}' 336 + '.slide video{background:#000;}' 337 338 /* --- ÑÑÑелки --- */ 339 + '.nav{position:absolute;top:50%;transform:translateY(-50%);z-index:2;' 340 + 'width:44px;height:44px;padding:0;margin:0;border:0;border-radius:50%;cursor:pointer;' 341 + 'display:flex;align-items:center;justify-content:center;letter-spacing:normal;' 342 + 'background:rgba(255,255,255,.85);color:#1a1a1a;backdrop-filter:blur(4px);' 343 + 'box-shadow:0 2px 10px rgba(0,0,0,.25);transition:background .2s,transform .2s;}' 344 + '.nav:hover{background:#fff;transform:translateY(-50%) scale(1.08);}' 345 + '.nav:active{transform:translateY(-50%) scale(.96);}' 346 + '.nav.prev{left:12px;} .nav.next{right:12px;}' 347 + '.nav svg{width:22px;height:22px;fill:none;stroke:currentColor;stroke-width:2.5;' 348 + 'stroke-linecap:round;stroke-linejoin:round;}' 349 350 /* --- кнопка звÑка (ÑолÑко на видео-ÑÐ»Ð°Ð¹Ð´Ð°Ñ ) --- */ 351 + '.sound{position:absolute;right:12px;bottom:12px;z-index:3;' 352 + 'width:38px;height:38px;padding:0;margin:0;border:0;border-radius:50%;cursor:pointer;' 353 + 'display:flex;align-items:center;justify-content:center;' 354 + 'background:rgba(255,255,255,.85);color:#1a1a1a;backdrop-filter:blur(4px);' 355 + 'box-shadow:0 2px 10px rgba(0,0,0,.25);transition:background .2s;}' 356 + '.sound:hover{background:#fff;}' 357 + '.sound[hidden]{display:none;}' 358 + '.sound svg{width:20px;height:20px;fill:currentColor;}' 359 + '.sound .ic-on{display:none;}' 360 + '.sound.is-on .ic-on{display:block;} .sound.is-on .ic-off{display:none;}' 361 362 /* --- ленÑа миниаÑÑÑ --- */ 363 + '.thumbs{display:flex;flex-wrap:nowrap;gap:8px;margin-top:8px;' 364 + 'overflow-x:auto;padding-bottom:6px;scrollbar-width:thin;' 365 + '-webkit-overflow-scrolling:touch;}' 366 + '.thumbs::-webkit-scrollbar{height:6px;}' 367 + '.thumbs::-webkit-scrollbar-thumb{background:rgba(0,0,0,.25);border-radius:3px;}' 368 + '.thumb{position:relative;flex:0 0 calc((100% - ' + ((perView - 1) * 8) + 'px) / ' + perView + ');' 369 + 'padding:0;margin:0;border:2px solid transparent;border-radius:10px;cursor:pointer;' 370 + 'overflow:hidden;background:#e9ecef;opacity:.5;letter-spacing:normal;' 371 + 'transition:opacity .2s,border-color .2s,transform .2s;}' 372 + '.thumb:hover{opacity:.85;transform:translateY(-2px);}' 373 + '.thumb.is-active{opacity:1;border-color:#2563eb;}' 374 + '.thumb img{display:block;width:100%;aspect-ratio:16/9;object-fit:cover;' 375 + 'border:0;border-radius:0;filter:none;max-width:none;}' 376 + '.thumb.is-video img{background:#000;}' 377 + '.thumb .play{position:absolute;inset:0;display:flex;align-items:center;' 378 + 'justify-content:center;pointer-events:none;}' 379 + '.thumb .play svg{width:26px;height:26px;fill:#fff;' 380 + 'filter:drop-shadow(0 1px 3px rgba(0,0,0,.65));}' 381 382 + '@media (prefers-reduced-motion: reduce){' 383 + '.slide{transition:none;} .nav,.thumb,.sound{transition:none;}}' 384 + '@media (max-width:640px){.nav{display:none;}}' /* на мобиле â ÑолÑко Ñвайп */ 385 + '</style>'; 386 } 387 } 388 389 function esc(s) { 390 return String(s).replace(/&/g,'&').replace(/"/g,'"') 391 .replace(/</g,'<').replace(/>/g,'>'); 392 } 393 394 // пÑÑÑем иÑÑ Ð¾Ð´Ð½ÑÑ light-DOM ÑазмеÑкÑ, как ÑолÑко ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÐµÐ½Ñ Ð³Ð¾Ñов 395 var hideStyle = document.createElement('style'); 396 hideStyle.textContent = 'site-carousel > a,site-carousel > video{display:none!important;}'; 397 document.head.appendChild(hideStyle); 398 399 customElements.define('site-carousel', SiteCarousel); 400})();
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.