// СпортПортал — данные и логика (мок-данные, фаза 1) const SPORTS = [ { icon: "trophy", name: "Футбол", desc: "Премьер-лига, Кубок, трансферы и лучшие моменты.", chip: "12 матчей сегодня", }, { icon: "dashboard", name: "Теннис", desc: "Туры ATP/WTA, турниры Большой четвёрки, рейтинги.", chip: "Полуфиналы турнира", }, { icon: "chart", name: "Хоккей", desc: "КХЛ, НХЛ: очки, шайбы и гонка бомбардиров.", chip: "Турнирная таблица КХЛ", }, { icon: "users", name: "Бокс", desc: "Большие бои, рейтинги и история рекордов.", chip: "Пояс WBA в игре", }, ]; // Демо-данные: работают только если API не доступно (например, в Предпросмотре без сервера) const LEAGUES = { premier: { label: "АПЛ, Англия", cols: ["И", "В", "Н", "П", "ГЗ", "ГП"], rows: [ ["Арсенал", 38, 24, 10, 4, 78, 32], ["Ливерпуль", 38, 23, 11, 4, 85, 34], ["Манчестер Сити", 38, 22, 8, 8, 80, 41], ["Челси", 38, 20, 8, 10, 64, 42], ["Лестер", 38, 19, 7, 12, 58, 44], ["Тоттенхэм", 38, 18, 7, 13, 54, 44], ], }, laliga: { label: "Ла Лига, Испания", cols: ["И", "В", "Н", "П", "ГЗ", "ГП"], rows: [ ["Реал Мадрид", 38, 27, 4, 7, 78, 33], ["Барселона", 38, 26, 5, 7, 87, 38], ["Атлетико", 38, 21, 9, 8, 64, 35], ["Атлетик", 38, 17, 11, 10, 47, 36], ["Райо", 38, 15, 10, 13, 44, 45], ["Вильярреал", 38, 14, 12, 12, 55, 43], ], }, khl: { label: "КХЛ", cols: ["И", "В", "Н", "П", "ШЗ", "ШП"], rows: [ ["СКА", 60, 42, 6, 12, 180, 98], ["ЦСКА", 60, 40, 6, 14, 172, 101], ["Ак Барс", 60, 38, 5, 17, 160, 97], ["Локомотив", 60, 35, 6, 19, 148, 104], ["Металлург", 60, 32, 5, 23, 137, 115], ["Авангард", 60, 30, 5, 25, 140, 121], ], }, nhl: { label: "НХЛ", cols: ["И", "В", "Н", "П", "ШЗ", "ШП"], rows: [ ["Эдмонтон", 82, 48, 10, 24, 290, 238], ["Виннипег", 82, 46, 10, 26, 271, 227], ["Филадельфия", 82, 44, 10, 28, 263, 237], ["Даллас", 82, 44, 0, 38, 270, 215], ["Колорадо", 82, 42, 8, 32, 286, 235], ["Нэшвилл", 82, 41, 8, 33, 265, 240], ], }, }; const MATCHES = [ { sport: "Футбол", home: "Арсенал", away: "Челси", score: "2:1", time: "Финал" }, { sport: "Футбол", home: "Реал Мадрид", away: "Барселона", score: "1:1", time: "110-я минута" }, { sport: "Теннис", home: "Сафара", away: "Медведев", score: "3:2", time: "Победа Сафары" }, { sport: "Хоккей", home: "СКА", away: "ЦСКА", score: "3:2", time: "После буллитов" }, { sport: "Бокс", home: "Алимхан", away: "Гарсия", score: "TKO", time: "раунд 9" }, ]; const ARTICLES = [ { sport: "Футбол", date: "22.09.2026", title: "Как Премьер-лига дошла до рекордной гонки за титул", excerpt: "Впервые за десять лет три команды делят первые места с разницей в два очка. Разбираем, что решает чемпионство.", }, { sport: "Теннис", date: "21.09.2026", title: "Медведев и Сафара: разбор полуфинала турнира", excerpt: "Пять сетов, 3 часа 12 минут. Ключевые моменты и статистика матча, который войдёт в историю турнира.", }, { sport: "Хоккей", date: "20.09.2026", title: "Гонка бомбардиров КХЛ: кто впереди и почему", excerpt: "Три форварда набрали больше 40 очков. Сравниваем их стили игры и роли в командах.", }, { sport: "Бокс", date: "19.09.2026", title: "Большой бой года: что ждать от реванша", excerpt: "Два чемпиона, два стиля и титул в двух версиях. Прогнозы экспертов и история их предыдущего боя.", }, { sport: "Футбол", date: "18.09.2026", title: "Молодые вратари, которые изменят Европу", excerpt: "Обзор пяти голкиперов до 22 лет, которые в этом сезоне уже доказали класс. Где посмотреть их игры.", }, { sport: "Теннис", date: "17.09.2026", title: "Как изменился грунтовый сезон за десять лет", excerpt: "Цифры и тренды: скорости подачи, длина матчей и новые герои сезона на медленных кортах.", }, ]; const fmtDate = (s) => s; function icon(name) { return ``; } function renderSports() { const grid = document.getElementById("sports-grid"); if (!grid) return; grid.style.gridTemplateColumns = "repeat(4, 1fr)"; grid.innerHTML = SPORTS.map((s) => `
${icon(s.icon)}

