themes/ehc/static/js/partner-carousel.js
112 Zeilen · 3.5 KB · JavaScript
| 1 | /* ============================================================ |
| 2 | Partner-Carousel — Quilldrop Widget |
| 3 | ------------------------------------------------------------ |
| 4 | Erwartet im DOM: |
| 5 | <div id="partner-carousel"></div> |
| 6 | <div id="partner-carousel-dots"></div> (optional) |
| 7 | |
| 8 | Daten: /static/data/partners.json |
| 9 | Konfig per data-Attribut auf #partner-carousel: |
| 10 | data-visible="3" Anzahl gleichzeitig sichtbarer Logos |
| 11 | data-interval="3500" Auto-Rotation in ms |
| 12 | ============================================================ */ |
| 13 | (function() { |
| 14 | 'use strict'; |
| 15 | |
| 16 | const DATA_URL = '/static/data/partners.json'; |
| 17 | |
| 18 | function esc(s) { |
| 19 | return String(s).replace(/[&<>"]/g, c => |
| 20 | ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]) |
| 21 | ); |
| 22 | } |
| 23 | |
| 24 | function build(host, dotsBox, partners, opts) { |
| 25 | const VISIBLE = opts.visible; |
| 26 | const INTERVAL = opts.interval; |
| 27 | |
| 28 | host.classList.add('carousel'); |
| 29 | host.innerHTML = ''; |
| 30 | |
| 31 | const track = document.createElement('div'); |
| 32 | track.className = 'carousel-track'; |
| 33 | host.appendChild(track); |
| 34 | |
| 35 | // Slides + Klone für Endlos-Loop |
| 36 | const all = partners.concat(partners.slice(0, VISIBLE)); |
| 37 | all.forEach(p => { |
| 38 | const slide = document.createElement('div'); |
| 39 | slide.className = 'carousel-slide'; |
| 40 | const card = `<div class="partner-card" title="${esc(p.name)}"> |
| 41 | <img src="${esc(p.src)}" alt="${esc(p.name)}" loading="lazy" /> |
| 42 | </div>`; |
| 43 | slide.innerHTML = p.url |
| 44 | ? `<a href="${esc(p.url)}" target="_blank" rel="noopener" class="partner-link" aria-label="${esc(p.name)}">${card}</a>` |
| 45 | : card; |
| 46 | track.appendChild(slide); |
| 47 | }); |
| 48 | |
| 49 | // Dots |
| 50 | if (dotsBox) { |
| 51 | dotsBox.innerHTML = ''; |
| 52 | partners.forEach((_, i) => { |
| 53 | const b = document.createElement('button'); |
| 54 | b.className = 'carousel-dot' + (i === 0 ? ' active' : ''); |
| 55 | b.setAttribute('aria-label', `Partner ${i + 1}`); |
| 56 | b.addEventListener('click', () => goTo(i)); |
| 57 | dotsBox.appendChild(b); |
| 58 | }); |
| 59 | } |
| 60 | |
| 61 | let index = 0; |
| 62 | function update() { |
| 63 | track.style.transition = 'transform 0.7s cubic-bezier(0.4, 0, 0.2, 1)'; |
| 64 | track.style.transform = `translateX(-${(100 / VISIBLE) * index}%)`; |
| 65 | if (dotsBox) { |
| 66 | const active = index % partners.length; |
| 67 | [...dotsBox.children].forEach((d, i) => d.classList.toggle('active', i === active)); |
| 68 | } |
| 69 | } |
| 70 | function goTo(i) { index = i; update(); } |
| 71 | function next() { |
| 72 | index++; |
| 73 | update(); |
| 74 | if (index >= partners.length) { |
| 75 | setTimeout(() => { |
| 76 | track.style.transition = 'none'; |
| 77 | index = 0; |
| 78 | track.style.transform = 'translateX(0)'; |
| 79 | if (dotsBox) [...dotsBox.children].forEach((d, i) => d.classList.toggle('active', i === 0)); |
| 80 | }, 720); |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | update(); |
| 85 | setInterval(next, INTERVAL); |
| 86 | } |
| 87 | |
| 88 | function init() { |
| 89 | const host = document.getElementById('partner-carousel'); |
| 90 | if (!host) return; |
| 91 | const dotsBox = document.getElementById('partner-carousel-dots'); |
| 92 | |
| 93 | const opts = { |
| 94 | visible: parseInt(host.dataset.visible, 10) || 3, |
| 95 | interval: parseInt(host.dataset.interval, 10) || 3500 |
| 96 | }; |
| 97 | |
| 98 | fetch(DATA_URL) |
| 99 | .then(r => r.json()) |
| 100 | .then(partners => { |
| 101 | if (!Array.isArray(partners) || partners.length === 0) return; |
| 102 | build(host, dotsBox, partners, opts); |
| 103 | }) |
| 104 | .catch(err => console.error('partner-carousel:', err)); |
| 105 | } |
| 106 | |
| 107 | if (document.readyState === 'loading') { |
| 108 | document.addEventListener('DOMContentLoaded', init); |
| 109 | } else { |
| 110 | init(); |
| 111 | } |
| 112 | })(); |