Compare commits
3 Commits
745fd3fe73
...
181440b81d
| Author | SHA1 | Date | |
|---|---|---|---|
| 181440b81d | |||
| 8050b669c2 | |||
| 727aebf2cb |
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.vibe42-run.log
|
||||||
|
.vibe42-run.pid
|
||||||
|
node_modules/
|
||||||
12
.vibe42-run.log
Normal file
12
.vibe42-run.log
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
--- 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
|
||||||
|
--- run Wed Sep 23 10:13:43 AM UTC 2026 :: npm start --silent
|
||||||
|
|
||||||
|
up to date in 326ms
|
||||||
|
Сервер на порту 3000
|
||||||
1
.vibe42-run.pid
Normal file
1
.vibe42-run.pid
Normal file
@ -0,0 +1 @@
|
|||||||
|
1345
|
||||||
@ -1,39 +1,40 @@
|
|||||||
/* Vibe AI: Стандартный дизайн. Load AFTER the four base stylesheets. */
|
/* Vibe AI: Стандартный дизайн. Load AFTER the four base stylesheets. */
|
||||||
|
/* Чёрно-белая палитра по запросу пользователя. */
|
||||||
:root, [data-theme] {
|
:root, [data-theme] {
|
||||||
color-scheme: light;
|
color-scheme: dark;
|
||||||
--kt-ai-bg: #fafaf9;
|
--kt-ai-bg: #0a0a0a;
|
||||||
--kt-ai-bg-soft: #fafaf9;
|
--kt-ai-bg-soft: #111111;
|
||||||
--kt-ai-bg-elevated: #ffffff;
|
--kt-ai-bg-elevated: #141414;
|
||||||
--kt-ai-bg-sunken: #fafaf9;
|
--kt-ai-bg-sunken: #0a0a0a;
|
||||||
--kt-ai-bg-glass: #ffffff;
|
--kt-ai-bg-glass: #141414;
|
||||||
--kt-ai-bg-hover: #c1e1f7;
|
--kt-ai-bg-hover: #1f1f1f;
|
||||||
--kt-ai-bg-active: #c1e1f7;
|
--kt-ai-bg-active: #1f1f1f;
|
||||||
--kt-ai-fg: #0c0a09;
|
--kt-ai-fg: #fafafa;
|
||||||
--kt-ai-fg-muted: #78716c;
|
--kt-ai-fg-muted: #a3a3a3;
|
||||||
--kt-ai-fg-faint: #78716c;
|
--kt-ai-fg-faint: #737373;
|
||||||
--kt-ai-fg-on-fill: #0c0a09;
|
--kt-ai-fg-on-fill: #fafafa;
|
||||||
--kt-ai-border: #e8e6e5;
|
--kt-ai-border: #262626;
|
||||||
--kt-ai-border-strong: #78716c;
|
--kt-ai-border-strong: #525252;
|
||||||
--kt-ai-divider: #e8e6e5;
|
--kt-ai-divider: #262626;
|
||||||
--kt-ai-card-bg: #ffffff;
|
--kt-ai-card-bg: #111111;
|
||||||
--kt-ai-card-bg-glow: #ffffff;
|
--kt-ai-card-bg-glow: #141414;
|
||||||
--kt-ai-card-border: #e8e6e5;
|
--kt-ai-card-border: #262626;
|
||||||
--kt-ai-card-border-hover: #78716c;
|
--kt-ai-card-border-hover: #404040;
|
||||||
--kt-ai-primary: #3ba6f1;
|
--kt-ai-primary: #fafafa;
|
||||||
--kt-ai-primary-hover: #3ba6f1;
|
--kt-ai-primary-hover: #e6e6e6;
|
||||||
--kt-ai-primary-subtle: #c1e1f7;
|
--kt-ai-primary-subtle: #1f1f1f;
|
||||||
--kt-ai-link: #0369a1;
|
--kt-ai-link: #d4d4d4;
|
||||||
--kt-ai-brand-blue: #3ba6f1;
|
--kt-ai-brand-blue: #fafafa;
|
||||||
--kt-ai-selection: #c1e1f7;
|
--kt-ai-selection: #333333;
|
||||||
--kt-ai-focus-ring: #0369a1;
|
--kt-ai-focus-ring: #fafafa;
|
||||||
--kt-ai-focus-shadow: 0 0 0 3px #c1e1f7;
|
--kt-ai-focus-shadow: 0 0 0 3px #333333;
|
||||||
--kt-ai-chip-default-bg: #c1e1f7;
|
--kt-ai-chip-default-bg: #262626;
|
||||||
--kt-ai-chip-default-fg: #0c0a09;
|
--kt-ai-chip-default-fg: #fafafa;
|
||||||
--kt-ai-font-sans: Inter, system-ui, sans-serif;
|
--kt-ai-font-sans: Inter, system-ui, sans-serif;
|
||||||
--kt-ai-card-shadow: 0 2px 6px #0c0a0908;
|
--kt-ai-card-shadow: 0 2px 6px #00000060;
|
||||||
--kt-ai-card-shadow-hover: 0 4px 12px #0c0a0910;
|
--kt-ai-card-shadow-hover: 0 4px 12px #00000080;
|
||||||
--vibe-accent: #3ba6f1;
|
--vibe-accent: #fafafa;
|
||||||
--vibe-tint: #c1e1f7;
|
--vibe-tint: #1f1f1f;
|
||||||
--vibe-heading-font: Inter, system-ui, sans-serif;
|
--vibe-heading-font: Inter, system-ui, sans-serif;
|
||||||
--vibe-heading-weight: 400;
|
--vibe-heading-weight: 400;
|
||||||
--vibe-card-radius: 10px;
|
--vibe-card-radius: 10px;
|
||||||
@ -49,5 +50,8 @@ h1, h2, .kt-ai-h1, .kt-ai-h2 { font-family: var(--vibe-heading-font); font-weigh
|
|||||||
.kt-ai-tile, .kt-ai-card { background: var(--kt-ai-card-bg); border: 1px solid var(--kt-ai-card-border); border-radius: var(--vibe-card-radius); box-shadow: var(--kt-ai-card-shadow); }
|
.kt-ai-tile, .kt-ai-card { background: var(--kt-ai-card-bg); border: 1px solid var(--kt-ai-card-border); border-radius: var(--vibe-card-radius); box-shadow: var(--kt-ai-card-shadow); }
|
||||||
.kt-ai-btn, .kt-ai-btn[data-size] { border-radius: var(--vibe-button-radius); }
|
.kt-ai-btn, .kt-ai-btn[data-size] { border-radius: var(--vibe-button-radius); }
|
||||||
.kt-ai-input, .kt-ai-select, .kt-ai-textarea { border-radius: var(--vibe-field-radius); }
|
.kt-ai-input, .kt-ai-select, .kt-ai-textarea { border-radius: var(--vibe-field-radius); }
|
||||||
.vibe-highlight { background: var(--vibe-tint); color: var(--kt-ai-fg); border-radius: 4px; padding-inline: .12em; }
|
/* Белая акцентная кнопка: текст на ней чёрный для читаемости */
|
||||||
.vibe-accent { color: var(--vibe-accent); }
|
.kt-ai-btn[data-variant="primary"] { color: var(--kt-ai-bg); }
|
||||||
|
.kt-ai-btn[data-variant="primary"]:hover { color: var(--kt-ai-bg); }
|
||||||
|
.vibe-highlight { background: var(--vibe-tint); color: var(--kt-ai-fg); border-radius: 4px; padding-inline: .12em; box-shadow: inset 0 0 0 1px var(--kt-ai-border); }
|
||||||
|
.vibe-accent { color: var(--kt-ai-fg); }
|
||||||
|
|||||||
@ -35,7 +35,7 @@
|
|||||||
.article h3 { font-size: 16px; font-weight: 500; line-height: 1.35; }
|
.article h3 { font-size: 16px; font-weight: 500; line-height: 1.35; }
|
||||||
.article .excerpt { color: var(--kt-ai-fg-muted); font-size: 14px; flex: 1; }
|
.article .excerpt { color: var(--kt-ai-fg-muted); font-size: 14px; flex: 1; }
|
||||||
.article-meta { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; font-size: 12px; color: var(--kt-ai-fg-muted); }
|
.article-meta { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; font-size: 12px; color: var(--kt-ai-fg-muted); }
|
||||||
.sport-banner { border-radius: var(--vibe-card-radius); background: var(--kt-ai-primary-subtle); border: none; padding: clamp(24px, 5vw, 44px); }
|
.sport-banner { border-radius: var(--vibe-card-radius); background: var(--kt-ai-bg-soft); border: 1px solid var(--kt-ai-border); padding: clamp(24px, 5vw, 44px); }
|
||||||
.sport-banner h2 { font-size: clamp(26px, 4vw, 40px); margin-bottom: 8px; }
|
.sport-banner h2 { font-size: clamp(26px, 4vw, 40px); margin-bottom: 8px; }
|
||||||
.sport-banner p { color: var(--kt-ai-fg-muted); max-width: 560px; }
|
.sport-banner p { color: var(--kt-ai-fg-muted); max-width: 560px; }
|
||||||
.match-strip { display: flex; gap: 12px; flex-wrap: wrap; margin-top: var(--kt-ai-space-8, 28px); }
|
.match-strip { display: flex; gap: 12px; flex-wrap: wrap; margin-top: var(--kt-ai-space-8, 28px); }
|
||||||
|
|||||||
10
package-lock.json
generated
Normal file
10
package-lock.json
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "sports-portal",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "sports-portal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
package.json
Normal file
6
package.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "sports-portal",
|
||||||
|
"private": true,
|
||||||
|
"type": "commonjs",
|
||||||
|
"scripts": { "start": "node server.js" }
|
||||||
|
}
|
||||||
219
script.js
Normal file
219
script.js
Normal file
@ -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 `<svg class="kt-ai-icon" aria-hidden="true"><use href="#${name}"></use></svg>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSports() {
|
||||||
|
const grid = document.getElementById("sports-grid");
|
||||||
|
if (!grid) return;
|
||||||
|
grid.style.gridTemplateColumns = "repeat(4, 1fr)";
|
||||||
|
grid.innerHTML = SPORTS.map((s) => `
|
||||||
|
<article class="kt-ai-tile" style="display:flex; flex-direction:column;">
|
||||||
|
<div class="sport-tile-head">
|
||||||
|
<span class="sport-badge">${icon(s.icon)}</span>
|
||||||
|
<h3>${s.name}</h3>
|
||||||
|
</div>
|
||||||
|
<p>${s.desc}</p>
|
||||||
|
<div class="tile-more">
|
||||||
|
<span class="kt-ai-chip">${s.chip}</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
`).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLeagueTabs() {
|
||||||
|
const tabs = document.getElementById("league-tabs");
|
||||||
|
if (!tabs) return;
|
||||||
|
tabs.innerHTML = Object.entries(LEAGUES).map(([key, l], i) => `
|
||||||
|
<button class="kt-ai-btn" data-league="${key}" data-variant="${i === 0 ? "primary" : "ghost"}">${l.label}</button>
|
||||||
|
`).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 renderTable(key) {
|
||||||
|
const league = LEAGUES[key] || LEAGUES.premier;
|
||||||
|
const table = document.getElementById("league-table");
|
||||||
|
if (!table) return;
|
||||||
|
const head = `<tr><th>#</th><th>Команда</th>${league.cols.map((c) => `<th>${c}</th>`).join("")}</tr>`;
|
||||||
|
const body = league.rows.map((r, i) => {
|
||||||
|
const pts = r[1] * 3 + r[2];
|
||||||
|
return `
|
||||||
|
<tr class="${i < 3 ? "top" : ""}">
|
||||||
|
<td><span class="pos">${i + 1}</span></td>
|
||||||
|
<td>${r[0]}</td>
|
||||||
|
<td style="text-align:center; font-variant-numeric: tabular-nums;">${pts}</td>
|
||||||
|
${r.slice(1).map((v) => `<td style="text-align:center; font-variant-numeric: tabular-nums;">${v}</td>`).join("")}
|
||||||
|
</tr>`;
|
||||||
|
}).join("");
|
||||||
|
table.innerHTML = head + `<tbody class="nums">${body}</tbody>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMatches() {
|
||||||
|
const strip = document.getElementById("match-strip");
|
||||||
|
if (!strip) return;
|
||||||
|
strip.innerHTML = MATCHES.map((m) => `
|
||||||
|
<div class="match">
|
||||||
|
<div class="vs"><span>${m.home}</span><span>${m.away}</span></div>
|
||||||
|
<div class="score">${m.score}</div>
|
||||||
|
<div class="info">${m.sport} · ${m.time}</div>
|
||||||
|
</div>
|
||||||
|
`).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderArticles() {
|
||||||
|
const grid = document.getElementById("article-grid");
|
||||||
|
if (!grid) return;
|
||||||
|
grid.innerHTML = ARTICLES.map((a) => `
|
||||||
|
<article class="kt-ai-card article" style="padding: 16px;">
|
||||||
|
<div class="article-meta">
|
||||||
|
<span class="kt-ai-chip">${a.sport}</span>
|
||||||
|
<span>${fmtDate(a.date)}</span>
|
||||||
|
</div>
|
||||||
|
<h3>${a.title}</h3>
|
||||||
|
<p class="excerpt">${a.excerpt}</p>
|
||||||
|
<a class="kt-ai-link" href="#articles">Читать далее →</a>
|
||||||
|
</article>
|
||||||
|
`).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
renderSports();
|
||||||
|
renderLeagueTabs();
|
||||||
|
renderTable("premier");
|
||||||
|
renderMatches();
|
||||||
|
renderArticles();
|
||||||
|
});
|
||||||
24
server.js
Normal file
24
server.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
const http = require("http");
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
http.createServer((req, res) => {
|
||||||
|
let file = req.url === "/" ? "/index.html" : req.url.split("?")[0];
|
||||||
|
const decoded = decodeURIComponent(file);
|
||||||
|
const full = path.normalize(path.join(__dirname, decoded));
|
||||||
|
if (!full.startsWith(__dirname)) { res.writeHead(403); return res.end(); }
|
||||||
|
if (!fs.existsSync(full) || !fs.statSync(full).isFile()) { res.writeHead(404); return res.end("Not found"); }
|
||||||
|
const ext = path.extname(full).toLowerCase();
|
||||||
|
const type = {
|
||||||
|
".html": "text/html",
|
||||||
|
".css": "text/css",
|
||||||
|
".js": "application/javascript",
|
||||||
|
".svg": "image/svg+xml",
|
||||||
|
".png": "image/png",
|
||||||
|
".ico": "image/x-icon",
|
||||||
|
}[ext] || "application/octet-stream";
|
||||||
|
res.writeHead(200, { "Content-Type": type + "; charset=utf-8" });
|
||||||
|
res.end(fs.readFileSync(full));
|
||||||
|
}).listen(PORT, () => console.log("Сервер на порту " + PORT));
|
||||||
Loading…
Reference in New Issue
Block a user