${s.name}

${s.desc}

${s.chip}
`).join(""); } const TABLE_COLS = ["И", "В", "Н", "П", "ГЗ", "ГП"]; const CURRENT = { premier: { label: "АПЛ, Англия", cols: TABLE_COLS, rows: [], live: true }, laliga: { label: "Ла Лига, Испания", cols: TABLE_COLS, rows: [], live: true }, bundesliga: { label: "Бундеслига, Германия", cols: TABLE_COLS, rows: [], live: true }, seriea: { label: "Серия А, Италия", cols: TABLE_COLS, rows: [], live: true }, }; const ZONES = { premier: { ucl: 4, uel: 1, releg: 3 }, laliga: { ucl: 4, uel: 1, releg: 3 }, bundesliga: { ucl: 4, uel: 1, releg: 3 }, seriea: { ucl: 4, uel: 0, releg: 3 }, }; function applyLive(key, payload) { if (!payload || !Array.isArray(payload.rows)) return; CURRENT[key] = { label: payload.name || CURRENT[key].label, cols: CURRENT[key].cols, rows: payload.rows.map((r) => [r.name, r.played, r.win, r.draw, r.lost, r.gf, r.ga]), live: true, }; } function liveError(key) { CURRENT[key].rows = []; CURRENT[key].live = false; } async function loadStandings() { try { const res = await fetch("api/standings"); const j = await res.json(); if (!j.ok || !j.data) throw new Error("bad payload"); for (const key of Object.keys(CURRENT)) { const d = j.data[key]; if (!d || d.error) { liveError(key); continue; } applyLive(key, d); } const anyLive = Object.values(CURRENT).some((l) => l.live); const status = document.getElementById("table-status"); if (status) { const when = new Date(j.updatedAt).toLocaleString("ru-RU", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" }); status.innerHTML = anyLive ? `обновлено в ${when}источник: TheSportsDB · повтор через 15 минут` : `нет связи с источником — показаны демо-данные`; } for (const key of Object.keys(CURRENT)) { if (!CURRENT[key].live) CURRENT[key].rows = (LEAGUES[key] && LEAGUES[key].rows) || []; } return anyLive; } catch (_) { for (const key of Object.keys(CURRENT)) { CURRENT[key].rows = (LEAGUES[key] && LEAGUES[key].rows) || []; CURRENT[key].live = false; } const status = document.getElementById("table-status"); if (status) status.innerHTML = `нет связи с источником — показаны демо-данные`; return false; } } function renderLeagueTabs() { const tabs = document.getElementById("league-tabs"); if (!tabs) return; tabs.innerHTML = Object.entries(CURRENT).map(([key, l], i) => ` `).join(""); tabs.addEventListener("click", (e) => { const btn = e.target.closest("[data-league]"); if (!btn) return; tabs.querySelectorAll("button").forEach((b) => b.setAttribute("data-variant", "ghost")); btn.setAttribute("data-variant", "primary"); renderTable(btn.dataset.league); }); } function zoneClass(key, i, total) { const z = ZONES[key] || {}; if (i < (z.ucl || 0)) return "zone-1"; if (i < (z.ucl || 0) + (z.uel || 0)) return "zone-2"; if (i >= total - (z.releg || 0)) return "zone-4"; return ""; } function renderTable(key) { const league = CURRENT[key] || CURRENT.premier; const table = document.getElementById("league-table"); if (!table) return; if (!league.rows.length) { table.innerHTML = `Данные по этой лиге временно недоступны.`; return; } const total = league.rows.length; const head = `#Команда${league.cols.map((c) => `${c}`).join("")}`; const body = league.rows.map((r, i) => { const pts = r[2] * 3 + r[3]; const zone = zoneClass(key, i, total); return ` ${i + 1} ${r[0]} ${pts} ${r.slice(1).map((v) => `${v}`).join("")} `; }).join(""); table.innerHTML = head + `${body}`; const legend = document.querySelector(".zone-legend"); if (legend) legend.innerHTML = `Лига чемпионовЕврокубкиВылет`; } function renderMatches() { const strip = document.getElementById("match-strip"); if (!strip) return; strip.innerHTML = MATCHES.map((m) => `
${m.home}${m.away}
${m.score}
${m.sport} · ${m.time}
`).join(""); } function renderArticles() { const grid = document.getElementById("article-grid"); if (!grid) return; grid.innerHTML = ARTICLES.map((a) => `
${a.sport} ${fmtDate(a.date)}

${a.title}

${a.excerpt}

Читать далее →
`).join(""); } document.addEventListener("DOMContentLoaded", async () => { renderSports(); renderLeagueTabs(); renderMatches(); renderArticles(); await loadStandings(); renderTable("premier"); setInterval(async () => { await loadStandings(); const activeBtn = document.querySelector("#league-tabs [data-variant='primary']"); renderTable(activeBtn ? activeBtn.dataset.league : "premier"); }, 15 * 60 * 1000); });