(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);
const dt = new Date(Date.UTC(year, month-1, day, hour, minute));
// adjust to Prague timezone visually
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; }
@media (max-width: 480px){
#facr-mid{ font-size:28px !important; min-width:100px; }
.facr-tab{ padding:4px 8px; font-size:14px; }
.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, ''');
}
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;
// pick next upcoming if available; otherwise latest recent within window
let pick = candidates.find(x=> x.dt >= now);
if(!pick) pick = 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 = '
';
return;
}
// Prefer first future match; if none, show the latest recent within 3 days
const now = new Date();
let autoIdx = items.findIndex(it => it.dt >= now);
if(autoIdx === -1) autoIdx = items.length - 1;
const idx = Math.min(state.matchIndex || autoIdx, 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 = (!isToday && diffMsPre > 0) ? `Za ${fmtCountdownLong(diffMsPre)}` : (m.score || '-');
root.innerHTML = `
`;
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');
if(cd){
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;
if(diff > 0){
// Always show a visible countdown, including on non-today future matches
cd.textContent = `Začátek za ${fmtCountdown(diff)}`;
}else if(Math.abs(diff) <= twoH){
cd.textContent = 'Právě probíhá';
}else if(-diff < threeD){
cd.textContent = (m.score ? `Výsledek: ${m.score}` : 'Ukončeno');
}else{
cd.textContent = '';
}
}
// 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){
midEl.textContent = `Za ${fmtCountdownLong(diff)}`;
}else{
// switch to score at/after kickoff
midEl.textContent = m.score || '-';
if(state.upcomingTimerId){ clearInterval(state.upcomingTimerId); state.upcomingTimerId = null; }
}
}
// Only run live countdown if the match is in the future and not today
if(startTime > Date.now() && !isToday){
tick();
state.upcomingTimerId = setInterval(tick, 1000);
}
}
}
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)=>
``
).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=>`
| ${r.rank} |
 |
${r.team} |
${r.played} |
${r.wins} |
${r.draws} |
${r.losses} |
${r.score} |
${r.points} |
`).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 `
${s1}
${s1}
:
${s2}
${s2}
`;
}).join('');
sections.push(`
`);
}
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);
})();