${s.name}
+${s.desc}
+diff --git a/.vibe42-run.log b/.vibe42-run.log
new file mode 100644
index 0000000..22aad9a
--- /dev/null
+++ b/.vibe42-run.log
@@ -0,0 +1,8 @@
+--- run Wed Sep 23 10:08:32 AM UTC 2026 :: npm start --silent
+
+up to date in 475ms
+Сервер на порту 3000
+--- run Wed Sep 23 10:09:20 AM UTC 2026 :: npm start --silent
+
+up to date in 355ms
+Сервер на порту 3000
diff --git a/.vibe42-run.pid b/.vibe42-run.pid
new file mode 100644
index 0000000..8408670
--- /dev/null
+++ b/.vibe42-run.pid
@@ -0,0 +1 @@
+290
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..6af7725
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,10 @@
+{
+ "name": "sports-portal",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "sports-portal"
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..810b897
--- /dev/null
+++ b/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "sports-portal",
+ "private": true,
+ "type": "commonjs",
+ "scripts": { "start": "node server.js" }
+}
diff --git a/script.js b/script.js
new file mode 100644
index 0000000..9eac1bf
--- /dev/null
+++ b/script.js
@@ -0,0 +1,219 @@
+// СпортПортал — данные и логика (мок-данные, фаза 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 в игре",
+ },
+];
+
+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) => `
+ ${s.desc}${s.name}
+
${a.excerpt}
+ Читать далее → +