mirror of
https://github.com/Dvorinka/bizoni.git
synced 2026-06-03 18:22:57 +00:00
Add slug support and new admin features
This commit is contained in:
+471
-471
@@ -1,471 +1,471 @@
|
||||
(function(){
|
||||
const DATA_URL_JSON = '/data/club.json';
|
||||
const TZ = 'Europe/Prague';
|
||||
|
||||
let state = {
|
||||
data: null,
|
||||
compIndex: 0,
|
||||
matchIndex: 0,
|
||||
intervalId: null,
|
||||
upcomingTimerId: null,
|
||||
};
|
||||
|
||||
function parseCZDate(s){
|
||||
try{
|
||||
// format: 02.01.2006 15:04
|
||||
const [d, t] = s.split(' ');
|
||||
const [day, month, year] = d.split('.').map(Number);
|
||||
const [hour, minute] = t.split(':').map(Number);
|
||||
// Interpret as local time (Europe/Prague). Using local constructor avoids UTC offset skew.
|
||||
const dt = new Date(year, month-1, day, hour, minute);
|
||||
return dt;
|
||||
}catch(e){ return null; }
|
||||
}
|
||||
|
||||
// using original logo URLs; no cleaning/proxy
|
||||
|
||||
function ensureFacrStyles(){
|
||||
if(document.getElementById('facr-styles')) return;
|
||||
const css = `
|
||||
/* logo background unchanged */
|
||||
.facr-nav{ background:#ffffff22; border:1px solid #ffffff55; color:#fff; padding:6px 10px; border-radius:6px; cursor:pointer; backdrop-filter: blur(2px); }
|
||||
.facr-nav:hover{ background:#ffffff40; }
|
||||
.facr-nav:disabled{ opacity:.5; cursor:default; }
|
||||
.facr-tab{ padding:6px 10px; margin:4px; border-radius:16px; border:1px solid #c42221; color:#c42221; background:#ffffff; font-weight:600; }
|
||||
.facr-tab.active{ background:#c42221; color:#ffffff; }
|
||||
.facr-tab:hover{ background:#c42221cc; color:#ffffff; }
|
||||
.facr-inline-status{ margin-left:8px; font-weight:700; font-size:14px; white-space:nowrap; color:inherit; display:inline-block; vertical-align:middle; }
|
||||
/* Default (desktop): show middle only */
|
||||
#facr-countdown{ display:none !important; }
|
||||
.facr-inline-status{ display:none !important; }
|
||||
.facr-mob-center-score{ display:none; font-weight:700; font-size:24px; line-height:1; }
|
||||
/* Mobile: keep only the bottom countdown by default */
|
||||
@media (max-width: 767px){
|
||||
#facr-mid{ display:none !important; }
|
||||
#facr-countdown{ display:block !important; }
|
||||
.facr-inline-status{ display:none !important; }
|
||||
.facr-mob-center-score{ display:inline-block !important; }
|
||||
/* If finished, hide the bottom countdown */
|
||||
.facr-finished #facr-countdown{ display:none !important; }
|
||||
}
|
||||
@media (max-width: 480px){
|
||||
#facr-mid{ font-size:28px !important; min-width:100px; }
|
||||
.facr-tab{ padding:4px 8px; font-size:14px; }
|
||||
.facr-inline-status{ font-size:12px; margin-left:6px; }
|
||||
.facr-nav{ padding:4px 8px; }
|
||||
}
|
||||
`;
|
||||
const style = document.createElement('style');
|
||||
style.id = 'facr-styles';
|
||||
style.type = 'text/css';
|
||||
style.appendChild(document.createTextNode(css));
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
function fmtCountdown(ms){
|
||||
if(ms <= 0) return '0m';
|
||||
const totalMin = Math.floor(ms/60000);
|
||||
const d = Math.floor(totalMin/(60*24));
|
||||
const h = Math.floor((totalMin - d*60*24)/60);
|
||||
const m = totalMin % 60;
|
||||
const parts = [];
|
||||
if(d) parts.push(`${d}d`);
|
||||
if(h || d) parts.push(`${h}h`);
|
||||
parts.push(`${m}m`);
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
function fmtCountdownLong(ms){
|
||||
if(ms < 0) ms = 0;
|
||||
const totalSec = Math.floor(ms/1000);
|
||||
const d = Math.floor(totalSec / (24*3600));
|
||||
const h = Math.floor((totalSec % (24*3600)) / 3600);
|
||||
const m = Math.floor((totalSec % 3600) / 60);
|
||||
const s = totalSec % 60;
|
||||
const dd = d > 0 ? `${d}d ` : '';
|
||||
const hh = String(h).padStart(2,'0');
|
||||
const mm = String(m).padStart(2,'0');
|
||||
const ss = String(s).padStart(2,'0');
|
||||
return `${dd}${hh}:${mm}:${ss}`.trim();
|
||||
}
|
||||
|
||||
function todayCZ(){
|
||||
const now = new Date();
|
||||
const dd = String(now.getDate()).padStart(2,'0');
|
||||
const mm = String(now.getMonth()+1).padStart(2,'0');
|
||||
const yyyy = now.getFullYear();
|
||||
return `${dd}.${mm}.${yyyy}`;
|
||||
}
|
||||
|
||||
async function fetchData(){
|
||||
const res = await fetch(DATA_URL_JSON, { cache: 'no-cache' });
|
||||
if(!res.ok) throw new Error('Failed to fetch data');
|
||||
const data = await res.json();
|
||||
state.data = data;
|
||||
return data;
|
||||
}
|
||||
|
||||
function escapeHTML(s){
|
||||
if(s == null) return '';
|
||||
return String(s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function truncate(s, max){
|
||||
const str = s == null ? '' : String(s);
|
||||
if(max <= 0) return '';
|
||||
return str.length > max ? str.slice(0, max - 1) + '…' : str;
|
||||
}
|
||||
|
||||
function isWithinMatchWindow(){
|
||||
if(!state.data) return false;
|
||||
const now = new Date();
|
||||
for(const comp of state.data.club_detail.competitions || []){
|
||||
for(const m of comp.matches || []){
|
||||
const dt = parseCZDate(m.date_time);
|
||||
if(!dt) continue;
|
||||
const diffMs = Math.abs(now - dt);
|
||||
if(diffMs <= 2*60*60*1000) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function upcomingMatchesAll(){
|
||||
const list = [];
|
||||
if(!state.data) return list;
|
||||
const now = new Date();
|
||||
const windowStart = new Date(now.getTime() - 3*24*60*60*1000);
|
||||
for(const comp of state.data.club_detail.competitions || []){
|
||||
const candidates = (comp.matches || [])
|
||||
.map(m=>({ comp, match: m, dt: parseCZDate(m.date_time) }))
|
||||
.filter(x=> x.dt && x.dt >= windowStart)
|
||||
.sort((a,b)=> a.dt - b.dt);
|
||||
if(candidates.length === 0) continue;
|
||||
// Prefer the latest recently finished match within the 3-day window;
|
||||
// if none, then choose the next upcoming; if none, fall back to the latest overall in window
|
||||
let pick = null;
|
||||
// find latest finished (dt < now) within window
|
||||
for(let i = candidates.length - 1; i >= 0; i--){
|
||||
if(candidates[i].dt < now){ pick = candidates[i]; break; }
|
||||
}
|
||||
if(!pick){
|
||||
pick = candidates.find(x=> x.dt >= now) || candidates[candidates.length - 1];
|
||||
}
|
||||
list.push(pick);
|
||||
}
|
||||
// sort resulting per-competition picks by time ascending for navigation
|
||||
list.sort((a,b)=> a.dt - b.dt);
|
||||
return list;
|
||||
}
|
||||
|
||||
function renderUpcoming(){
|
||||
const root = document.getElementById('facr-upcoming');
|
||||
if(!root) return;
|
||||
ensureFacrStyles();
|
||||
|
||||
// clear any previous per-second timer
|
||||
if(state.upcomingTimerId){
|
||||
clearInterval(state.upcomingTimerId);
|
||||
state.upcomingTimerId = null;
|
||||
}
|
||||
|
||||
const items = upcomingMatchesAll();
|
||||
if(items.length === 0){
|
||||
root.innerHTML = '<div class="lte-football-upcoming"><span class="lte-header lte-header-upcoming">Žádné nadcházející zápasy</span></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Selection policy:
|
||||
// 1) If there is any finished match within the last 3 days across competitions,
|
||||
// show the latest such finished match (keep result visible for 3 days)
|
||||
// 2) Otherwise, show the first future match
|
||||
// 3) If none, show the latest overall (shouldn't happen as items filtered by 3d window per-comp)
|
||||
const now = new Date();
|
||||
const threeDms = 3*24*60*60*1000;
|
||||
let latestRecentIdx = -1;
|
||||
let latestRecentTime = -Infinity;
|
||||
items.forEach((it, i) => {
|
||||
const dtms = it.dt.getTime();
|
||||
if(dtms <= now.getTime() && now.getTime() - dtms <= threeDms){
|
||||
if(dtms > latestRecentTime){ latestRecentTime = dtms; latestRecentIdx = i; }
|
||||
}
|
||||
});
|
||||
let preferredIdx = latestRecentIdx;
|
||||
if(preferredIdx === -1){
|
||||
preferredIdx = items.findIndex(it => it.dt >= now);
|
||||
if(preferredIdx === -1) preferredIdx = items.length - 1;
|
||||
}
|
||||
const idx = Math.min(state.matchIndex || preferredIdx, items.length-1);
|
||||
const { comp, match:m } = items[idx];
|
||||
const compName = truncate(escapeHTML(comp.name || comp.code || 'Soutěž'), 60);
|
||||
|
||||
const homeLogo = m.home_logo_url || 'img/logo.png';
|
||||
const awayLogo = m.away_logo_url || 'img/logo.png';
|
||||
const facrLink = m.facr_link || comp.matches_link || state.data.club_detail.url || '#';
|
||||
const UP_MAX = 24;
|
||||
const homeName = truncate(escapeHTML(m.home), UP_MAX);
|
||||
const awayName = truncate(escapeHTML(m.away), UP_MAX);
|
||||
const dateVenue = truncate(escapeHTML(m.date_time + (m.venue?`, ${m.venue}`:'')), 40);
|
||||
|
||||
// Determine if match is today (CZ date) and precompute mid display text
|
||||
const matchDayCZ = (m.date_time || '').split(' ')[0];
|
||||
const isToday = matchDayCZ === todayCZ();
|
||||
const startDt = m.date_time ? parseCZDate(m.date_time) : null;
|
||||
const diffMsPre = startDt ? (startDt.getTime() - new Date().getTime()) : 0;
|
||||
const midText = (diffMsPre > 0) ? `Za ${fmtCountdownLong(diffMsPre)}` : (m.score || '-');
|
||||
|
||||
// Determine status flags and parse score parts if any
|
||||
const now2_forTpl = new Date();
|
||||
const startMs_forTpl = m.date_time ? parseCZDate(m.date_time).getTime() : 0;
|
||||
const diff_forTpl = startMs_forTpl - now2_forTpl.getTime();
|
||||
const twoH_forTpl = 2*60*60*1000;
|
||||
const threeD_forTpl = 3*24*60*60*1000;
|
||||
const isFuture = diff_forTpl > 0;
|
||||
const isLive = Math.abs(diff_forTpl) <= twoH_forTpl;
|
||||
const isRecentFinished = (!isFuture && !isLive && -diff_forTpl < threeD_forTpl);
|
||||
const scoreStr = m.score || '';
|
||||
const s1 = scoreStr && scoreStr.includes(':') ? escapeHTML(scoreStr.split(':')[0]) : '';
|
||||
const s2 = scoreStr && scoreStr.includes(':') ? escapeHTML(scoreStr.split(':')[1]) : '';
|
||||
// Date/time formatting for display lines
|
||||
const dtForDisp = startDt || (m.date_time ? parseCZDate(m.date_time) : null);
|
||||
const dd = dtForDisp ? String(dtForDisp.getDate()).padStart(1,'') : '';
|
||||
const mm = dtForDisp ? String(dtForDisp.getMonth()+1).padStart(1,'') : '';
|
||||
const yyyy = dtForDisp ? dtForDisp.getFullYear() : '';
|
||||
const HH = dtForDisp ? String(dtForDisp.getHours()).padStart(2,'0') : '';
|
||||
const MM = dtForDisp ? String(dtForDisp.getMinutes()).padStart(2,'0') : '';
|
||||
const dateOnly = dtForDisp ? `${dd}. ${mm}. ${yyyy}` : '';
|
||||
const timeToken = (m.date_time || '').split(' ')[1] || '';
|
||||
const timeOnly = dtForDisp ? `${HH}:${MM}` : '';
|
||||
const timeDisplay = dtForDisp ? ((timeToken === '' || timeToken === '00:00') ? 'Bude upřesněno' : timeOnly) : '';
|
||||
const venue = m.venue || '';
|
||||
const wrapperExtraClass = (isRecentFinished && s1 && s2) ? ' facr-finished' : '';
|
||||
|
||||
const headerLabel = isLive ? 'Aktuální zápas' : (isFuture ? 'Nadcházející zápas' : (isRecentFinished ? 'Poslední zápas' : `Zápasy (${idx+1}/${items.length})`));
|
||||
|
||||
root.innerHTML = `
|
||||
<div class="lte-football-upcoming${wrapperExtraClass}">
|
||||
<div class="facr-comp-title lte-football-date" style="text-align:center; margin-bottom:6px;">${compName}</div>
|
||||
<div class="facr-upcoming-header">
|
||||
<button id="facr-prev" class="facr-nav">◀</button>
|
||||
<span class="lte-header lte-header-upcoming">${headerLabel}</span>
|
||||
<button id="facr-next" class="facr-nav">▶</button>
|
||||
</div>
|
||||
<div class="lte-teams">
|
||||
<span class="lte-team-name lte-team-1 lte-header" title="${escapeHTML(m.home)}">
|
||||
<span class="lte-team-logo"><img decoding="async" src="${homeLogo}" alt="${escapeHTML(m.home)}"></span>${homeName}
|
||||
<span id="facr-inline-status" class="facr-inline-status" aria-live="polite"></span>
|
||||
${isRecentFinished && s1 ? `<span class="lte-team-count-mob">${s1}</span>` : ''}
|
||||
</span>
|
||||
<span class="lte-team-count">
|
||||
<span id="facr-mid" style="font-size:32px; line-height:1; font-weight:700; display:inline-block; min-width:120px; text-align:center;">${midText}</span>
|
||||
${isRecentFinished && s1 && s2 ? `<span class="facr-mob-center-score">${s1}<span>:</span>${s2}</span>` : ''}
|
||||
</span>
|
||||
<span class="lte-team-name lte-team-2 lte-header" title="${escapeHTML(m.away)}">
|
||||
${isRecentFinished && s2 ? `<span class=\"lte-team-count-mob\">${s2}</span>` : ''}${awayName}<span class="lte-team-logo"><img decoding="async" src="${awayLogo}" alt="${escapeHTML(m.away)}"></span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="lte-football-date" style="text-align:center;" title="${escapeHTML(m.date_time + (m.venue?`, ${m.venue}`:''))}">${escapeHTML(dateOnly + (venue?`, ${venue}`:''))}</span>
|
||||
${timeDisplay ? `<span class="lte-football-time" style="display:block; text-align:center;">${escapeHTML(timeDisplay)}</span>` : ''}
|
||||
<span id="facr-countdown" class="lte-football-date" style="display:block; text-align:center;"></span>
|
||||
<br>
|
||||
<a class="lte-football-date" target="_blank" href="${facrLink}" style="text-align:center; background-color:#c42221; color:#ffffff; opacity:1;">Detail na FACR</a>
|
||||
<span style="display:block; margin-top:6px;"></span>
|
||||
<a class="lte-football-date" href="#tabulka" style="text-align:center; background-color:#ffffff43; color:#ffffff; opacity:1; width:49%; display:inline-block;">Tabulka bodů</a>
|
||||
<a class="lte-football-date" href="/zapasy/vsechny.html" style="text-align:center; background-color:#ffffff43; color:#ffffff; opacity:1; width:49%; display:inline-block;">Všechny zápasy</a>
|
||||
</div>`;
|
||||
|
||||
const prev = document.getElementById('facr-prev');
|
||||
const next = document.getElementById('facr-next');
|
||||
if(prev) prev.onclick = ()=>{ state.matchIndex = (idx - 1 + items.length) % items.length; renderUpcoming(); };
|
||||
if(next) next.onclick = ()=>{ state.matchIndex = (idx + 1) % items.length; renderUpcoming(); };
|
||||
|
||||
// setup countdown / status text
|
||||
const cd = document.getElementById('facr-countdown');
|
||||
const inlineStatus = document.getElementById('facr-inline-status');
|
||||
if(cd || inlineStatus){
|
||||
const now2 = new Date();
|
||||
const startMs = m.date_time ? parseCZDate(m.date_time).getTime() : 0;
|
||||
const diff = startMs - now2.getTime();
|
||||
const twoH = 2*60*60*1000;
|
||||
const threeD = 3*24*60*60*1000;
|
||||
let text = '';
|
||||
if(diff > 0){
|
||||
text = `Začátek za ${fmtCountdown(diff)}`;
|
||||
}else if(Math.abs(diff) <= twoH){
|
||||
text = 'Právě probíhá';
|
||||
}else if(-diff < threeD){
|
||||
text = (m.score ? `Výsledek: ${m.score}` : 'Ukončeno');
|
||||
}else{
|
||||
text = '';
|
||||
}
|
||||
if(cd) cd.textContent = text;
|
||||
if(inlineStatus) inlineStatus.textContent = text;
|
||||
}
|
||||
|
||||
// Live countdown in the middle area when future and not today
|
||||
const midEl = document.getElementById('facr-mid');
|
||||
if(midEl){
|
||||
const startTime = m.date_time ? parseCZDate(m.date_time).getTime() : 0;
|
||||
function tick(){
|
||||
const now = Date.now();
|
||||
const diff = startTime - now;
|
||||
if(diff > 0){
|
||||
const longTxt = `Za ${fmtCountdownLong(diff)}`;
|
||||
midEl.textContent = longTxt;
|
||||
const inlineEl = document.getElementById('facr-inline-status');
|
||||
if(inlineEl) inlineEl.textContent = `Začátek za ${fmtCountdown(diff)}`;
|
||||
const cdEl = document.getElementById('facr-countdown');
|
||||
if(cdEl) cdEl.textContent = `Začátek za ${fmtCountdown(diff)}`;
|
||||
}else{
|
||||
// switch to score at/after kickoff
|
||||
const scoreTxt = m.score || '-';
|
||||
midEl.textContent = scoreTxt;
|
||||
const inlineEl = document.getElementById('facr-inline-status');
|
||||
if(inlineEl) inlineEl.textContent = (m.score ? `Výsledek: ${m.score}` : 'Ukončeno');
|
||||
const cdEl = document.getElementById('facr-countdown');
|
||||
if(cdEl) cdEl.textContent = (m.score ? `Výsledek: ${m.score}` : 'Ukončeno');
|
||||
if(state.upcomingTimerId){ clearInterval(state.upcomingTimerId); state.upcomingTimerId = null; }
|
||||
}
|
||||
}
|
||||
// Run live countdown for any future match (including today)
|
||||
if(startTime > Date.now()){
|
||||
tick();
|
||||
state.upcomingTimerId = setInterval(tick, 1000);
|
||||
} else {
|
||||
// Ensure inline status reflects finished state on initial render
|
||||
const inlineEl = document.getElementById('facr-inline-status');
|
||||
if(inlineEl) inlineEl.textContent = (m.score ? `Výsledek: ${m.score}` : 'Ukončeno');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderCompetitionTabs(){
|
||||
const tabs = document.getElementById('facr-comp-tabs');
|
||||
if(!tabs || !state.data) return;
|
||||
ensureFacrStyles();
|
||||
const comps = state.data.club_table.competitions || [];
|
||||
tabs.innerHTML = comps.map((c,i)=>
|
||||
`<button class="facr-tab ${i===state.compIndex?'active':''}" data-idx="${i}">${c.name || c.code || 'Soutěž'}</button>`
|
||||
).join('');
|
||||
tabs.querySelectorAll('button').forEach(btn=>{
|
||||
btn.addEventListener('click', ()=>{
|
||||
state.compIndex = Number(btn.dataset.idx)||0;
|
||||
renderCompetitionTabs();
|
||||
renderTable();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderTable(){
|
||||
const tbody = document.getElementById('facr-table-body');
|
||||
const badge = document.getElementById('facr-comp-badge');
|
||||
if(!tbody || !state.data) return;
|
||||
const comps = state.data.club_table.competitions || [];
|
||||
if(comps.length === 0){ tbody.innerHTML = ''; return; }
|
||||
const comp = comps[Math.min(state.compIndex, comps.length-1)];
|
||||
if(badge){ badge.textContent = comp.name || comp.code || 'Soutěž'; }
|
||||
const rows = comp.table && comp.table.overall ? comp.table.overall : [];
|
||||
tbody.innerHTML = rows.map(r=>`
|
||||
<tr>
|
||||
<td class="lte-row"><span>${r.rank}</span></td>
|
||||
<td class="lte-club-logo"><img decoding="async" src="${r.team_logo_url || 'img/logo.png'}"></td>
|
||||
<td class="lte-name">${r.team}</td>
|
||||
<td class="lte-rate">${r.played}</td>
|
||||
<td class="lte-rate">${r.wins}</td>
|
||||
<td class="lte-rate">${r.draws}</td>
|
||||
<td class="lte-rate">${r.losses}</td>
|
||||
<td class="lte-rate">${r.score}</td>
|
||||
<td class="lte-summary">${r.points}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function renderAllMatches(){
|
||||
const container = document.getElementById('facr-all-matches');
|
||||
if(!container || !state.data) return;
|
||||
const comps = state.data.club_detail.competitions || [];
|
||||
const sections = [];
|
||||
for(const comp of comps){
|
||||
const matches = (comp.matches || [])
|
||||
.map(m=>({ m, dt: parseCZDate(m.date_time) }))
|
||||
.filter(x=>!!x.dt)
|
||||
.sort((a,b)=> b.dt - a.dt);
|
||||
if(matches.length === 0) continue;
|
||||
const compName = truncate(escapeHTML(comp.name || comp.code || 'Soutěž'), 40);
|
||||
const itemsHtml = matches.map(({m})=>{
|
||||
const homeLogo = m.home_logo_url || '../img/logo.png';
|
||||
const awayLogo = m.away_logo_url || '../img/logo.png';
|
||||
const facrLink = m.report_url || comp.matches_link || state.data.club_detail.url || '#';
|
||||
const score = m.score || '-';
|
||||
const GRID_MAX = 22;
|
||||
const home = truncate(escapeHTML(m.home), GRID_MAX);
|
||||
const away = truncate(escapeHTML(m.away), GRID_MAX);
|
||||
const dateVenue = truncate(escapeHTML(m.date_time + (m.venue?`, ${m.venue}`:'')), 36);
|
||||
const s1 = escapeHTML((score.split(':')[0]||'-'));
|
||||
const s2 = escapeHTML((score.split(':')[1]||'-'));
|
||||
return `
|
||||
<a href="${facrLink}" target="_blank" class="lte-item swiper-slide">
|
||||
<div class="lte-teams lte-match-time-public">
|
||||
<span class="lte-team-name lte-team-1 lte-header" title="${escapeHTML(m.home)}">
|
||||
<span class="lte-team-logo"><img src="${homeLogo}" alt="${escapeHTML(m.home)}"></span>${home}</span>
|
||||
<span class="lte-score-mob lte-score-1">${s1}</span>
|
||||
<span class="lte-team-count">
|
||||
<span class="lte-c lte-score-1">${s1}</span>
|
||||
<span class="lte-d">:</span>
|
||||
<span class="lte-c lte-score-4">${s2}</span>
|
||||
</span>
|
||||
<span class="lte-team-name lte-team-2 lte-header" title="${escapeHTML(m.away)}">${away}
|
||||
<span class="lte-team-logo"><img src="${awayLogo}" alt="${escapeHTML(m.away)}"></span>
|
||||
</span>
|
||||
<span class="lte-score-mob lte-score-4">${s2}</span>
|
||||
</div>
|
||||
<div class="lte-footer">
|
||||
<span class="lte-football-date" title="${escapeHTML(m.date_time + (m.venue?`, ${m.venue}`:''))}">${dateVenue}</span>
|
||||
</div>
|
||||
</a>`;
|
||||
}).join('');
|
||||
sections.push(`
|
||||
<div class="lte-section">
|
||||
<h3 class="lte-header" style="margin: 20px 0 10px;">${compName}</h3>
|
||||
<div class="lte-football-matches inner-page">${itemsHtml}</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
container.innerHTML = sections.join('');
|
||||
}
|
||||
|
||||
function schedule(){
|
||||
if(state.intervalId) clearInterval(state.intervalId);
|
||||
const intervalMs = isWithinMatchWindow() ? 2*60*1000 : 30*60*1000;
|
||||
state.intervalId = setInterval(async ()=>{
|
||||
try{
|
||||
await fetchData();
|
||||
renderUpcoming();
|
||||
renderCompetitionTabs();
|
||||
renderTable();
|
||||
renderAllMatches();
|
||||
schedule(); // reevaluate interval if window changed
|
||||
}catch(e){ console.warn('refresh failed', e); }
|
||||
}, intervalMs);
|
||||
}
|
||||
|
||||
async function init(){
|
||||
try{
|
||||
await fetchData();
|
||||
renderUpcoming();
|
||||
renderCompetitionTabs();
|
||||
renderTable();
|
||||
renderAllMatches();
|
||||
schedule();
|
||||
}catch(e){
|
||||
console.error('FACR init failed', e);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
})();
|
||||
(function(){
|
||||
const DATA_URL_JSON = '/data/club.json';
|
||||
const TZ = 'Europe/Prague';
|
||||
|
||||
let state = {
|
||||
data: null,
|
||||
compIndex: 0,
|
||||
matchIndex: 0,
|
||||
intervalId: null,
|
||||
upcomingTimerId: null,
|
||||
};
|
||||
|
||||
function parseCZDate(s){
|
||||
try{
|
||||
// format: 02.01.2006 15:04
|
||||
const [d, t] = s.split(' ');
|
||||
const [day, month, year] = d.split('.').map(Number);
|
||||
const [hour, minute] = t.split(':').map(Number);
|
||||
// Interpret as local time (Europe/Prague). Using local constructor avoids UTC offset skew.
|
||||
const dt = new Date(year, month-1, day, hour, minute);
|
||||
return dt;
|
||||
}catch(e){ return null; }
|
||||
}
|
||||
|
||||
// using original logo URLs; no cleaning/proxy
|
||||
|
||||
function ensureFacrStyles(){
|
||||
if(document.getElementById('facr-styles')) return;
|
||||
const css = `
|
||||
/* logo background unchanged */
|
||||
.facr-nav{ background:#ffffff22; border:1px solid #ffffff55; color:#fff; padding:6px 10px; border-radius:6px; cursor:pointer; backdrop-filter: blur(2px); }
|
||||
.facr-nav:hover{ background:#ffffff40; }
|
||||
.facr-nav:disabled{ opacity:.5; cursor:default; }
|
||||
.facr-tab{ padding:6px 10px; margin:4px; border-radius:16px; border:1px solid #c42221; color:#c42221; background:#ffffff; font-weight:600; }
|
||||
.facr-tab.active{ background:#c42221; color:#ffffff; }
|
||||
.facr-tab:hover{ background:#c42221cc; color:#ffffff; }
|
||||
.facr-inline-status{ margin-left:8px; font-weight:700; font-size:14px; white-space:nowrap; color:inherit; display:inline-block; vertical-align:middle; }
|
||||
/* Default (desktop): show middle only */
|
||||
#facr-countdown{ display:none !important; }
|
||||
.facr-inline-status{ display:none !important; }
|
||||
.facr-mob-center-score{ display:none; font-weight:700; font-size:24px; line-height:1; }
|
||||
/* Mobile: keep only the bottom countdown by default */
|
||||
@media (max-width: 767px){
|
||||
#facr-mid{ display:none !important; }
|
||||
#facr-countdown{ display:block !important; }
|
||||
.facr-inline-status{ display:none !important; }
|
||||
.facr-mob-center-score{ display:inline-block !important; }
|
||||
/* If finished, hide the bottom countdown */
|
||||
.facr-finished #facr-countdown{ display:none !important; }
|
||||
}
|
||||
@media (max-width: 480px){
|
||||
#facr-mid{ font-size:28px !important; min-width:100px; }
|
||||
.facr-tab{ padding:4px 8px; font-size:14px; }
|
||||
.facr-inline-status{ font-size:12px; margin-left:6px; }
|
||||
.facr-nav{ padding:4px 8px; }
|
||||
}
|
||||
`;
|
||||
const style = document.createElement('style');
|
||||
style.id = 'facr-styles';
|
||||
style.type = 'text/css';
|
||||
style.appendChild(document.createTextNode(css));
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
function fmtCountdown(ms){
|
||||
if(ms <= 0) return '0m';
|
||||
const totalMin = Math.floor(ms/60000);
|
||||
const d = Math.floor(totalMin/(60*24));
|
||||
const h = Math.floor((totalMin - d*60*24)/60);
|
||||
const m = totalMin % 60;
|
||||
const parts = [];
|
||||
if(d) parts.push(`${d}d`);
|
||||
if(h || d) parts.push(`${h}h`);
|
||||
parts.push(`${m}m`);
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
function fmtCountdownLong(ms){
|
||||
if(ms < 0) ms = 0;
|
||||
const totalSec = Math.floor(ms/1000);
|
||||
const d = Math.floor(totalSec / (24*3600));
|
||||
const h = Math.floor((totalSec % (24*3600)) / 3600);
|
||||
const m = Math.floor((totalSec % 3600) / 60);
|
||||
const s = totalSec % 60;
|
||||
const dd = d > 0 ? `${d}d ` : '';
|
||||
const hh = String(h).padStart(2,'0');
|
||||
const mm = String(m).padStart(2,'0');
|
||||
const ss = String(s).padStart(2,'0');
|
||||
return `${dd}${hh}:${mm}:${ss}`.trim();
|
||||
}
|
||||
|
||||
function todayCZ(){
|
||||
const now = new Date();
|
||||
const dd = String(now.getDate()).padStart(2,'0');
|
||||
const mm = String(now.getMonth()+1).padStart(2,'0');
|
||||
const yyyy = now.getFullYear();
|
||||
return `${dd}.${mm}.${yyyy}`;
|
||||
}
|
||||
|
||||
async function fetchData(){
|
||||
const res = await fetch(DATA_URL_JSON, { cache: 'no-cache' });
|
||||
if(!res.ok) throw new Error('Failed to fetch data');
|
||||
const data = await res.json();
|
||||
state.data = data;
|
||||
return data;
|
||||
}
|
||||
|
||||
function escapeHTML(s){
|
||||
if(s == null) return '';
|
||||
return String(s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function truncate(s, max){
|
||||
const str = s == null ? '' : String(s);
|
||||
if(max <= 0) return '';
|
||||
return str.length > max ? str.slice(0, max - 1) + '…' : str;
|
||||
}
|
||||
|
||||
function isWithinMatchWindow(){
|
||||
if(!state.data) return false;
|
||||
const now = new Date();
|
||||
for(const comp of state.data.club_detail.competitions || []){
|
||||
for(const m of comp.matches || []){
|
||||
const dt = parseCZDate(m.date_time);
|
||||
if(!dt) continue;
|
||||
const diffMs = Math.abs(now - dt);
|
||||
if(diffMs <= 2*60*60*1000) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function upcomingMatchesAll(){
|
||||
const list = [];
|
||||
if(!state.data) return list;
|
||||
const now = new Date();
|
||||
const windowStart = new Date(now.getTime() - 3*24*60*60*1000);
|
||||
for(const comp of state.data.club_detail.competitions || []){
|
||||
const candidates = (comp.matches || [])
|
||||
.map(m=>({ comp, match: m, dt: parseCZDate(m.date_time) }))
|
||||
.filter(x=> x.dt && x.dt >= windowStart)
|
||||
.sort((a,b)=> a.dt - b.dt);
|
||||
if(candidates.length === 0) continue;
|
||||
// Prefer the latest recently finished match within the 3-day window;
|
||||
// if none, then choose the next upcoming; if none, fall back to the latest overall in window
|
||||
let pick = null;
|
||||
// find latest finished (dt < now) within window
|
||||
for(let i = candidates.length - 1; i >= 0; i--){
|
||||
if(candidates[i].dt < now){ pick = candidates[i]; break; }
|
||||
}
|
||||
if(!pick){
|
||||
pick = candidates.find(x=> x.dt >= now) || candidates[candidates.length - 1];
|
||||
}
|
||||
list.push(pick);
|
||||
}
|
||||
// sort resulting per-competition picks by time ascending for navigation
|
||||
list.sort((a,b)=> a.dt - b.dt);
|
||||
return list;
|
||||
}
|
||||
|
||||
function renderUpcoming(){
|
||||
const root = document.getElementById('facr-upcoming');
|
||||
if(!root) return;
|
||||
ensureFacrStyles();
|
||||
|
||||
// clear any previous per-second timer
|
||||
if(state.upcomingTimerId){
|
||||
clearInterval(state.upcomingTimerId);
|
||||
state.upcomingTimerId = null;
|
||||
}
|
||||
|
||||
const items = upcomingMatchesAll();
|
||||
if(items.length === 0){
|
||||
root.innerHTML = '<div class="lte-football-upcoming"><span class="lte-header lte-header-upcoming">Žádné nadcházející zápasy</span></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Selection policy:
|
||||
// 1) If there is any finished match within the last 3 days across competitions,
|
||||
// show the latest such finished match (keep result visible for 3 days)
|
||||
// 2) Otherwise, show the first future match
|
||||
// 3) If none, show the latest overall (shouldn't happen as items filtered by 3d window per-comp)
|
||||
const now = new Date();
|
||||
const threeDms = 3*24*60*60*1000;
|
||||
let latestRecentIdx = -1;
|
||||
let latestRecentTime = -Infinity;
|
||||
items.forEach((it, i) => {
|
||||
const dtms = it.dt.getTime();
|
||||
if(dtms <= now.getTime() && now.getTime() - dtms <= threeDms){
|
||||
if(dtms > latestRecentTime){ latestRecentTime = dtms; latestRecentIdx = i; }
|
||||
}
|
||||
});
|
||||
let preferredIdx = latestRecentIdx;
|
||||
if(preferredIdx === -1){
|
||||
preferredIdx = items.findIndex(it => it.dt >= now);
|
||||
if(preferredIdx === -1) preferredIdx = items.length - 1;
|
||||
}
|
||||
const idx = Math.min(state.matchIndex || preferredIdx, items.length-1);
|
||||
const { comp, match:m } = items[idx];
|
||||
const compName = truncate(escapeHTML(comp.name || comp.code || 'Soutěž'), 60);
|
||||
|
||||
const homeLogo = m.home_logo_url || 'img/logo.png';
|
||||
const awayLogo = m.away_logo_url || 'img/logo.png';
|
||||
const facrLink = m.facr_link || comp.matches_link || state.data.club_detail.url || '#';
|
||||
const UP_MAX = 24;
|
||||
const homeName = truncate(escapeHTML(m.home), UP_MAX);
|
||||
const awayName = truncate(escapeHTML(m.away), UP_MAX);
|
||||
const dateVenue = truncate(escapeHTML(m.date_time + (m.venue?`, ${m.venue}`:'')), 40);
|
||||
|
||||
// Determine if match is today (CZ date) and precompute mid display text
|
||||
const matchDayCZ = (m.date_time || '').split(' ')[0];
|
||||
const isToday = matchDayCZ === todayCZ();
|
||||
const startDt = m.date_time ? parseCZDate(m.date_time) : null;
|
||||
const diffMsPre = startDt ? (startDt.getTime() - new Date().getTime()) : 0;
|
||||
const midText = (diffMsPre > 0) ? `Za ${fmtCountdownLong(diffMsPre)}` : (m.score || '-');
|
||||
|
||||
// Determine status flags and parse score parts if any
|
||||
const now2_forTpl = new Date();
|
||||
const startMs_forTpl = m.date_time ? parseCZDate(m.date_time).getTime() : 0;
|
||||
const diff_forTpl = startMs_forTpl - now2_forTpl.getTime();
|
||||
const twoH_forTpl = 2*60*60*1000;
|
||||
const threeD_forTpl = 3*24*60*60*1000;
|
||||
const isFuture = diff_forTpl > 0;
|
||||
const isLive = Math.abs(diff_forTpl) <= twoH_forTpl;
|
||||
const isRecentFinished = (!isFuture && !isLive && -diff_forTpl < threeD_forTpl);
|
||||
const scoreStr = m.score || '';
|
||||
const s1 = scoreStr && scoreStr.includes(':') ? escapeHTML(scoreStr.split(':')[0]) : '';
|
||||
const s2 = scoreStr && scoreStr.includes(':') ? escapeHTML(scoreStr.split(':')[1]) : '';
|
||||
// Date/time formatting for display lines
|
||||
const dtForDisp = startDt || (m.date_time ? parseCZDate(m.date_time) : null);
|
||||
const dd = dtForDisp ? String(dtForDisp.getDate()).padStart(1,'') : '';
|
||||
const mm = dtForDisp ? String(dtForDisp.getMonth()+1).padStart(1,'') : '';
|
||||
const yyyy = dtForDisp ? dtForDisp.getFullYear() : '';
|
||||
const HH = dtForDisp ? String(dtForDisp.getHours()).padStart(2,'0') : '';
|
||||
const MM = dtForDisp ? String(dtForDisp.getMinutes()).padStart(2,'0') : '';
|
||||
const dateOnly = dtForDisp ? `${dd}. ${mm}. ${yyyy}` : '';
|
||||
const timeToken = (m.date_time || '').split(' ')[1] || '';
|
||||
const timeOnly = dtForDisp ? `${HH}:${MM}` : '';
|
||||
const timeDisplay = dtForDisp ? ((timeToken === '' || timeToken === '00:00') ? 'Bude upřesněno' : timeOnly) : '';
|
||||
const venue = m.venue || '';
|
||||
const wrapperExtraClass = (isRecentFinished && s1 && s2) ? ' facr-finished' : '';
|
||||
|
||||
const headerLabel = isLive ? 'Aktuální zápas' : (isFuture ? 'Nadcházející zápas' : (isRecentFinished ? 'Poslední zápas' : `Zápasy (${idx+1}/${items.length})`));
|
||||
|
||||
root.innerHTML = `
|
||||
<div class="lte-football-upcoming${wrapperExtraClass}">
|
||||
<div class="facr-comp-title lte-football-date" style="text-align:center; margin-bottom:6px;">${compName}</div>
|
||||
<div class="facr-upcoming-header">
|
||||
<button id="facr-prev" class="facr-nav">◀</button>
|
||||
<span class="lte-header lte-header-upcoming">${headerLabel}</span>
|
||||
<button id="facr-next" class="facr-nav">▶</button>
|
||||
</div>
|
||||
<div class="lte-teams">
|
||||
<span class="lte-team-name lte-team-1 lte-header" title="${escapeHTML(m.home)}">
|
||||
<span class="lte-team-logo"><img decoding="async" src="${homeLogo}" alt="${escapeHTML(m.home)}"></span>${homeName}
|
||||
<span id="facr-inline-status" class="facr-inline-status" aria-live="polite"></span>
|
||||
${isRecentFinished && s1 ? `<span class="lte-team-count-mob">${s1}</span>` : ''}
|
||||
</span>
|
||||
<span class="lte-team-count">
|
||||
<span id="facr-mid" style="font-size:32px; line-height:1; font-weight:700; display:inline-block; min-width:120px; text-align:center;">${midText}</span>
|
||||
${isRecentFinished && s1 && s2 ? `<span class="facr-mob-center-score">${s1}<span>:</span>${s2}</span>` : ''}
|
||||
</span>
|
||||
<span class="lte-team-name lte-team-2 lte-header" title="${escapeHTML(m.away)}">
|
||||
${isRecentFinished && s2 ? `<span class=\"lte-team-count-mob\">${s2}</span>` : ''}${awayName}<span class="lte-team-logo"><img decoding="async" src="${awayLogo}" alt="${escapeHTML(m.away)}"></span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="lte-football-date" style="text-align:center;" title="${escapeHTML(m.date_time + (m.venue?`, ${m.venue}`:''))}">${escapeHTML(dateOnly + (venue?`, ${venue}`:''))}</span>
|
||||
${timeDisplay ? `<span class="lte-football-time" style="display:block; text-align:center;">${escapeHTML(timeDisplay)}</span>` : ''}
|
||||
<span id="facr-countdown" class="lte-football-date" style="display:block; text-align:center;"></span>
|
||||
<br>
|
||||
<a class="lte-football-date" target="_blank" href="${facrLink}" style="text-align:center; background-color:#c42221; color:#ffffff; opacity:1;">Detail na FACR</a>
|
||||
<span style="display:block; margin-top:6px;"></span>
|
||||
<a class="lte-football-date" href="#tabulka" style="text-align:center; background-color:#ffffff43; color:#ffffff; opacity:1; width:49%; display:inline-block;">Tabulka bodů</a>
|
||||
<a class="lte-football-date" href="/zapasy/vsechny.html" style="text-align:center; background-color:#ffffff43; color:#ffffff; opacity:1; width:49%; display:inline-block;">Všechny zápasy</a>
|
||||
</div>`;
|
||||
|
||||
const prev = document.getElementById('facr-prev');
|
||||
const next = document.getElementById('facr-next');
|
||||
if(prev) prev.onclick = ()=>{ state.matchIndex = (idx - 1 + items.length) % items.length; renderUpcoming(); };
|
||||
if(next) next.onclick = ()=>{ state.matchIndex = (idx + 1) % items.length; renderUpcoming(); };
|
||||
|
||||
// setup countdown / status text
|
||||
const cd = document.getElementById('facr-countdown');
|
||||
const inlineStatus = document.getElementById('facr-inline-status');
|
||||
if(cd || inlineStatus){
|
||||
const now2 = new Date();
|
||||
const startMs = m.date_time ? parseCZDate(m.date_time).getTime() : 0;
|
||||
const diff = startMs - now2.getTime();
|
||||
const twoH = 2*60*60*1000;
|
||||
const threeD = 3*24*60*60*1000;
|
||||
let text = '';
|
||||
if(diff > 0){
|
||||
text = `Začátek za ${fmtCountdown(diff)}`;
|
||||
}else if(Math.abs(diff) <= twoH){
|
||||
text = 'Právě probíhá';
|
||||
}else if(-diff < threeD){
|
||||
text = (m.score ? `Výsledek: ${m.score}` : 'Ukončeno');
|
||||
}else{
|
||||
text = '';
|
||||
}
|
||||
if(cd) cd.textContent = text;
|
||||
if(inlineStatus) inlineStatus.textContent = text;
|
||||
}
|
||||
|
||||
// Live countdown in the middle area when future and not today
|
||||
const midEl = document.getElementById('facr-mid');
|
||||
if(midEl){
|
||||
const startTime = m.date_time ? parseCZDate(m.date_time).getTime() : 0;
|
||||
function tick(){
|
||||
const now = Date.now();
|
||||
const diff = startTime - now;
|
||||
if(diff > 0){
|
||||
const longTxt = `Za ${fmtCountdownLong(diff)}`;
|
||||
midEl.textContent = longTxt;
|
||||
const inlineEl = document.getElementById('facr-inline-status');
|
||||
if(inlineEl) inlineEl.textContent = `Začátek za ${fmtCountdown(diff)}`;
|
||||
const cdEl = document.getElementById('facr-countdown');
|
||||
if(cdEl) cdEl.textContent = `Začátek za ${fmtCountdown(diff)}`;
|
||||
}else{
|
||||
// switch to score at/after kickoff
|
||||
const scoreTxt = m.score || '-';
|
||||
midEl.textContent = scoreTxt;
|
||||
const inlineEl = document.getElementById('facr-inline-status');
|
||||
if(inlineEl) inlineEl.textContent = (m.score ? `Výsledek: ${m.score}` : 'Ukončeno');
|
||||
const cdEl = document.getElementById('facr-countdown');
|
||||
if(cdEl) cdEl.textContent = (m.score ? `Výsledek: ${m.score}` : 'Ukončeno');
|
||||
if(state.upcomingTimerId){ clearInterval(state.upcomingTimerId); state.upcomingTimerId = null; }
|
||||
}
|
||||
}
|
||||
// Run live countdown for any future match (including today)
|
||||
if(startTime > Date.now()){
|
||||
tick();
|
||||
state.upcomingTimerId = setInterval(tick, 1000);
|
||||
} else {
|
||||
// Ensure inline status reflects finished state on initial render
|
||||
const inlineEl = document.getElementById('facr-inline-status');
|
||||
if(inlineEl) inlineEl.textContent = (m.score ? `Výsledek: ${m.score}` : 'Ukončeno');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderCompetitionTabs(){
|
||||
const tabs = document.getElementById('facr-comp-tabs');
|
||||
if(!tabs || !state.data) return;
|
||||
ensureFacrStyles();
|
||||
const comps = state.data.club_table.competitions || [];
|
||||
tabs.innerHTML = comps.map((c,i)=>
|
||||
`<button class="facr-tab ${i===state.compIndex?'active':''}" data-idx="${i}">${c.name || c.code || 'Soutěž'}</button>`
|
||||
).join('');
|
||||
tabs.querySelectorAll('button').forEach(btn=>{
|
||||
btn.addEventListener('click', ()=>{
|
||||
state.compIndex = Number(btn.dataset.idx)||0;
|
||||
renderCompetitionTabs();
|
||||
renderTable();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderTable(){
|
||||
const tbody = document.getElementById('facr-table-body');
|
||||
const badge = document.getElementById('facr-comp-badge');
|
||||
if(!tbody || !state.data) return;
|
||||
const comps = state.data.club_table.competitions || [];
|
||||
if(comps.length === 0){ tbody.innerHTML = ''; return; }
|
||||
const comp = comps[Math.min(state.compIndex, comps.length-1)];
|
||||
if(badge){ badge.textContent = comp.name || comp.code || 'Soutěž'; }
|
||||
const rows = comp.table && comp.table.overall ? comp.table.overall : [];
|
||||
tbody.innerHTML = rows.map(r=>`
|
||||
<tr>
|
||||
<td class="lte-row"><span>${r.rank}</span></td>
|
||||
<td class="lte-club-logo"><img decoding="async" src="${r.team_logo_url || 'img/logo.png'}"></td>
|
||||
<td class="lte-name">${r.team}</td>
|
||||
<td class="lte-rate">${r.played}</td>
|
||||
<td class="lte-rate">${r.wins}</td>
|
||||
<td class="lte-rate">${r.draws}</td>
|
||||
<td class="lte-rate">${r.losses}</td>
|
||||
<td class="lte-rate">${r.score}</td>
|
||||
<td class="lte-summary">${r.points}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function renderAllMatches(){
|
||||
const container = document.getElementById('facr-all-matches');
|
||||
if(!container || !state.data) return;
|
||||
const comps = state.data.club_detail.competitions || [];
|
||||
const sections = [];
|
||||
for(const comp of comps){
|
||||
const matches = (comp.matches || [])
|
||||
.map(m=>({ m, dt: parseCZDate(m.date_time) }))
|
||||
.filter(x=>!!x.dt)
|
||||
.sort((a,b)=> b.dt - a.dt);
|
||||
if(matches.length === 0) continue;
|
||||
const compName = truncate(escapeHTML(comp.name || comp.code || 'Soutěž'), 40);
|
||||
const itemsHtml = matches.map(({m})=>{
|
||||
const homeLogo = m.home_logo_url || '../img/logo.png';
|
||||
const awayLogo = m.away_logo_url || '../img/logo.png';
|
||||
const facrLink = m.report_url || comp.matches_link || state.data.club_detail.url || '#';
|
||||
const score = m.score || '-';
|
||||
const GRID_MAX = 22;
|
||||
const home = truncate(escapeHTML(m.home), GRID_MAX);
|
||||
const away = truncate(escapeHTML(m.away), GRID_MAX);
|
||||
const dateVenue = truncate(escapeHTML(m.date_time + (m.venue?`, ${m.venue}`:'')), 36);
|
||||
const s1 = escapeHTML((score.split(':')[0]||'-'));
|
||||
const s2 = escapeHTML((score.split(':')[1]||'-'));
|
||||
return `
|
||||
<a href="${facrLink}" target="_blank" class="lte-item swiper-slide">
|
||||
<div class="lte-teams lte-match-time-public">
|
||||
<span class="lte-team-name lte-team-1 lte-header" title="${escapeHTML(m.home)}">
|
||||
<span class="lte-team-logo"><img src="${homeLogo}" alt="${escapeHTML(m.home)}"></span>${home}</span>
|
||||
<span class="lte-score-mob lte-score-1">${s1}</span>
|
||||
<span class="lte-team-count">
|
||||
<span class="lte-c lte-score-1">${s1}</span>
|
||||
<span class="lte-d">:</span>
|
||||
<span class="lte-c lte-score-4">${s2}</span>
|
||||
</span>
|
||||
<span class="lte-team-name lte-team-2 lte-header" title="${escapeHTML(m.away)}">${away}
|
||||
<span class="lte-team-logo"><img src="${awayLogo}" alt="${escapeHTML(m.away)}"></span>
|
||||
</span>
|
||||
<span class="lte-score-mob lte-score-4">${s2}</span>
|
||||
</div>
|
||||
<div class="lte-footer">
|
||||
<span class="lte-football-date" title="${escapeHTML(m.date_time + (m.venue?`, ${m.venue}`:''))}">${dateVenue}</span>
|
||||
</div>
|
||||
</a>`;
|
||||
}).join('');
|
||||
sections.push(`
|
||||
<div class="lte-section">
|
||||
<h3 class="lte-header" style="margin: 20px 0 10px;">${compName}</h3>
|
||||
<div class="lte-football-matches inner-page">${itemsHtml}</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
container.innerHTML = sections.join('');
|
||||
}
|
||||
|
||||
function schedule(){
|
||||
if(state.intervalId) clearInterval(state.intervalId);
|
||||
const intervalMs = isWithinMatchWindow() ? 2*60*1000 : 30*60*1000;
|
||||
state.intervalId = setInterval(async ()=>{
|
||||
try{
|
||||
await fetchData();
|
||||
renderUpcoming();
|
||||
renderCompetitionTabs();
|
||||
renderTable();
|
||||
renderAllMatches();
|
||||
schedule(); // reevaluate interval if window changed
|
||||
}catch(e){ console.warn('refresh failed', e); }
|
||||
}, intervalMs);
|
||||
}
|
||||
|
||||
async function init(){
|
||||
try{
|
||||
await fetchData();
|
||||
renderUpcoming();
|
||||
renderCompetitionTabs();
|
||||
renderTable();
|
||||
renderAllMatches();
|
||||
schedule();
|
||||
}catch(e){
|
||||
console.error('FACR init failed', e);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
})();
|
||||
|
||||
+552
-552
File diff suppressed because it is too large
Load Diff
+237
-237
@@ -1,238 +1,238 @@
|
||||
/**
|
||||
* jQuery plugin paroller.js v1.4.7
|
||||
* https://github.com/tgomilar/paroller.js
|
||||
* preview: https://tgomilar.github.io/paroller/
|
||||
* author: Tanja Gomilar
|
||||
**/
|
||||
|
||||
(function (factory) {
|
||||
'use strict';
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('parollerjs', ['jquery'], factory);
|
||||
} else if (typeof module === 'object' && typeof module.exports === 'object') {
|
||||
module.exports = factory(require('jquery'));
|
||||
}
|
||||
else {
|
||||
factory(jQuery);
|
||||
}
|
||||
})(function ($) {
|
||||
'use strict';
|
||||
|
||||
var working = false;
|
||||
var scrollAction = function() {
|
||||
working = false;
|
||||
};
|
||||
|
||||
var setDirection = {
|
||||
bgVertical: function (elem, bgOffset) {
|
||||
return elem.css({'background-position': 'center ' + -bgOffset + 'px'});
|
||||
},
|
||||
bgHorizontal: function (elem, bgOffset) {
|
||||
return elem.css({'background-position': -bgOffset + 'px' + ' center'});
|
||||
},
|
||||
vertical: function (elem, elemOffset, transition, oldTransform) {
|
||||
(oldTransform === 'none' ? oldTransform = '' : true);
|
||||
return elem.css({
|
||||
'-webkit-transform': 'translateY(' + elemOffset + 'px)' + oldTransform,
|
||||
'-moz-transform': 'translateY(' + elemOffset + 'px)' + oldTransform,
|
||||
'transform': 'translateY(' + elemOffset + 'px)' + oldTransform,
|
||||
'transition': transition,
|
||||
'will-change': 'transform'
|
||||
});
|
||||
},
|
||||
horizontal: function (elem, elemOffset, transition, oldTransform) {
|
||||
(oldTransform === 'none' ? oldTransform = '' : true);
|
||||
return elem.css({
|
||||
'-webkit-transform': 'translateX(' + elemOffset + 'px)' + oldTransform,
|
||||
'-moz-transform': 'translateX(' + elemOffset + 'px)' + oldTransform,
|
||||
'transform': 'translateX(' + elemOffset + 'px)' + oldTransform,
|
||||
'transition': transition,
|
||||
'will-change': 'transform'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var setMovement = {
|
||||
factor: function (elem, width, options) {
|
||||
var dataFactor = elem.data('paroller-factor');
|
||||
var factor = (dataFactor) ? dataFactor : options.factor;
|
||||
if (width < 576) {
|
||||
var dataFactorXs = elem.data('paroller-factor-xs');
|
||||
var factorXs = (dataFactorXs) ? dataFactorXs : options.factorXs;
|
||||
return (factorXs) ? factorXs : factor;
|
||||
}
|
||||
else if (width <= 768) {
|
||||
var dataFactorSm = elem.data('paroller-factor-sm');
|
||||
var factorSm = (dataFactorSm) ? dataFactorSm : options.factorSm;
|
||||
return (factorSm) ? factorSm : factor;
|
||||
}
|
||||
else if (width <= 1024) {
|
||||
var dataFactorMd = elem.data('paroller-factor-md');
|
||||
var factorMd = (dataFactorMd) ? dataFactorMd : options.factorMd;
|
||||
return (factorMd) ? factorMd : factor;
|
||||
}
|
||||
else if (width <= 1200) {
|
||||
var dataFactorLg = elem.data('paroller-factor-lg');
|
||||
var factorLg = (dataFactorLg) ? dataFactorLg : options.factorLg;
|
||||
return (factorLg) ? factorLg : factor;
|
||||
} else if (width <= 1920) {
|
||||
var dataFactorXl = elem.data('paroller-factor-xl');
|
||||
var factorXl = (dataFactorXl) ? dataFactorXl : options.factorXl;
|
||||
return (factorXl) ? factorXl : factor;
|
||||
} else {
|
||||
return factor;
|
||||
}
|
||||
},
|
||||
bgOffset: function (offset, factor) {
|
||||
return Math.round(offset * factor);
|
||||
},
|
||||
transform: function (offset, factor, windowHeight, height) {
|
||||
return Math.round((offset - (windowHeight / 2) + height) * factor);
|
||||
}
|
||||
};
|
||||
|
||||
var clearPositions = {
|
||||
background: function (elem) {
|
||||
return elem.css({'background-position': 'unset'});
|
||||
},
|
||||
foreground: function (elem) {
|
||||
return elem.css({
|
||||
'transform' : 'unset',
|
||||
'transition' : 'unset'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.paroller = function (options) {
|
||||
var windowHeight = $(window).height();
|
||||
var documentHeight = $(document).height();
|
||||
|
||||
// default options
|
||||
var options = $.extend({
|
||||
factor: 0, // - to +
|
||||
factorXs: 0, // - to +
|
||||
factorSm: 0, // - to +
|
||||
factorMd: 0, // - to +
|
||||
factorLg: 0, // - to +
|
||||
factorXl: 0, // - to +
|
||||
transition: 'translate 0.1s ease', // CSS transition
|
||||
type: 'background', // foreground
|
||||
direction: 'vertical', // horizontal
|
||||
offsetVal : 0, // horizontal
|
||||
}, options);
|
||||
|
||||
|
||||
return this.each(function () {
|
||||
var $this = $(this);
|
||||
var width = $(window).width();
|
||||
var offset = $this.offset().top;
|
||||
var height = $this.outerHeight();
|
||||
|
||||
var dataType = $this.data('paroller-type');
|
||||
var dataDirection = $this.data('paroller-direction');
|
||||
var dataTransition = $this.data('paroller-transition');
|
||||
var oldTransform = $this.css('transform');
|
||||
var offsetVal = parseInt($this.data('offset'));
|
||||
|
||||
var transition = (dataTransition) ? dataTransition : options.transition;
|
||||
var type = (dataType) ? dataType : options.type;
|
||||
var direction = (dataDirection) ? dataDirection : options.direction;
|
||||
var factor = 0;
|
||||
var bgOffset = setMovement.bgOffset(offset, factor);
|
||||
var transform = setMovement.transform(offset, factor, windowHeight, height);
|
||||
|
||||
if (type === 'background') {
|
||||
if (direction === 'vertical') {
|
||||
setDirection.bgVertical($this, bgOffset);
|
||||
}
|
||||
else if (direction === 'horizontal') {
|
||||
setDirection.bgHorizontal($this, bgOffset);
|
||||
}
|
||||
}
|
||||
else if (type === 'foreground') {
|
||||
if (direction === 'vertical') {
|
||||
setDirection.vertical($this, transform, transition, oldTransform);
|
||||
}
|
||||
else if (direction === 'horizontal') {
|
||||
setDirection.horizontal($this, transform, transition, oldTransform);
|
||||
}
|
||||
}
|
||||
|
||||
$(window).on('resize', function () {
|
||||
var scrolling = $(this).scrollTop();
|
||||
width = $(window).width();
|
||||
offset = $this.offset().top;
|
||||
height = $this.outerHeight();
|
||||
factor = setMovement.factor($this, width, options);
|
||||
bgOffset = Math.round(offset * factor);
|
||||
transform = Math.round((offset - (windowHeight / 2) + height) * factor);
|
||||
|
||||
/*if ( offsetVal != 0 )*/
|
||||
|
||||
offset = offset - 400;
|
||||
|
||||
|
||||
|
||||
if (! working) {
|
||||
window.requestAnimationFrame(scrollAction);
|
||||
working = true;
|
||||
}
|
||||
|
||||
if (type === 'background') {
|
||||
clearPositions.background($this);
|
||||
if (direction === 'vertical') {
|
||||
setDirection.bgVertical($this, bgOffset);
|
||||
}
|
||||
else if (direction === 'horizontal') {
|
||||
setDirection.bgHorizontal($this, bgOffset);
|
||||
}
|
||||
}
|
||||
else if ((type === 'foreground') && (scrolling <= documentHeight)) {
|
||||
clearPositions.foreground($this);
|
||||
if (direction === 'vertical') {
|
||||
setDirection.vertical($this, transform, transition);
|
||||
}
|
||||
else if (direction === 'horizontal') {
|
||||
setDirection.horizontal($this, transform, transition);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on('scroll', function () {
|
||||
var scrolling = $(this).scrollTop();
|
||||
var scrollTop = $(document).scrollTop();
|
||||
|
||||
if (scrollTop === 0) {
|
||||
factor = 0;
|
||||
} else {
|
||||
factor = setMovement.factor($this, width, options);
|
||||
}
|
||||
|
||||
bgOffset = Math.round((offset - scrolling) * factor);
|
||||
transform = Math.round(((offset - (windowHeight / 2) + height) - scrolling) * factor);
|
||||
|
||||
if (! working) {
|
||||
window.requestAnimationFrame(scrollAction);
|
||||
working = true;
|
||||
}
|
||||
|
||||
if (type === 'background') {
|
||||
if (direction === 'vertical') {
|
||||
setDirection.bgVertical($this, bgOffset);
|
||||
}
|
||||
else if (direction === 'horizontal') {
|
||||
setDirection.bgHorizontal($this, bgOffset);
|
||||
}
|
||||
}
|
||||
else if ((type === 'foreground') && (scrolling <= documentHeight)) {
|
||||
if (direction === 'vertical') {
|
||||
setDirection.vertical($this, transform, transition, oldTransform);
|
||||
}
|
||||
else if (direction === 'horizontal') {
|
||||
setDirection.horizontal($this, transform, transition, oldTransform);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
/**
|
||||
* jQuery plugin paroller.js v1.4.7
|
||||
* https://github.com/tgomilar/paroller.js
|
||||
* preview: https://tgomilar.github.io/paroller/
|
||||
* author: Tanja Gomilar
|
||||
**/
|
||||
|
||||
(function (factory) {
|
||||
'use strict';
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('parollerjs', ['jquery'], factory);
|
||||
} else if (typeof module === 'object' && typeof module.exports === 'object') {
|
||||
module.exports = factory(require('jquery'));
|
||||
}
|
||||
else {
|
||||
factory(jQuery);
|
||||
}
|
||||
})(function ($) {
|
||||
'use strict';
|
||||
|
||||
var working = false;
|
||||
var scrollAction = function() {
|
||||
working = false;
|
||||
};
|
||||
|
||||
var setDirection = {
|
||||
bgVertical: function (elem, bgOffset) {
|
||||
return elem.css({'background-position': 'center ' + -bgOffset + 'px'});
|
||||
},
|
||||
bgHorizontal: function (elem, bgOffset) {
|
||||
return elem.css({'background-position': -bgOffset + 'px' + ' center'});
|
||||
},
|
||||
vertical: function (elem, elemOffset, transition, oldTransform) {
|
||||
(oldTransform === 'none' ? oldTransform = '' : true);
|
||||
return elem.css({
|
||||
'-webkit-transform': 'translateY(' + elemOffset + 'px)' + oldTransform,
|
||||
'-moz-transform': 'translateY(' + elemOffset + 'px)' + oldTransform,
|
||||
'transform': 'translateY(' + elemOffset + 'px)' + oldTransform,
|
||||
'transition': transition,
|
||||
'will-change': 'transform'
|
||||
});
|
||||
},
|
||||
horizontal: function (elem, elemOffset, transition, oldTransform) {
|
||||
(oldTransform === 'none' ? oldTransform = '' : true);
|
||||
return elem.css({
|
||||
'-webkit-transform': 'translateX(' + elemOffset + 'px)' + oldTransform,
|
||||
'-moz-transform': 'translateX(' + elemOffset + 'px)' + oldTransform,
|
||||
'transform': 'translateX(' + elemOffset + 'px)' + oldTransform,
|
||||
'transition': transition,
|
||||
'will-change': 'transform'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var setMovement = {
|
||||
factor: function (elem, width, options) {
|
||||
var dataFactor = elem.data('paroller-factor');
|
||||
var factor = (dataFactor) ? dataFactor : options.factor;
|
||||
if (width < 576) {
|
||||
var dataFactorXs = elem.data('paroller-factor-xs');
|
||||
var factorXs = (dataFactorXs) ? dataFactorXs : options.factorXs;
|
||||
return (factorXs) ? factorXs : factor;
|
||||
}
|
||||
else if (width <= 768) {
|
||||
var dataFactorSm = elem.data('paroller-factor-sm');
|
||||
var factorSm = (dataFactorSm) ? dataFactorSm : options.factorSm;
|
||||
return (factorSm) ? factorSm : factor;
|
||||
}
|
||||
else if (width <= 1024) {
|
||||
var dataFactorMd = elem.data('paroller-factor-md');
|
||||
var factorMd = (dataFactorMd) ? dataFactorMd : options.factorMd;
|
||||
return (factorMd) ? factorMd : factor;
|
||||
}
|
||||
else if (width <= 1200) {
|
||||
var dataFactorLg = elem.data('paroller-factor-lg');
|
||||
var factorLg = (dataFactorLg) ? dataFactorLg : options.factorLg;
|
||||
return (factorLg) ? factorLg : factor;
|
||||
} else if (width <= 1920) {
|
||||
var dataFactorXl = elem.data('paroller-factor-xl');
|
||||
var factorXl = (dataFactorXl) ? dataFactorXl : options.factorXl;
|
||||
return (factorXl) ? factorXl : factor;
|
||||
} else {
|
||||
return factor;
|
||||
}
|
||||
},
|
||||
bgOffset: function (offset, factor) {
|
||||
return Math.round(offset * factor);
|
||||
},
|
||||
transform: function (offset, factor, windowHeight, height) {
|
||||
return Math.round((offset - (windowHeight / 2) + height) * factor);
|
||||
}
|
||||
};
|
||||
|
||||
var clearPositions = {
|
||||
background: function (elem) {
|
||||
return elem.css({'background-position': 'unset'});
|
||||
},
|
||||
foreground: function (elem) {
|
||||
return elem.css({
|
||||
'transform' : 'unset',
|
||||
'transition' : 'unset'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.paroller = function (options) {
|
||||
var windowHeight = $(window).height();
|
||||
var documentHeight = $(document).height();
|
||||
|
||||
// default options
|
||||
var options = $.extend({
|
||||
factor: 0, // - to +
|
||||
factorXs: 0, // - to +
|
||||
factorSm: 0, // - to +
|
||||
factorMd: 0, // - to +
|
||||
factorLg: 0, // - to +
|
||||
factorXl: 0, // - to +
|
||||
transition: 'translate 0.1s ease', // CSS transition
|
||||
type: 'background', // foreground
|
||||
direction: 'vertical', // horizontal
|
||||
offsetVal : 0, // horizontal
|
||||
}, options);
|
||||
|
||||
|
||||
return this.each(function () {
|
||||
var $this = $(this);
|
||||
var width = $(window).width();
|
||||
var offset = $this.offset().top;
|
||||
var height = $this.outerHeight();
|
||||
|
||||
var dataType = $this.data('paroller-type');
|
||||
var dataDirection = $this.data('paroller-direction');
|
||||
var dataTransition = $this.data('paroller-transition');
|
||||
var oldTransform = $this.css('transform');
|
||||
var offsetVal = parseInt($this.data('offset'));
|
||||
|
||||
var transition = (dataTransition) ? dataTransition : options.transition;
|
||||
var type = (dataType) ? dataType : options.type;
|
||||
var direction = (dataDirection) ? dataDirection : options.direction;
|
||||
var factor = 0;
|
||||
var bgOffset = setMovement.bgOffset(offset, factor);
|
||||
var transform = setMovement.transform(offset, factor, windowHeight, height);
|
||||
|
||||
if (type === 'background') {
|
||||
if (direction === 'vertical') {
|
||||
setDirection.bgVertical($this, bgOffset);
|
||||
}
|
||||
else if (direction === 'horizontal') {
|
||||
setDirection.bgHorizontal($this, bgOffset);
|
||||
}
|
||||
}
|
||||
else if (type === 'foreground') {
|
||||
if (direction === 'vertical') {
|
||||
setDirection.vertical($this, transform, transition, oldTransform);
|
||||
}
|
||||
else if (direction === 'horizontal') {
|
||||
setDirection.horizontal($this, transform, transition, oldTransform);
|
||||
}
|
||||
}
|
||||
|
||||
$(window).on('resize', function () {
|
||||
var scrolling = $(this).scrollTop();
|
||||
width = $(window).width();
|
||||
offset = $this.offset().top;
|
||||
height = $this.outerHeight();
|
||||
factor = setMovement.factor($this, width, options);
|
||||
bgOffset = Math.round(offset * factor);
|
||||
transform = Math.round((offset - (windowHeight / 2) + height) * factor);
|
||||
|
||||
/*if ( offsetVal != 0 )*/
|
||||
|
||||
offset = offset - 400;
|
||||
|
||||
|
||||
|
||||
if (! working) {
|
||||
window.requestAnimationFrame(scrollAction);
|
||||
working = true;
|
||||
}
|
||||
|
||||
if (type === 'background') {
|
||||
clearPositions.background($this);
|
||||
if (direction === 'vertical') {
|
||||
setDirection.bgVertical($this, bgOffset);
|
||||
}
|
||||
else if (direction === 'horizontal') {
|
||||
setDirection.bgHorizontal($this, bgOffset);
|
||||
}
|
||||
}
|
||||
else if ((type === 'foreground') && (scrolling <= documentHeight)) {
|
||||
clearPositions.foreground($this);
|
||||
if (direction === 'vertical') {
|
||||
setDirection.vertical($this, transform, transition);
|
||||
}
|
||||
else if (direction === 'horizontal') {
|
||||
setDirection.horizontal($this, transform, transition);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on('scroll', function () {
|
||||
var scrolling = $(this).scrollTop();
|
||||
var scrollTop = $(document).scrollTop();
|
||||
|
||||
if (scrollTop === 0) {
|
||||
factor = 0;
|
||||
} else {
|
||||
factor = setMovement.factor($this, width, options);
|
||||
}
|
||||
|
||||
bgOffset = Math.round((offset - scrolling) * factor);
|
||||
transform = Math.round(((offset - (windowHeight / 2) + height) - scrolling) * factor);
|
||||
|
||||
if (! working) {
|
||||
window.requestAnimationFrame(scrollAction);
|
||||
working = true;
|
||||
}
|
||||
|
||||
if (type === 'background') {
|
||||
if (direction === 'vertical') {
|
||||
setDirection.bgVertical($this, bgOffset);
|
||||
}
|
||||
else if (direction === 'horizontal') {
|
||||
setDirection.bgHorizontal($this, bgOffset);
|
||||
}
|
||||
}
|
||||
else if ((type === 'foreground') && (scrolling <= documentHeight)) {
|
||||
if (direction === 'vertical') {
|
||||
setDirection.vertical($this, transform, transition, oldTransform);
|
||||
}
|
||||
else if (direction === 'horizontal') {
|
||||
setDirection.horizontal($this, transform, transition, oldTransform);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
+409
-409
@@ -1,409 +1,409 @@
|
||||
/*
|
||||
* zoomSlider - v1.0.2 Fork
|
||||
* CSS3 background zoom slideshow
|
||||
* http://mingthings.com
|
||||
*
|
||||
* Made by Ming Yeung
|
||||
* Under MIT License
|
||||
*/
|
||||
|
||||
;(function ( $, window, document, undefined ) {
|
||||
|
||||
var pluginName = "zoomSlider",
|
||||
defaults = {
|
||||
src: null,
|
||||
src2: null,
|
||||
speed: 8000,
|
||||
initzoom: 1.2,
|
||||
switchSpeed: 1000,
|
||||
interval: 4600,
|
||||
autoplay: true,
|
||||
bullets: true,
|
||||
overlay: 'plain' // false, plain, dots
|
||||
};
|
||||
|
||||
// The actual plugin constructor
|
||||
function Plugin ( element, options ) {
|
||||
this.element = element;
|
||||
this.$el = $(element);
|
||||
this._defaults = defaults;
|
||||
this._name = pluginName;
|
||||
|
||||
var elData = this.$el.data();
|
||||
var elDataObj = {};
|
||||
for (var key in elData) {
|
||||
if ( elData.hasOwnProperty(key) ) {
|
||||
if ( key.match(/zs[A-Z]/) ) {
|
||||
var keyName = key.substr(2);
|
||||
keyName = keyName.charAt(0).toLowerCase() + keyName.slice(1);
|
||||
elDataObj[keyName] = elData[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
this.settings = $.extend( {}, defaults, elDataObj, options );
|
||||
|
||||
if ( this.settings.src == null || this.settings.src.length < 1 ) {
|
||||
console.log('ZoomSlider terminated - invalid input.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
// Avoid Plugin.prototype conflicts
|
||||
$.extend(Plugin.prototype, {
|
||||
init: function () {
|
||||
// Place initialization logic here
|
||||
// You already have access to the DOM element and
|
||||
// the options via the instance, e.g. this.element
|
||||
// and this.settings
|
||||
// you can add more functions like the one below and
|
||||
// call them like so: this.yourOtherFunction(this.element, this.settings).
|
||||
|
||||
// make sure src is an Array
|
||||
if ($.isArray(this.settings.src) == false) {
|
||||
this.settings.src = [this.settings.src];
|
||||
}
|
||||
|
||||
if ($.isArray(this.settings.src2) == false) {
|
||||
this.settings.src2 = [this.settings.src2];
|
||||
}
|
||||
|
||||
// https://github.com/twitter/bootstrap/issues/2870
|
||||
this.transEndEventNames = {
|
||||
'WebkitTransition' : 'webkitTransitionEnd',
|
||||
'MozTransition' : 'transitionend',
|
||||
'OTransition' : 'oTransitionEnd',
|
||||
'msTransition' : 'MSTransitionEnd',
|
||||
'transition' : 'transitionend'
|
||||
};
|
||||
this.transEndEventName = this.transEndEventNames[ Modernizr.prefixed( 'transition' ) ];
|
||||
|
||||
// suport for css transforms and css transitions
|
||||
this.support = Modernizr.csstransitions && Modernizr.csstransforms;
|
||||
|
||||
// set inline CSS3 transition properties
|
||||
var transformPrefixed = Modernizr.prefixed('transform');
|
||||
transformPrefixed = transformPrefixed.replace(/([A-Z])/g, function(transformPrefixed,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-');
|
||||
this.transitionProp = {
|
||||
'transition': transformPrefixed+' '+this.settings.speed+'ms ease-out, opacity '+this.settings.switchSpeed+'ms'
|
||||
};
|
||||
|
||||
this.numSlides = this.settings.src.length;
|
||||
|
||||
// make sure the container is not [position: static]
|
||||
switch(this.$el.css('position')) {
|
||||
case 'relative':
|
||||
case 'absolute':
|
||||
case 'fixed':
|
||||
break;
|
||||
default:
|
||||
this.$el.css('position', 'relative');
|
||||
break;
|
||||
}
|
||||
|
||||
// make sure the first image has been loaded.
|
||||
var self = this;
|
||||
var $img = $('<img />');
|
||||
//$img.load( function() {
|
||||
if (self.numSlides == 1) {
|
||||
self.initSingle();
|
||||
} else {
|
||||
self.initSlideshow();
|
||||
}
|
||||
//});
|
||||
|
||||
$img.attr('src', this.settings.src[0]);
|
||||
},
|
||||
initSlideshow: function () {
|
||||
|
||||
var self = this;
|
||||
var $slideshow = $('<div class="zs-slideshow"></div>'),
|
||||
$slidesWrap = $('<div class="zs-slides"></div>'),
|
||||
$arrowsWrap = $('<div class="zs-arrows"></div>'),
|
||||
$zslayer = $('<div class="zs-layer"></div>'),
|
||||
$bulletsWrap = $('<div class="zs-bullets"></div>'),
|
||||
$ww = $(window).width();
|
||||
|
||||
for (i = 0; i < this.numSlides; i++) {
|
||||
|
||||
var $slide = $('<div class="zs-slide zs-slide-' + i + '"></div>');
|
||||
if ( $ww <= 767 && this.settings.src2[i].length ) {
|
||||
|
||||
$slide.css({ 'background-image': "url('" + this.settings.src2[i] + "')" }).appendTo( $slidesWrap );
|
||||
}
|
||||
else {
|
||||
|
||||
$slide.css({ 'background-image': "url('" + this.settings.src[i] + "')" }).appendTo( $slidesWrap );
|
||||
}
|
||||
|
||||
var $bullet = $('<div class="zs-bullet zs-bullet-' + i + '"></div>')
|
||||
$bullet.appendTo( $bulletsWrap );
|
||||
|
||||
if (i == 0) {
|
||||
|
||||
$slide.addClass('active').css('opacity', 1);
|
||||
$bullet.addClass('active');
|
||||
|
||||
$('.zs-enabled .lte-zs-slider-inner.lte-zs-slide-' + i).addClass('visible');
|
||||
}
|
||||
}
|
||||
|
||||
self._promoteChildren();
|
||||
|
||||
$slideshow.append( $zslayer );
|
||||
|
||||
$slideshow.append( $slidesWrap ).prependTo( this.$el );
|
||||
|
||||
if ( this.settings.bullets != false || this.settings.bullets == 'outside' ) {
|
||||
|
||||
if ( this.settings.bullets == 'outside' ) {
|
||||
|
||||
$slideshow.after( $bulletsWrap );
|
||||
}
|
||||
else {
|
||||
|
||||
$slideshow.append( $bulletsWrap );
|
||||
}
|
||||
|
||||
$slideshow.on('click', '.zs-bullet', function(e){
|
||||
self.jump( $(this).index() );
|
||||
});
|
||||
}
|
||||
|
||||
if ( this.settings.arrows == true || this.settings.arrows == 'right' || this.settings.arrows == 'bottom' ) {
|
||||
|
||||
var container_class = '';
|
||||
if ( this.settings.arrows == 'bottom' ) {
|
||||
|
||||
container_class = 'container';
|
||||
}
|
||||
|
||||
$('<div class="'+container_class+'"><span class="lte-arrow-left">'+this.settings.prev+'</span><span class="lte-arrow-right">'+this.settings.next+'</span></div>').appendTo( $arrowsWrap );
|
||||
this.$el.append( $arrowsWrap );
|
||||
|
||||
this.$el.on('click', '.lte-arrow-left', function(e){
|
||||
self.prev();
|
||||
});
|
||||
|
||||
this.$el.on('click', '.lte-arrow-right', function(e){
|
||||
self.next() ;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
this.pos = 0;
|
||||
this.pending = null;
|
||||
this.switching = false;
|
||||
this.$slideshow = $slideshow;
|
||||
this.$slides = $slidesWrap.children( '.zs-slide' );
|
||||
this.$bullets = $bulletsWrap.children( '.zs-bullet' );
|
||||
this.$el.addClass('zs-enabled');
|
||||
|
||||
var $firstBlock = $('.zs-enabled .lte-zs-slider-inner');
|
||||
|
||||
$('.zs-enabled .lte-zs-slider-inner').css('opacity', '');
|
||||
|
||||
var minHeight = 0;
|
||||
$('.lte-zs-slider-inner').each(function(i, el) {
|
||||
|
||||
if ( $(el).height() > minHeight ) {
|
||||
|
||||
minHeight = $(el).height();
|
||||
}
|
||||
});
|
||||
|
||||
if (this.support) {
|
||||
var $firstSlide = this.$slides.eq(0);
|
||||
var $initzoom = this.settings.initzoom;
|
||||
$firstSlide.css('opacity', 0).css( this.transitionProp );
|
||||
|
||||
$('.lte-zs-slider-wrapper').css('min-height', (minHeight ) + 'px' );
|
||||
$('.zs-slideshow').css('min-height', (minHeight - 2 ) + 'px' );
|
||||
jQuery(window).on('resize', function(){
|
||||
|
||||
var minHeight = 0;
|
||||
$('.lte-zs-slider-inner').each(function(i, el) {
|
||||
|
||||
if ( $(el).height() > minHeight ) {
|
||||
|
||||
minHeight = $(el).height();
|
||||
}
|
||||
});
|
||||
|
||||
$('.lte-zs-slider-wrapper').css('min-height', (minHeight ) + 'px' );
|
||||
$('.zs-slideshow').css('min-height', (minHeight - 2 ) + 'px' );
|
||||
});
|
||||
|
||||
setTimeout(function(){
|
||||
|
||||
$firstSlide.css( { 'opacity': 1.0, 'transform': 'scale('+ $initzoom +', '+ $initzoom +')', 'z-index': 2 } );
|
||||
}, 50);
|
||||
}
|
||||
|
||||
if (this.settings.autoplay == true) {
|
||||
this.play();
|
||||
}
|
||||
},
|
||||
initSingle: function() {
|
||||
var self = this;
|
||||
var $slideshow = $('<div class="zs-slideshow"></div>'),
|
||||
$slidesWrap = $('<div class="zs-slides"></div>'),
|
||||
$slide = $('<div class="zs-slide zs-slide-0"></div>');
|
||||
|
||||
$slide.css({ 'background-image': "url('" + this.settings.src[0] + "')" }).appendTo( $slidesWrap );
|
||||
$slide.addClass('active').css('opacity', 1);
|
||||
|
||||
$('.zs-enabled .lte-zs-slider-inner.lte-zs-slide-0').addClass('visible').addClass('single');
|
||||
|
||||
self._promoteChildren();
|
||||
|
||||
$slideshow.append( $slidesWrap ).prependTo( this.$el );
|
||||
this.$el.addClass('zs-enabled');
|
||||
|
||||
if (this.settings.overlay == 'dots') {
|
||||
this.$el.addClass('overlay-dots');
|
||||
} else if (this.settings.overlay == 'plain') {
|
||||
this.$el.addClass('overlay-plain')
|
||||
}
|
||||
|
||||
if (this.support) {
|
||||
$slide.css('opacity', 1).css( this.transitionProp );
|
||||
|
||||
if (this.settings) {
|
||||
|
||||
setTimeout(function(){
|
||||
$slide.css( { 'opacity': 1.0, 'transform': 'scale(1)', 'z-index': 2 } )
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
},
|
||||
_promoteChildren: function() {
|
||||
// make sure every children have high enough z-index
|
||||
this.$el.children().each(function(index){
|
||||
$this = $(this);
|
||||
if ($this.css('z-index') == 'auto') {
|
||||
$this.css('z-index', 2);
|
||||
}
|
||||
if ($this.css('position') == 'static') {
|
||||
$this.css('position', 'relative');
|
||||
}
|
||||
});
|
||||
},
|
||||
jump: function( pos ) {
|
||||
if ( pos >= this.numSlides ) {
|
||||
console.log('ZoomSlider: jump(pos) aborted. supplied index out of range.');
|
||||
return;
|
||||
}
|
||||
|
||||
if ( this.pos == pos ) return;
|
||||
|
||||
if ( this.switching ) {
|
||||
this.pending = pos;
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var $lastSlide = this.$slides.eq( this.pos );
|
||||
var $nowSlide = this.$slides.eq( pos );
|
||||
|
||||
$('.zs-enabled .lte-zoompages .current').html(pos + 1);
|
||||
$('.zs-enabled .lte-zs-slider-inner.visible').removeClass('visible');
|
||||
$('.zs-enabled .lte-zs-slider-inner.lte-zs-slide-' + pos).addClass('visible');
|
||||
|
||||
if ( this.support ) {
|
||||
|
||||
this.switching = true;
|
||||
$lastSlide.css('z-index', 1);
|
||||
$nowSlide.addClass('active')
|
||||
.css( this.transitionProp )
|
||||
.css( { 'opacity': 1.0, 'transform': 'scale('+this.settings.initzoom+', '+this.settings.initzoom+')', 'z-index': 2 } )
|
||||
.on( this.transEndEventName, function(e) {
|
||||
if (e.originalEvent.propertyName == 'opacity') {
|
||||
lastSlideBg = $lastSlide.css('background-image');
|
||||
$lastSlide.removeClass('active')
|
||||
.removeAttr('style')
|
||||
.css('background-image', lastSlideBg);
|
||||
$nowSlide.off( self.transEndEventName );
|
||||
self.switching = false;
|
||||
if ( self.pending != null ) {
|
||||
setTimeout(function(){
|
||||
var newPos = self.pending;
|
||||
self.pending = null;
|
||||
self.$bullets.eq(newPos).click();
|
||||
}, 30)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
$lastSlide.removeClass('active');
|
||||
$nowSlide.addClass('active');
|
||||
}
|
||||
this.$bullets.eq(this.pos).removeClass('active');
|
||||
this.$bullets.eq(pos).addClass('active');
|
||||
this.pos = pos;
|
||||
|
||||
if (this.settings.autoplay) {
|
||||
this.play();
|
||||
}
|
||||
},
|
||||
prev: function() {
|
||||
var posPrev = this.pos - 1;
|
||||
if (posPrev < 0) posPrev = this.numSlides - 1;
|
||||
this.jump( posPrev );
|
||||
},
|
||||
next: function() {
|
||||
var posNext = this.pos + 1;
|
||||
if (posNext >= this.numSlides) posNext = 0;
|
||||
this.jump( posNext );
|
||||
},
|
||||
play: function() {
|
||||
// clear any existing timer
|
||||
if (this.timer != null) {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
var self = this;
|
||||
this.settings.autoplay = true;
|
||||
// add timer
|
||||
|
||||
this.timer = setInterval( function(){
|
||||
self.next();
|
||||
}, this.settings.interval );
|
||||
},
|
||||
stop: function() {
|
||||
this.settings.autoplay = false;
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
});
|
||||
|
||||
// A really lightweight plugin wrapper around the constructor,
|
||||
// preventing against multiple instantiations
|
||||
$.fn[ pluginName ] = function ( options ) {
|
||||
return this.each(function() {
|
||||
if ( !$.data( this, "plugin_" + pluginName ) ) {
|
||||
$.data( this, "plugin_" + pluginName, new Plugin( this, options ) );
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var WidgetZoomsliderHandler = function ($scope, $) {
|
||||
|
||||
// auto create slideshow on [data-zs-enabled] instances.
|
||||
var $instances = $('[data-zs-src]');
|
||||
if ($instances.length > 0) {
|
||||
$instances.each( function(index) {
|
||||
var $this = $(this);
|
||||
$this.zoomSlider();
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$(window).on('elementor/frontend/init', function () {
|
||||
|
||||
elementorFrontend.hooks.addAction('frontend/element_ready/lte-zoomslider.default', WidgetZoomsliderHandler);
|
||||
});
|
||||
|
||||
})( jQuery, window, document );
|
||||
/*
|
||||
* zoomSlider - v1.0.2 Fork
|
||||
* CSS3 background zoom slideshow
|
||||
* http://mingthings.com
|
||||
*
|
||||
* Made by Ming Yeung
|
||||
* Under MIT License
|
||||
*/
|
||||
|
||||
;(function ( $, window, document, undefined ) {
|
||||
|
||||
var pluginName = "zoomSlider",
|
||||
defaults = {
|
||||
src: null,
|
||||
src2: null,
|
||||
speed: 8000,
|
||||
initzoom: 1.2,
|
||||
switchSpeed: 1000,
|
||||
interval: 4600,
|
||||
autoplay: true,
|
||||
bullets: true,
|
||||
overlay: 'plain' // false, plain, dots
|
||||
};
|
||||
|
||||
// The actual plugin constructor
|
||||
function Plugin ( element, options ) {
|
||||
this.element = element;
|
||||
this.$el = $(element);
|
||||
this._defaults = defaults;
|
||||
this._name = pluginName;
|
||||
|
||||
var elData = this.$el.data();
|
||||
var elDataObj = {};
|
||||
for (var key in elData) {
|
||||
if ( elData.hasOwnProperty(key) ) {
|
||||
if ( key.match(/zs[A-Z]/) ) {
|
||||
var keyName = key.substr(2);
|
||||
keyName = keyName.charAt(0).toLowerCase() + keyName.slice(1);
|
||||
elDataObj[keyName] = elData[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
this.settings = $.extend( {}, defaults, elDataObj, options );
|
||||
|
||||
if ( this.settings.src == null || this.settings.src.length < 1 ) {
|
||||
console.log('ZoomSlider terminated - invalid input.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
// Avoid Plugin.prototype conflicts
|
||||
$.extend(Plugin.prototype, {
|
||||
init: function () {
|
||||
// Place initialization logic here
|
||||
// You already have access to the DOM element and
|
||||
// the options via the instance, e.g. this.element
|
||||
// and this.settings
|
||||
// you can add more functions like the one below and
|
||||
// call them like so: this.yourOtherFunction(this.element, this.settings).
|
||||
|
||||
// make sure src is an Array
|
||||
if ($.isArray(this.settings.src) == false) {
|
||||
this.settings.src = [this.settings.src];
|
||||
}
|
||||
|
||||
if ($.isArray(this.settings.src2) == false) {
|
||||
this.settings.src2 = [this.settings.src2];
|
||||
}
|
||||
|
||||
// https://github.com/twitter/bootstrap/issues/2870
|
||||
this.transEndEventNames = {
|
||||
'WebkitTransition' : 'webkitTransitionEnd',
|
||||
'MozTransition' : 'transitionend',
|
||||
'OTransition' : 'oTransitionEnd',
|
||||
'msTransition' : 'MSTransitionEnd',
|
||||
'transition' : 'transitionend'
|
||||
};
|
||||
this.transEndEventName = this.transEndEventNames[ Modernizr.prefixed( 'transition' ) ];
|
||||
|
||||
// suport for css transforms and css transitions
|
||||
this.support = Modernizr.csstransitions && Modernizr.csstransforms;
|
||||
|
||||
// set inline CSS3 transition properties
|
||||
var transformPrefixed = Modernizr.prefixed('transform');
|
||||
transformPrefixed = transformPrefixed.replace(/([A-Z])/g, function(transformPrefixed,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-');
|
||||
this.transitionProp = {
|
||||
'transition': transformPrefixed+' '+this.settings.speed+'ms ease-out, opacity '+this.settings.switchSpeed+'ms'
|
||||
};
|
||||
|
||||
this.numSlides = this.settings.src.length;
|
||||
|
||||
// make sure the container is not [position: static]
|
||||
switch(this.$el.css('position')) {
|
||||
case 'relative':
|
||||
case 'absolute':
|
||||
case 'fixed':
|
||||
break;
|
||||
default:
|
||||
this.$el.css('position', 'relative');
|
||||
break;
|
||||
}
|
||||
|
||||
// make sure the first image has been loaded.
|
||||
var self = this;
|
||||
var $img = $('<img />');
|
||||
//$img.load( function() {
|
||||
if (self.numSlides == 1) {
|
||||
self.initSingle();
|
||||
} else {
|
||||
self.initSlideshow();
|
||||
}
|
||||
//});
|
||||
|
||||
$img.attr('src', this.settings.src[0]);
|
||||
},
|
||||
initSlideshow: function () {
|
||||
|
||||
var self = this;
|
||||
var $slideshow = $('<div class="zs-slideshow"></div>'),
|
||||
$slidesWrap = $('<div class="zs-slides"></div>'),
|
||||
$arrowsWrap = $('<div class="zs-arrows"></div>'),
|
||||
$zslayer = $('<div class="zs-layer"></div>'),
|
||||
$bulletsWrap = $('<div class="zs-bullets"></div>'),
|
||||
$ww = $(window).width();
|
||||
|
||||
for (i = 0; i < this.numSlides; i++) {
|
||||
|
||||
var $slide = $('<div class="zs-slide zs-slide-' + i + '"></div>');
|
||||
if ( $ww <= 767 && this.settings.src2[i].length ) {
|
||||
|
||||
$slide.css({ 'background-image': "url('" + this.settings.src2[i] + "')" }).appendTo( $slidesWrap );
|
||||
}
|
||||
else {
|
||||
|
||||
$slide.css({ 'background-image': "url('" + this.settings.src[i] + "')" }).appendTo( $slidesWrap );
|
||||
}
|
||||
|
||||
var $bullet = $('<div class="zs-bullet zs-bullet-' + i + '"></div>')
|
||||
$bullet.appendTo( $bulletsWrap );
|
||||
|
||||
if (i == 0) {
|
||||
|
||||
$slide.addClass('active').css('opacity', 1);
|
||||
$bullet.addClass('active');
|
||||
|
||||
$('.zs-enabled .lte-zs-slider-inner.lte-zs-slide-' + i).addClass('visible');
|
||||
}
|
||||
}
|
||||
|
||||
self._promoteChildren();
|
||||
|
||||
$slideshow.append( $zslayer );
|
||||
|
||||
$slideshow.append( $slidesWrap ).prependTo( this.$el );
|
||||
|
||||
if ( this.settings.bullets != false || this.settings.bullets == 'outside' ) {
|
||||
|
||||
if ( this.settings.bullets == 'outside' ) {
|
||||
|
||||
$slideshow.after( $bulletsWrap );
|
||||
}
|
||||
else {
|
||||
|
||||
$slideshow.append( $bulletsWrap );
|
||||
}
|
||||
|
||||
$slideshow.on('click', '.zs-bullet', function(e){
|
||||
self.jump( $(this).index() );
|
||||
});
|
||||
}
|
||||
|
||||
if ( this.settings.arrows == true || this.settings.arrows == 'right' || this.settings.arrows == 'bottom' ) {
|
||||
|
||||
var container_class = '';
|
||||
if ( this.settings.arrows == 'bottom' ) {
|
||||
|
||||
container_class = 'container';
|
||||
}
|
||||
|
||||
$('<div class="'+container_class+'"><span class="lte-arrow-left">'+this.settings.prev+'</span><span class="lte-arrow-right">'+this.settings.next+'</span></div>').appendTo( $arrowsWrap );
|
||||
this.$el.append( $arrowsWrap );
|
||||
|
||||
this.$el.on('click', '.lte-arrow-left', function(e){
|
||||
self.prev();
|
||||
});
|
||||
|
||||
this.$el.on('click', '.lte-arrow-right', function(e){
|
||||
self.next() ;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
this.pos = 0;
|
||||
this.pending = null;
|
||||
this.switching = false;
|
||||
this.$slideshow = $slideshow;
|
||||
this.$slides = $slidesWrap.children( '.zs-slide' );
|
||||
this.$bullets = $bulletsWrap.children( '.zs-bullet' );
|
||||
this.$el.addClass('zs-enabled');
|
||||
|
||||
var $firstBlock = $('.zs-enabled .lte-zs-slider-inner');
|
||||
|
||||
$('.zs-enabled .lte-zs-slider-inner').css('opacity', '');
|
||||
|
||||
var minHeight = 0;
|
||||
$('.lte-zs-slider-inner').each(function(i, el) {
|
||||
|
||||
if ( $(el).height() > minHeight ) {
|
||||
|
||||
minHeight = $(el).height();
|
||||
}
|
||||
});
|
||||
|
||||
if (this.support) {
|
||||
var $firstSlide = this.$slides.eq(0);
|
||||
var $initzoom = this.settings.initzoom;
|
||||
$firstSlide.css('opacity', 0).css( this.transitionProp );
|
||||
|
||||
$('.lte-zs-slider-wrapper').css('min-height', (minHeight ) + 'px' );
|
||||
$('.zs-slideshow').css('min-height', (minHeight - 2 ) + 'px' );
|
||||
jQuery(window).on('resize', function(){
|
||||
|
||||
var minHeight = 0;
|
||||
$('.lte-zs-slider-inner').each(function(i, el) {
|
||||
|
||||
if ( $(el).height() > minHeight ) {
|
||||
|
||||
minHeight = $(el).height();
|
||||
}
|
||||
});
|
||||
|
||||
$('.lte-zs-slider-wrapper').css('min-height', (minHeight ) + 'px' );
|
||||
$('.zs-slideshow').css('min-height', (minHeight - 2 ) + 'px' );
|
||||
});
|
||||
|
||||
setTimeout(function(){
|
||||
|
||||
$firstSlide.css( { 'opacity': 1.0, 'transform': 'scale('+ $initzoom +', '+ $initzoom +')', 'z-index': 2 } );
|
||||
}, 50);
|
||||
}
|
||||
|
||||
if (this.settings.autoplay == true) {
|
||||
this.play();
|
||||
}
|
||||
},
|
||||
initSingle: function() {
|
||||
var self = this;
|
||||
var $slideshow = $('<div class="zs-slideshow"></div>'),
|
||||
$slidesWrap = $('<div class="zs-slides"></div>'),
|
||||
$slide = $('<div class="zs-slide zs-slide-0"></div>');
|
||||
|
||||
$slide.css({ 'background-image': "url('" + this.settings.src[0] + "')" }).appendTo( $slidesWrap );
|
||||
$slide.addClass('active').css('opacity', 1);
|
||||
|
||||
$('.zs-enabled .lte-zs-slider-inner.lte-zs-slide-0').addClass('visible').addClass('single');
|
||||
|
||||
self._promoteChildren();
|
||||
|
||||
$slideshow.append( $slidesWrap ).prependTo( this.$el );
|
||||
this.$el.addClass('zs-enabled');
|
||||
|
||||
if (this.settings.overlay == 'dots') {
|
||||
this.$el.addClass('overlay-dots');
|
||||
} else if (this.settings.overlay == 'plain') {
|
||||
this.$el.addClass('overlay-plain')
|
||||
}
|
||||
|
||||
if (this.support) {
|
||||
$slide.css('opacity', 1).css( this.transitionProp );
|
||||
|
||||
if (this.settings) {
|
||||
|
||||
setTimeout(function(){
|
||||
$slide.css( { 'opacity': 1.0, 'transform': 'scale(1)', 'z-index': 2 } )
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
},
|
||||
_promoteChildren: function() {
|
||||
// make sure every children have high enough z-index
|
||||
this.$el.children().each(function(index){
|
||||
$this = $(this);
|
||||
if ($this.css('z-index') == 'auto') {
|
||||
$this.css('z-index', 2);
|
||||
}
|
||||
if ($this.css('position') == 'static') {
|
||||
$this.css('position', 'relative');
|
||||
}
|
||||
});
|
||||
},
|
||||
jump: function( pos ) {
|
||||
if ( pos >= this.numSlides ) {
|
||||
console.log('ZoomSlider: jump(pos) aborted. supplied index out of range.');
|
||||
return;
|
||||
}
|
||||
|
||||
if ( this.pos == pos ) return;
|
||||
|
||||
if ( this.switching ) {
|
||||
this.pending = pos;
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var $lastSlide = this.$slides.eq( this.pos );
|
||||
var $nowSlide = this.$slides.eq( pos );
|
||||
|
||||
$('.zs-enabled .lte-zoompages .current').html(pos + 1);
|
||||
$('.zs-enabled .lte-zs-slider-inner.visible').removeClass('visible');
|
||||
$('.zs-enabled .lte-zs-slider-inner.lte-zs-slide-' + pos).addClass('visible');
|
||||
|
||||
if ( this.support ) {
|
||||
|
||||
this.switching = true;
|
||||
$lastSlide.css('z-index', 1);
|
||||
$nowSlide.addClass('active')
|
||||
.css( this.transitionProp )
|
||||
.css( { 'opacity': 1.0, 'transform': 'scale('+this.settings.initzoom+', '+this.settings.initzoom+')', 'z-index': 2 } )
|
||||
.on( this.transEndEventName, function(e) {
|
||||
if (e.originalEvent.propertyName == 'opacity') {
|
||||
lastSlideBg = $lastSlide.css('background-image');
|
||||
$lastSlide.removeClass('active')
|
||||
.removeAttr('style')
|
||||
.css('background-image', lastSlideBg);
|
||||
$nowSlide.off( self.transEndEventName );
|
||||
self.switching = false;
|
||||
if ( self.pending != null ) {
|
||||
setTimeout(function(){
|
||||
var newPos = self.pending;
|
||||
self.pending = null;
|
||||
self.$bullets.eq(newPos).click();
|
||||
}, 30)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
$lastSlide.removeClass('active');
|
||||
$nowSlide.addClass('active');
|
||||
}
|
||||
this.$bullets.eq(this.pos).removeClass('active');
|
||||
this.$bullets.eq(pos).addClass('active');
|
||||
this.pos = pos;
|
||||
|
||||
if (this.settings.autoplay) {
|
||||
this.play();
|
||||
}
|
||||
},
|
||||
prev: function() {
|
||||
var posPrev = this.pos - 1;
|
||||
if (posPrev < 0) posPrev = this.numSlides - 1;
|
||||
this.jump( posPrev );
|
||||
},
|
||||
next: function() {
|
||||
var posNext = this.pos + 1;
|
||||
if (posNext >= this.numSlides) posNext = 0;
|
||||
this.jump( posNext );
|
||||
},
|
||||
play: function() {
|
||||
// clear any existing timer
|
||||
if (this.timer != null) {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
var self = this;
|
||||
this.settings.autoplay = true;
|
||||
// add timer
|
||||
|
||||
this.timer = setInterval( function(){
|
||||
self.next();
|
||||
}, this.settings.interval );
|
||||
},
|
||||
stop: function() {
|
||||
this.settings.autoplay = false;
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
});
|
||||
|
||||
// A really lightweight plugin wrapper around the constructor,
|
||||
// preventing against multiple instantiations
|
||||
$.fn[ pluginName ] = function ( options ) {
|
||||
return this.each(function() {
|
||||
if ( !$.data( this, "plugin_" + pluginName ) ) {
|
||||
$.data( this, "plugin_" + pluginName, new Plugin( this, options ) );
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var WidgetZoomsliderHandler = function ($scope, $) {
|
||||
|
||||
// auto create slideshow on [data-zs-enabled] instances.
|
||||
var $instances = $('[data-zs-src]');
|
||||
if ($instances.length > 0) {
|
||||
$instances.each( function(index) {
|
||||
var $this = $(this);
|
||||
$this.zoomSlider();
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$(window).on('elementor/frontend/init', function () {
|
||||
|
||||
elementorFrontend.hooks.addAction('frontend/element_ready/lte-zoomslider.default', WidgetZoomsliderHandler);
|
||||
});
|
||||
|
||||
})( jQuery, window, document );
|
||||
|
||||
+40
-40
@@ -1,41 +1,41 @@
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
// Get the buttons and the navbar element
|
||||
const openButton = document.getElementById('open-button');
|
||||
const closeButton = document.getElementById('close-button');
|
||||
const navbar = document.getElementById('navbar');
|
||||
|
||||
// Log to check if elements exist
|
||||
console.log('Open button:', openButton);
|
||||
console.log('Close button:', closeButton);
|
||||
console.log('Navbar:', navbar);
|
||||
|
||||
// Ensure that buttons and navbar exist
|
||||
if (openButton && closeButton && navbar) {
|
||||
console.log('Elements found and event listeners ready.');
|
||||
|
||||
// Add event listener to the open button to remove the collapse class
|
||||
openButton.addEventListener('click', function() {
|
||||
console.log('Open button clicked');
|
||||
if (navbar.classList.contains('collapse')) {
|
||||
navbar.classList.remove('collapse');
|
||||
console.log('Collapse class removed');
|
||||
} else {
|
||||
console.log('Navbar is already open (no collapse class).');
|
||||
}
|
||||
});
|
||||
|
||||
// Add event listener to the close button to add the collapse class
|
||||
closeButton.addEventListener('click', function() {
|
||||
console.log('Close button clicked');
|
||||
if (!navbar.classList.contains('collapse')) {
|
||||
navbar.classList.add('collapse');
|
||||
console.log('Collapse class added');
|
||||
} else {
|
||||
console.log('Navbar is already collapsed (collapse class exists).');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('Error: Buttons or navbar element not found.');
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
// Get the buttons and the navbar element
|
||||
const openButton = document.getElementById('open-button');
|
||||
const closeButton = document.getElementById('close-button');
|
||||
const navbar = document.getElementById('navbar');
|
||||
|
||||
// Log to check if elements exist
|
||||
console.log('Open button:', openButton);
|
||||
console.log('Close button:', closeButton);
|
||||
console.log('Navbar:', navbar);
|
||||
|
||||
// Ensure that buttons and navbar exist
|
||||
if (openButton && closeButton && navbar) {
|
||||
console.log('Elements found and event listeners ready.');
|
||||
|
||||
// Add event listener to the open button to remove the collapse class
|
||||
openButton.addEventListener('click', function() {
|
||||
console.log('Open button clicked');
|
||||
if (navbar.classList.contains('collapse')) {
|
||||
navbar.classList.remove('collapse');
|
||||
console.log('Collapse class removed');
|
||||
} else {
|
||||
console.log('Navbar is already open (no collapse class).');
|
||||
}
|
||||
});
|
||||
|
||||
// Add event listener to the close button to add the collapse class
|
||||
closeButton.addEventListener('click', function() {
|
||||
console.log('Close button clicked');
|
||||
if (!navbar.classList.contains('collapse')) {
|
||||
navbar.classList.add('collapse');
|
||||
console.log('Collapse class added');
|
||||
} else {
|
||||
console.log('Navbar is already collapsed (collapse class exists).');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('Error: Buttons or navbar element not found.');
|
||||
}
|
||||
});
|
||||
Vendored
+12
-12
File diff suppressed because one or more lines are too long
+328
-328
@@ -1,328 +1,328 @@
|
||||
// team-switcher.js
|
||||
// Loads team data from XML and populates the team slider. Adds a men/women switcher.
|
||||
|
||||
(function () {
|
||||
const XML_URL = 'data/team.xml';
|
||||
const SWITCHER_ID = 'gender-switcher';
|
||||
const WRAPPER_ID = 'team-swiper-wrapper-1';
|
||||
const PRELOADER_ID = 'team-preloader-1';
|
||||
const SECTION_ID = 'team-section-1';
|
||||
|
||||
let teamData = null; // cached parsed XML data
|
||||
let currentGender = 'men';
|
||||
// Autoscroll timers
|
||||
let autoTimer = null;
|
||||
let resumeTimer = null;
|
||||
const AUTO_DELAY = 3500; // 5s
|
||||
const RESUME_AFTER = 10000; // resume 10s after user interaction
|
||||
|
||||
function qs(sel, root = document) { return root.querySelector(sel); }
|
||||
function qsa(sel, root = document) { return Array.from(root.querySelectorAll(sel)); }
|
||||
|
||||
async function loadXML() {
|
||||
if (teamData) return teamData;
|
||||
const res = await fetch(XML_URL, { cache: 'no-cache' });
|
||||
if (!res.ok) throw new Error('Failed to fetch team.xml');
|
||||
const text = await res.text();
|
||||
const parser = new DOMParser();
|
||||
const xml = parser.parseFromString(text, 'application/xml');
|
||||
const parseError = xml.querySelector('parsererror');
|
||||
if (parseError) throw new Error('Invalid XML in team.xml');
|
||||
teamData = xml;
|
||||
return xml;
|
||||
}
|
||||
|
||||
function getMembersByCategory(xml, categoryName) {
|
||||
const cat = Array.from(xml.querySelectorAll('team > category'))
|
||||
.find(c => (c.getAttribute('name') || '').toLowerCase() === categoryName);
|
||||
if (!cat) return [];
|
||||
return Array.from(cat.querySelectorAll('member')).map(m => ({
|
||||
name: (m.querySelector('name')?.textContent || '').trim(),
|
||||
number: (m.querySelector('number')?.textContent || '').trim(),
|
||||
role: (m.querySelector('role')?.textContent || '').trim(),
|
||||
image: (m.querySelector('image')?.textContent || '').trim(),
|
||||
}));
|
||||
}
|
||||
|
||||
function slideHTML(member) {
|
||||
const numHTML = member.number ? `<div class="lte-num">${member.number}</div>` : '<div class="lte-num"></div>';
|
||||
const safeImg = member.image || '';
|
||||
return (
|
||||
`<div class="lte-item swiper-slide">
|
||||
<div class="lte-team-item">
|
||||
<a class="lte-image" style="background-image: url()">
|
||||
<img loading="lazy" decoding="async" width="800" height="1200" src="${safeImg}" class="attachment-full size-full" />
|
||||
</a>
|
||||
<div class="lte-descr">
|
||||
${numHTML}
|
||||
<a href="${safeImg}" target="_blank">
|
||||
<h4 class="lte-header">${member.name}</h4>
|
||||
</a>
|
||||
<p class="lte-subheader" style="color: #c42221">${member.role}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
);
|
||||
}
|
||||
|
||||
function renderMembers(members) {
|
||||
const wrapper = document.getElementById(WRAPPER_ID);
|
||||
if (!wrapper) return;
|
||||
const swiperEl = wrapper.closest('.swiper-container');
|
||||
const swiper = swiperEl && swiperEl.swiper;
|
||||
|
||||
// Use DOM-based rendering to match theme's slider expectations
|
||||
wrapper.innerHTML = members.map(slideHTML).join('');
|
||||
|
||||
// Strong refresh
|
||||
if (swiper) {
|
||||
try {
|
||||
if (typeof swiper.updateSlides === 'function') swiper.updateSlides();
|
||||
if (typeof swiper.updateSize === 'function') swiper.updateSize();
|
||||
if (typeof swiper.updateAutoHeight === 'function') swiper.updateAutoHeight(0);
|
||||
if (typeof swiper.slideTo === 'function') swiper.slideTo(0, 0, false);
|
||||
if (typeof swiper.update === 'function') swiper.update();
|
||||
} catch (e) {}
|
||||
}
|
||||
// Ask the theme to re-init this slider completely so arrows/loop/order are consistent
|
||||
const sliderContainer = wrapper.closest('.lte-swiper-slider');
|
||||
if (sliderContainer) sliderContainer.classList.remove('lte-inited');
|
||||
if (typeof window.initSwiperWrappers === 'function') {
|
||||
try { window.initSwiperWrappers(); } catch (_) {}
|
||||
}
|
||||
// Remove any duplicate arrow bars the theme may have added on re-init
|
||||
cleanupDuplicateArrows();
|
||||
setTimeout(() => window.dispatchEvent(new Event('resize')), 0);
|
||||
|
||||
// Ensure arrows exist and are bound; manual endless wrap
|
||||
setupEndlessNavigation(swiperEl);
|
||||
setupDragWrap(swiper);
|
||||
// Restart autoscroll on fresh render
|
||||
stopAutoScroll();
|
||||
startAutoScroll();
|
||||
}
|
||||
|
||||
// Keep only one arrows bar; prefer the one whose anchors already have our data-ts-bound
|
||||
function cleanupDuplicateArrows() {
|
||||
const wrapper = document.getElementById(WRAPPER_ID);
|
||||
if (!wrapper) return;
|
||||
const slider = wrapper.closest('.lte-swiper-slider');
|
||||
if (!slider) return;
|
||||
|
||||
// Arrows can be siblings of slider or children inside slider depending on theme config
|
||||
const candidates = [];
|
||||
const parent = slider.parentElement;
|
||||
if (parent) {
|
||||
Array.from(parent.children).forEach((el) => { if (el.classList && el.classList.contains('lte-arrows')) candidates.push(el); });
|
||||
}
|
||||
Array.from(slider.children).forEach((el) => { if (el.classList && el.classList.contains('lte-arrows')) candidates.push(el); });
|
||||
|
||||
if (candidates.length <= 1) return;
|
||||
|
||||
// Prefer the one that already has data-ts-bound anchors
|
||||
const hasBound = candidates.find(a => a.querySelector('a[data-ts-bound="1"]'));
|
||||
const keep = hasBound || candidates[0];
|
||||
candidates.forEach((a) => { if (a !== keep && a.parentElement) a.parentElement.removeChild(a); });
|
||||
}
|
||||
|
||||
async function switchGender(gender) {
|
||||
currentGender = gender;
|
||||
try {
|
||||
showPreloader();
|
||||
const xml = await loadXML();
|
||||
// Keep the order as in XML so the first visible is the first listed (e.g., Janečka Martin)
|
||||
const list = getMembersByCategory(xml, gender);
|
||||
renderMembers(list);
|
||||
updateActiveButton();
|
||||
markReady();
|
||||
hidePreloader();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
hidePreloader();
|
||||
}
|
||||
}
|
||||
|
||||
function updateActiveButton() {
|
||||
const container = document.getElementById(SWITCHER_ID);
|
||||
if (!container) return;
|
||||
qsa('button[data-gender]', container).forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.gender === currentGender);
|
||||
});
|
||||
}
|
||||
|
||||
function bindUI() {
|
||||
const container = document.getElementById(SWITCHER_ID);
|
||||
if (!container) return;
|
||||
container.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('button[data-gender]');
|
||||
if (!btn) return;
|
||||
const gender = btn.dataset.gender;
|
||||
if (gender && gender !== currentGender) switchGender(gender);
|
||||
});
|
||||
}
|
||||
|
||||
function ensureBasicStyles() {
|
||||
const css = `
|
||||
#${SWITCHER_ID}{display:flex;gap:.5rem;justify-content:center;margin:10px 0}
|
||||
#${SWITCHER_ID} .switch-btn{background:#eee;border:1px solid #ccc;border-radius:20px;padding:.35rem .9rem;font-weight:600;cursor:pointer}
|
||||
#${SWITCHER_ID} .switch-btn.active{background:#111;color:#fff;border-color:#111}
|
||||
#${PRELOADER_ID}{display:none;align-items:center;justify-content:center;gap:.6rem;color:#fff;padding:8px 0}
|
||||
#${PRELOADER_ID}.visible{display:flex}
|
||||
#${PRELOADER_ID} .spinner{width:16px;height:16px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:ts-spin .8s linear infinite}
|
||||
@keyframes ts-spin{to{transform:rotate(360deg)}}
|
||||
#${SECTION_ID}.not-ready .lte-swiper-slider-wrapper{visibility:hidden}
|
||||
`;
|
||||
const style = document.createElement('style');
|
||||
style.textContent = css;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
function getSwiperInstance() {
|
||||
const wrapper = document.getElementById(WRAPPER_ID);
|
||||
const swiperEl = wrapper && wrapper.closest('.swiper-container');
|
||||
return swiperEl && swiperEl.swiper ? { el: swiperEl, api: swiperEl.swiper } : null;
|
||||
}
|
||||
|
||||
function setupEndlessNavigation(swiperContainerEl) {
|
||||
const inst = getSwiperInstance();
|
||||
if (!inst) return;
|
||||
const { el, api } = inst;
|
||||
|
||||
// Ensure only one set of arrows remains before binding
|
||||
cleanupDuplicateArrows();
|
||||
|
||||
// Theme uses .lte-arrow-left / .lte-arrow-right (see frontend.js init)
|
||||
let nextBtn = el.parentElement && el.parentElement.querySelector('.lte-arrows .lte-arrow-right');
|
||||
let prevBtn = el.parentElement && el.parentElement.querySelector('.lte-arrows .lte-arrow-left');
|
||||
// Fallback to common Swiper classes if theme structure changes
|
||||
if (!nextBtn) nextBtn = el.querySelector('.swiper-button-next, .lte-swiper-button-next, .lte-next, .lte-arrow-next, .lte-arrow-right');
|
||||
if (!prevBtn) prevBtn = el.querySelector('.swiper-button-prev, .lte-swiper-button-prev, .lte-prev, .lte-arrow-prev, .lte-arrow-left');
|
||||
|
||||
// Do not create fallback arrows; rely on theme arrows only
|
||||
|
||||
function bind(btn, dir) {
|
||||
if (!btn || btn.dataset.tsBound) return;
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
if (!api) return;
|
||||
// User override: pause and schedule resume
|
||||
stopAutoScroll();
|
||||
scheduleAutoResume();
|
||||
if (dir === 'next') {
|
||||
if (typeof api.slideNext === 'function') api.slideNext(400);
|
||||
else api.slideTo((api.activeIndex || 0) + 1, 400, false);
|
||||
} else {
|
||||
if (typeof api.slidePrev === 'function') api.slidePrev(400);
|
||||
else api.slideTo(Math.max((api.activeIndex || 0) - 1, 0), 400, false);
|
||||
}
|
||||
});
|
||||
btn.dataset.tsBound = '1';
|
||||
}
|
||||
|
||||
bind(nextBtn, 'next');
|
||||
bind(prevBtn, 'prev');
|
||||
|
||||
// Hover pause/resume on the whole slider area
|
||||
if (el && !el.__tsHoverBound) {
|
||||
el.addEventListener('mouseenter', () => stopAutoScroll());
|
||||
el.addEventListener('mouseleave', () => startAutoScroll());
|
||||
el.__tsHoverBound = true;
|
||||
}
|
||||
}
|
||||
|
||||
function setupDragWrap(swiper) {
|
||||
if (!swiper || !swiper.on) return;
|
||||
if (!swiper.__tsWrapBound) {
|
||||
swiper.on('reachEnd', () => { swiper.slideTo(0, 400, false); });
|
||||
swiper.on('reachBeginning', () => {
|
||||
const last = (swiper.slides && swiper.slides.length ? swiper.slides.length - 1 : 0);
|
||||
swiper.slideTo(last, 400, false);
|
||||
});
|
||||
swiper.__tsWrapBound = true;
|
||||
}
|
||||
|
||||
// Pause autoscroll on user touch/drag and schedule resume on release
|
||||
if (!swiper.__tsAutoBound) {
|
||||
try {
|
||||
swiper.on('touchStart', () => { stopAutoScroll(); });
|
||||
swiper.on('touchEnd', () => { scheduleAutoResume(); });
|
||||
swiper.on('pointerDown', () => { stopAutoScroll(); });
|
||||
swiper.on('pointerUp', () => { scheduleAutoResume(); });
|
||||
} catch (_) {}
|
||||
swiper.__tsAutoBound = true;
|
||||
}
|
||||
}
|
||||
|
||||
function startAutoScroll() {
|
||||
const inst = getSwiperInstance();
|
||||
if (!inst) return;
|
||||
const { api } = inst;
|
||||
stopAutoScroll();
|
||||
autoTimer = window.setInterval(() => {
|
||||
if (!api) return;
|
||||
try {
|
||||
// If not looping, wrap to first when at end
|
||||
const loop = api.params && api.params.loop;
|
||||
if (!loop && api.isEnd) {
|
||||
api.slideTo(0, 600, false);
|
||||
} else if (typeof api.slideNext === 'function') {
|
||||
api.slideNext(600);
|
||||
}
|
||||
} catch (_) {}
|
||||
}, AUTO_DELAY);
|
||||
}
|
||||
|
||||
function stopAutoScroll() {
|
||||
if (autoTimer) {
|
||||
clearInterval(autoTimer);
|
||||
autoTimer = null;
|
||||
}
|
||||
if (resumeTimer) {
|
||||
clearTimeout(resumeTimer);
|
||||
resumeTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleAutoResume() {
|
||||
if (resumeTimer) {
|
||||
clearTimeout(resumeTimer);
|
||||
resumeTimer = null;
|
||||
}
|
||||
resumeTimer = window.setTimeout(() => {
|
||||
startAutoScroll();
|
||||
}, RESUME_AFTER);
|
||||
}
|
||||
|
||||
function showPreloader() {
|
||||
const el = document.getElementById(PRELOADER_ID);
|
||||
if (el) el.classList.add('visible');
|
||||
}
|
||||
|
||||
function hidePreloader() {
|
||||
const el = document.getElementById(PRELOADER_ID);
|
||||
if (el) el.classList.remove('visible');
|
||||
}
|
||||
|
||||
function markNotReady() {
|
||||
const sec = document.getElementById(SECTION_ID);
|
||||
if (sec) sec.classList.add('not-ready');
|
||||
}
|
||||
|
||||
function markReady() {
|
||||
const sec = document.getElementById(SECTION_ID);
|
||||
if (sec) sec.classList.remove('not-ready');
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
ensureBasicStyles();
|
||||
markNotReady();
|
||||
showPreloader();
|
||||
bindUI();
|
||||
});
|
||||
|
||||
// Defer initial population until all assets and theme scripts (e.g., sliders) are fully initialized
|
||||
window.addEventListener('load', () => {
|
||||
switchGender(currentGender);
|
||||
});
|
||||
})();
|
||||
// team-switcher.js
|
||||
// Loads team data from XML and populates the team slider. Adds a men/women switcher.
|
||||
|
||||
(function () {
|
||||
const XML_URL = 'data/team.xml';
|
||||
const SWITCHER_ID = 'gender-switcher';
|
||||
const WRAPPER_ID = 'team-swiper-wrapper-1';
|
||||
const PRELOADER_ID = 'team-preloader-1';
|
||||
const SECTION_ID = 'team-section-1';
|
||||
|
||||
let teamData = null; // cached parsed XML data
|
||||
let currentGender = 'men';
|
||||
// Autoscroll timers
|
||||
let autoTimer = null;
|
||||
let resumeTimer = null;
|
||||
const AUTO_DELAY = 3500; // 5s
|
||||
const RESUME_AFTER = 10000; // resume 10s after user interaction
|
||||
|
||||
function qs(sel, root = document) { return root.querySelector(sel); }
|
||||
function qsa(sel, root = document) { return Array.from(root.querySelectorAll(sel)); }
|
||||
|
||||
async function loadXML() {
|
||||
if (teamData) return teamData;
|
||||
const res = await fetch(XML_URL, { cache: 'no-cache' });
|
||||
if (!res.ok) throw new Error('Failed to fetch team.xml');
|
||||
const text = await res.text();
|
||||
const parser = new DOMParser();
|
||||
const xml = parser.parseFromString(text, 'application/xml');
|
||||
const parseError = xml.querySelector('parsererror');
|
||||
if (parseError) throw new Error('Invalid XML in team.xml');
|
||||
teamData = xml;
|
||||
return xml;
|
||||
}
|
||||
|
||||
function getMembersByCategory(xml, categoryName) {
|
||||
const cat = Array.from(xml.querySelectorAll('team > category'))
|
||||
.find(c => (c.getAttribute('name') || '').toLowerCase() === categoryName);
|
||||
if (!cat) return [];
|
||||
return Array.from(cat.querySelectorAll('member')).map(m => ({
|
||||
name: (m.querySelector('name')?.textContent || '').trim(),
|
||||
number: (m.querySelector('number')?.textContent || '').trim(),
|
||||
role: (m.querySelector('role')?.textContent || '').trim(),
|
||||
image: (m.querySelector('image')?.textContent || '').trim(),
|
||||
}));
|
||||
}
|
||||
|
||||
function slideHTML(member) {
|
||||
const numHTML = member.number ? `<div class="lte-num">${member.number}</div>` : '<div class="lte-num"></div>';
|
||||
const safeImg = member.image || '';
|
||||
return (
|
||||
`<div class="lte-item swiper-slide">
|
||||
<div class="lte-team-item">
|
||||
<a class="lte-image" style="background-image: url()">
|
||||
<img loading="lazy" decoding="async" width="800" height="1200" src="${safeImg}" class="attachment-full size-full" />
|
||||
</a>
|
||||
<div class="lte-descr">
|
||||
${numHTML}
|
||||
<a href="${safeImg}" target="_blank">
|
||||
<h4 class="lte-header">${member.name}</h4>
|
||||
</a>
|
||||
<p class="lte-subheader" style="color: #c42221">${member.role}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
);
|
||||
}
|
||||
|
||||
function renderMembers(members) {
|
||||
const wrapper = document.getElementById(WRAPPER_ID);
|
||||
if (!wrapper) return;
|
||||
const swiperEl = wrapper.closest('.swiper-container');
|
||||
const swiper = swiperEl && swiperEl.swiper;
|
||||
|
||||
// Use DOM-based rendering to match theme's slider expectations
|
||||
wrapper.innerHTML = members.map(slideHTML).join('');
|
||||
|
||||
// Strong refresh
|
||||
if (swiper) {
|
||||
try {
|
||||
if (typeof swiper.updateSlides === 'function') swiper.updateSlides();
|
||||
if (typeof swiper.updateSize === 'function') swiper.updateSize();
|
||||
if (typeof swiper.updateAutoHeight === 'function') swiper.updateAutoHeight(0);
|
||||
if (typeof swiper.slideTo === 'function') swiper.slideTo(0, 0, false);
|
||||
if (typeof swiper.update === 'function') swiper.update();
|
||||
} catch (e) {}
|
||||
}
|
||||
// Ask the theme to re-init this slider completely so arrows/loop/order are consistent
|
||||
const sliderContainer = wrapper.closest('.lte-swiper-slider');
|
||||
if (sliderContainer) sliderContainer.classList.remove('lte-inited');
|
||||
if (typeof window.initSwiperWrappers === 'function') {
|
||||
try { window.initSwiperWrappers(); } catch (_) {}
|
||||
}
|
||||
// Remove any duplicate arrow bars the theme may have added on re-init
|
||||
cleanupDuplicateArrows();
|
||||
setTimeout(() => window.dispatchEvent(new Event('resize')), 0);
|
||||
|
||||
// Ensure arrows exist and are bound; manual endless wrap
|
||||
setupEndlessNavigation(swiperEl);
|
||||
setupDragWrap(swiper);
|
||||
// Restart autoscroll on fresh render
|
||||
stopAutoScroll();
|
||||
startAutoScroll();
|
||||
}
|
||||
|
||||
// Keep only one arrows bar; prefer the one whose anchors already have our data-ts-bound
|
||||
function cleanupDuplicateArrows() {
|
||||
const wrapper = document.getElementById(WRAPPER_ID);
|
||||
if (!wrapper) return;
|
||||
const slider = wrapper.closest('.lte-swiper-slider');
|
||||
if (!slider) return;
|
||||
|
||||
// Arrows can be siblings of slider or children inside slider depending on theme config
|
||||
const candidates = [];
|
||||
const parent = slider.parentElement;
|
||||
if (parent) {
|
||||
Array.from(parent.children).forEach((el) => { if (el.classList && el.classList.contains('lte-arrows')) candidates.push(el); });
|
||||
}
|
||||
Array.from(slider.children).forEach((el) => { if (el.classList && el.classList.contains('lte-arrows')) candidates.push(el); });
|
||||
|
||||
if (candidates.length <= 1) return;
|
||||
|
||||
// Prefer the one that already has data-ts-bound anchors
|
||||
const hasBound = candidates.find(a => a.querySelector('a[data-ts-bound="1"]'));
|
||||
const keep = hasBound || candidates[0];
|
||||
candidates.forEach((a) => { if (a !== keep && a.parentElement) a.parentElement.removeChild(a); });
|
||||
}
|
||||
|
||||
async function switchGender(gender) {
|
||||
currentGender = gender;
|
||||
try {
|
||||
showPreloader();
|
||||
const xml = await loadXML();
|
||||
// Keep the order as in XML so the first visible is the first listed (e.g., Janečka Martin)
|
||||
const list = getMembersByCategory(xml, gender);
|
||||
renderMembers(list);
|
||||
updateActiveButton();
|
||||
markReady();
|
||||
hidePreloader();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
hidePreloader();
|
||||
}
|
||||
}
|
||||
|
||||
function updateActiveButton() {
|
||||
const container = document.getElementById(SWITCHER_ID);
|
||||
if (!container) return;
|
||||
qsa('button[data-gender]', container).forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.gender === currentGender);
|
||||
});
|
||||
}
|
||||
|
||||
function bindUI() {
|
||||
const container = document.getElementById(SWITCHER_ID);
|
||||
if (!container) return;
|
||||
container.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('button[data-gender]');
|
||||
if (!btn) return;
|
||||
const gender = btn.dataset.gender;
|
||||
if (gender && gender !== currentGender) switchGender(gender);
|
||||
});
|
||||
}
|
||||
|
||||
function ensureBasicStyles() {
|
||||
const css = `
|
||||
#${SWITCHER_ID}{display:flex;gap:.5rem;justify-content:center;margin:10px 0}
|
||||
#${SWITCHER_ID} .switch-btn{background:#eee;border:1px solid #ccc;border-radius:20px;padding:.35rem .9rem;font-weight:600;cursor:pointer}
|
||||
#${SWITCHER_ID} .switch-btn.active{background:#111;color:#fff;border-color:#111}
|
||||
#${PRELOADER_ID}{display:none;align-items:center;justify-content:center;gap:.6rem;color:#fff;padding:8px 0}
|
||||
#${PRELOADER_ID}.visible{display:flex}
|
||||
#${PRELOADER_ID} .spinner{width:16px;height:16px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:ts-spin .8s linear infinite}
|
||||
@keyframes ts-spin{to{transform:rotate(360deg)}}
|
||||
#${SECTION_ID}.not-ready .lte-swiper-slider-wrapper{visibility:hidden}
|
||||
`;
|
||||
const style = document.createElement('style');
|
||||
style.textContent = css;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
function getSwiperInstance() {
|
||||
const wrapper = document.getElementById(WRAPPER_ID);
|
||||
const swiperEl = wrapper && wrapper.closest('.swiper-container');
|
||||
return swiperEl && swiperEl.swiper ? { el: swiperEl, api: swiperEl.swiper } : null;
|
||||
}
|
||||
|
||||
function setupEndlessNavigation(swiperContainerEl) {
|
||||
const inst = getSwiperInstance();
|
||||
if (!inst) return;
|
||||
const { el, api } = inst;
|
||||
|
||||
// Ensure only one set of arrows remains before binding
|
||||
cleanupDuplicateArrows();
|
||||
|
||||
// Theme uses .lte-arrow-left / .lte-arrow-right (see frontend.js init)
|
||||
let nextBtn = el.parentElement && el.parentElement.querySelector('.lte-arrows .lte-arrow-right');
|
||||
let prevBtn = el.parentElement && el.parentElement.querySelector('.lte-arrows .lte-arrow-left');
|
||||
// Fallback to common Swiper classes if theme structure changes
|
||||
if (!nextBtn) nextBtn = el.querySelector('.swiper-button-next, .lte-swiper-button-next, .lte-next, .lte-arrow-next, .lte-arrow-right');
|
||||
if (!prevBtn) prevBtn = el.querySelector('.swiper-button-prev, .lte-swiper-button-prev, .lte-prev, .lte-arrow-prev, .lte-arrow-left');
|
||||
|
||||
// Do not create fallback arrows; rely on theme arrows only
|
||||
|
||||
function bind(btn, dir) {
|
||||
if (!btn || btn.dataset.tsBound) return;
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
if (!api) return;
|
||||
// User override: pause and schedule resume
|
||||
stopAutoScroll();
|
||||
scheduleAutoResume();
|
||||
if (dir === 'next') {
|
||||
if (typeof api.slideNext === 'function') api.slideNext(400);
|
||||
else api.slideTo((api.activeIndex || 0) + 1, 400, false);
|
||||
} else {
|
||||
if (typeof api.slidePrev === 'function') api.slidePrev(400);
|
||||
else api.slideTo(Math.max((api.activeIndex || 0) - 1, 0), 400, false);
|
||||
}
|
||||
});
|
||||
btn.dataset.tsBound = '1';
|
||||
}
|
||||
|
||||
bind(nextBtn, 'next');
|
||||
bind(prevBtn, 'prev');
|
||||
|
||||
// Hover pause/resume on the whole slider area
|
||||
if (el && !el.__tsHoverBound) {
|
||||
el.addEventListener('mouseenter', () => stopAutoScroll());
|
||||
el.addEventListener('mouseleave', () => startAutoScroll());
|
||||
el.__tsHoverBound = true;
|
||||
}
|
||||
}
|
||||
|
||||
function setupDragWrap(swiper) {
|
||||
if (!swiper || !swiper.on) return;
|
||||
if (!swiper.__tsWrapBound) {
|
||||
swiper.on('reachEnd', () => { swiper.slideTo(0, 400, false); });
|
||||
swiper.on('reachBeginning', () => {
|
||||
const last = (swiper.slides && swiper.slides.length ? swiper.slides.length - 1 : 0);
|
||||
swiper.slideTo(last, 400, false);
|
||||
});
|
||||
swiper.__tsWrapBound = true;
|
||||
}
|
||||
|
||||
// Pause autoscroll on user touch/drag and schedule resume on release
|
||||
if (!swiper.__tsAutoBound) {
|
||||
try {
|
||||
swiper.on('touchStart', () => { stopAutoScroll(); });
|
||||
swiper.on('touchEnd', () => { scheduleAutoResume(); });
|
||||
swiper.on('pointerDown', () => { stopAutoScroll(); });
|
||||
swiper.on('pointerUp', () => { scheduleAutoResume(); });
|
||||
} catch (_) {}
|
||||
swiper.__tsAutoBound = true;
|
||||
}
|
||||
}
|
||||
|
||||
function startAutoScroll() {
|
||||
const inst = getSwiperInstance();
|
||||
if (!inst) return;
|
||||
const { api } = inst;
|
||||
stopAutoScroll();
|
||||
autoTimer = window.setInterval(() => {
|
||||
if (!api) return;
|
||||
try {
|
||||
// If not looping, wrap to first when at end
|
||||
const loop = api.params && api.params.loop;
|
||||
if (!loop && api.isEnd) {
|
||||
api.slideTo(0, 600, false);
|
||||
} else if (typeof api.slideNext === 'function') {
|
||||
api.slideNext(600);
|
||||
}
|
||||
} catch (_) {}
|
||||
}, AUTO_DELAY);
|
||||
}
|
||||
|
||||
function stopAutoScroll() {
|
||||
if (autoTimer) {
|
||||
clearInterval(autoTimer);
|
||||
autoTimer = null;
|
||||
}
|
||||
if (resumeTimer) {
|
||||
clearTimeout(resumeTimer);
|
||||
resumeTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleAutoResume() {
|
||||
if (resumeTimer) {
|
||||
clearTimeout(resumeTimer);
|
||||
resumeTimer = null;
|
||||
}
|
||||
resumeTimer = window.setTimeout(() => {
|
||||
startAutoScroll();
|
||||
}, RESUME_AFTER);
|
||||
}
|
||||
|
||||
function showPreloader() {
|
||||
const el = document.getElementById(PRELOADER_ID);
|
||||
if (el) el.classList.add('visible');
|
||||
}
|
||||
|
||||
function hidePreloader() {
|
||||
const el = document.getElementById(PRELOADER_ID);
|
||||
if (el) el.classList.remove('visible');
|
||||
}
|
||||
|
||||
function markNotReady() {
|
||||
const sec = document.getElementById(SECTION_ID);
|
||||
if (sec) sec.classList.add('not-ready');
|
||||
}
|
||||
|
||||
function markReady() {
|
||||
const sec = document.getElementById(SECTION_ID);
|
||||
if (sec) sec.classList.remove('not-ready');
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
ensureBasicStyles();
|
||||
markNotReady();
|
||||
showPreloader();
|
||||
bindUI();
|
||||
});
|
||||
|
||||
// Defer initial population until all assets and theme scripts (e.g., sliders) are fully initialized
|
||||
window.addEventListener('load', () => {
|
||||
switchGender(currentGender);
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user