feat: news/events pages, pagination, cookie banner, MONT→MONT rename, logo update, admin improvements, API endpoints, dynamic links by domain

This commit is contained in:
2026-06-01 17:44:25 +03:00
parent 7c0c2ea14a
commit b1fde8344e
117 changed files with 3993 additions and 70 deletions
+12 -3
View File
@@ -106,11 +106,20 @@
<form method="post" class="admin-create">
<input type="hidden" name="scope" value="{{ scope }}" />
<input type="hidden" name="action" value="create_admin" />
<input type="text" name="username" placeholder="Логин нового админа" required />
<label><input type="checkbox" name="allow_infra" checked /> Инфраструктура</label>
<label><input type="checkbox" name="allow_ib" checked /> ИБ</label>
<input type="text" name="username" placeholder="Логин нового пользователя" required />
<select name="new_role" id="newRoleSelect" style="padding:8px 10px;border-radius:8px;border:1px solid #ccd8f0;font-family:Manrope,sans-serif;font-size:13px;color:#1a3060;" onchange="toggleScopeFields(this.value)">
<option value="admin">Администратор матрицы</option>
<option value="news_editor">Редактор новостей</option>
</select>
<span id="scopeFields"><label><input type="checkbox" name="allow_infra" checked /> Инфраструктура</label>
<label><input type="checkbox" name="allow_ib" checked /> ИБ</label></span>
<button class="pri" type="submit">Создать</button>
</form>
<script>
function toggleScopeFields(role) {
document.getElementById('scopeFields').style.display = role === 'news_editor' ? 'none' : '';
}
</script>
<div class="admin-users-list">
{% for user in admin_users %}
<form class="admin-user-item" method="post">
+115
View File
@@ -0,0 +1,115 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ event.title }} — MONT</title>
<meta name="description" content="{{ event.body[:160] | striptags | replace('\n',' ') }}" />
<meta name="robots" content="index, follow" />
<link rel="canonical" href="{{ base_url }}/events/{{ event.slug }}" />
<meta property="og:type" content="article" />
<meta property="og:url" content="{{ base_url }}/events/{{ event.slug }}" />
<meta property="og:title" content="{{ event.title }} — MONT" />
{% if event.image %}<meta property="og:image" content="{{ base_url }}/static/{{ event.image }}" />{% endif %}
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg" />
<link rel="icon" type="image/png" href="/static/favicon.png" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700;800&display=swap" rel="stylesheet">
<style>
:root { --brand: #1f4ea3; --brand2: #3978e0; --bg: #eef4ff; }
* { box-sizing: border-box; }
body { margin: 0; font-family: Manrope, sans-serif; background: var(--bg); color: #15203b; min-height: 100vh; }
.wrap { width: min(1100px, calc(100% - 32px)); margin: 0 auto; padding: 28px 0 60px; }
.back-btn {
display: inline-flex; align-items: center; gap: 6px;
font-size: 13px; font-weight: 700; color: var(--brand);
text-decoration: none; margin-bottom: 20px;
padding: 8px 16px; border-radius: 999px;
background: #fff; border: 1px solid #c8d8f7; transition: .15s ease;
}
.back-btn:hover { background: #eef4ff; transform: translateX(-2px); }
.article-card {
background: #fff; border-radius: 20px;
border: 1px solid #dae6ff;
box-shadow: 0 12px 40px rgba(16,43,95,.1);
overflow: hidden;
}
.article-header { display: flex; gap: 20px; align-items: flex-start; padding: 28px 32px 0; }
.article-hero-img { flex-shrink: 0; width: 160px; height: 106px; object-fit: cover; border-radius: 12px; display: block; }
.article-header-text { flex: 1; min-width: 0; }
.article-body-wrap { padding: 20px 32px 36px; }
.event-date-badge {
display: inline-block; background: linear-gradient(135deg,#1f4ea3,#3978e0);
color: #fff; font-size: 12px; font-weight: 800;
padding: 4px 14px; border-radius: 999px; margin-bottom: 10px;
}
.article-title { font-size: 24px; font-weight: 800; color: #1a3e79; margin: 0; line-height: 1.25; }
.article-body { font-size: 15px; color: #2e3f58; line-height: 1.75; }
.article-body p { margin: 0 0 16px; }
.article-body p:last-child { margin-bottom: 0; }
.article-body a { color: var(--brand); font-weight: 600; text-decoration: none; }
.article-body a:hover { text-decoration: underline; }
.article-body strong, .article-body b { font-weight: 700; color: #1a3e79; }
.article-body em, .article-body i { font-style: italic; }
.article-body ul, .article-body ol { margin: 0 0 16px 20px; padding: 0; }
.article-body li { margin-bottom: 6px; }
.article-body h2, .article-body h3, .article-body h4 { font-weight: 800; color: #1a3e79; margin: 20px 0 10px; }
.article-body h2 { font-size: 20px; }
.article-body h3 { font-size: 17px; }
.article-body blockquote { border-left: 3px solid var(--brand); margin: 16px 0; padding: 8px 16px; background: #f0f5ff; border-radius: 0 8px 8px 0; color: #526079; }
.article-body table { width: 100%; border-collapse: collapse; margin: 0 0 16px; font-size: 14px; }
.article-body th, .article-body td { border: 1px solid #dae6ff; padding: 8px 12px; text-align: left; }
.article-body th { background: #eef4ff; font-weight: 700; color: #1a3e79; }
.article-contact { margin-top: 28px; padding-top: 20px; border-top: 1px solid #e8f0ff; font-size: 13px; color: #526079; }
.article-contact a { color: var(--brand); font-weight: 700; text-decoration: none; }
@media (max-width: 640px) {
.article-header { flex-direction: column; padding: 20px 20px 0; }
.article-hero-img { width: 100%; height: 140px; }
.article-body-wrap { padding: 16px 20px 28px; }
.article-title { font-size: 20px; }
}
</style>
</head>
<body>
<div class="wrap">
<a href="/events" class="back-btn">← Все мероприятия</a>
<article class="article-card">
<div class="article-header">
{% if event.image %}
<img class="article-hero-img" src="/static/{{ event.image }}" alt="{{ event.title }}" />
{% endif %}
<div class="article-header-text">
<span class="event-date-badge">📅 {{ event.event_date[8:10] }}.{{ event.event_date[5:7] }}.{{ event.event_date[:4] }}</span>
<h1 class="article-title">{{ event.title }}</h1>
</div>
</div>
<div class="article-body-wrap">
<div class="article-body">
{% if '<' in event.body %}
{{ event.body | safe }}
{% else %}
{% for paragraph in event.body.split('\n\n') %}
<p>{{ paragraph }}</p>
{% endfor %}
{% endif %}
</div>
{% if event.register_url %}
<div style="margin-top:24px;">
<a href="{{ event.register_url }}" target="_blank" rel="noopener"
style="display:inline-block;background:linear-gradient(135deg,#1f4ea3,#3978e0);
color:#fff;font-weight:800;font-size:15px;padding:12px 28px;
border-radius:12px;text-decoration:none;transition:.15s;"
onmouseover="this.style.opacity='.85'" onmouseout="this.style.opacity='1'">
Зарегистрироваться →
</a>
</div>
{% endif %}
<div class="article-contact">
По вопросам участия: <a href="https://www.mont.ru/ru-ru/events" target="_blank" rel="noopener">mont.ru/events</a>
</div>
</div>
</article>
</div>
</body>
</html>
+129
View File
@@ -0,0 +1,129 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Мероприятия MONT</title>
<meta name="description" content="Предстоящие мероприятия, вебинары и конференции дистрибьютора MONT." />
<meta name="robots" content="index, follow" />
<link rel="canonical" href="{{ base_url }}/events" />
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg" />
<link rel="icon" type="image/png" href="/static/favicon.png" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700;800&display=swap" rel="stylesheet">
<style>
:root { --brand: #1f4ea3; --brand2: #3978e0; --bg: #eef4ff; }
* { box-sizing: border-box; }
body { margin: 0; font-family: Manrope, sans-serif; background: var(--bg); color: #15203b; min-height: 100vh; }
.wrap { width: min(900px, calc(100% - 32px)); margin: 0 auto; padding: 28px 0 60px; }
.back-btn {
display: inline-flex; align-items: center; gap: 6px;
font-size: 13px; font-weight: 700; color: var(--brand);
text-decoration: none; margin-bottom: 24px;
padding: 8px 16px; border-radius: 999px;
background: #fff; border: 1px solid #c8d8f7; transition: .15s;
}
.back-btn:hover { background: #eef4ff; transform: translateX(-2px); }
h1 { font-size: 28px; font-weight: 800; color: #1a3e79; margin: 0 0 24px; }
.top3 { margin-bottom: 32px; }
.top3-label {
font-size: 11px; font-weight: 800; letter-spacing: 1.2px;
text-transform: uppercase; color: #7a9bc0; margin-bottom: 14px;
}
.top3-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.top3-card {
background: #fff; border-radius: 16px; border: 1px solid #dae6ff;
box-shadow: 0 6px 20px rgba(16,43,95,.08);
overflow: hidden; text-decoration: none; color: inherit;
transition: .18s ease; display: flex; flex-direction: column;
}
.top3-card:hover { transform: translateY(-3px); box-shadow: 0 12px 32px rgba(16,43,95,.14); }
.top3-card img { width: 100%; height: 160px; object-fit: cover; display: block; }
.top3-card .no-img { width: 100%; height: 100px; background: linear-gradient(135deg, #e8f4f0, #d0eee4); display: flex; align-items: center; justify-content: center; }
.top3-card .no-img-icon { font-size: 36px; opacity: .5; }
.top3-card-body { padding: 14px 16px 18px; flex: 1; display: flex; flex-direction: column; }
.event-date-badge {
display: inline-block; background: linear-gradient(135deg,#1f4ea3,#3978e0);
color: #fff; font-size: 11px; font-weight: 800; letter-spacing: .3px;
padding: 3px 10px; border-radius: 999px; margin-bottom: 8px; align-self: flex-start;
}
.top3-card-title { font-size: 14px; font-weight: 800; color: #1a3e79; line-height: 1.4; flex: 1; margin: 0 0 12px; }
.top3-card-excerpt { font-size: 12px; color: #5a7090; line-height: 1.55; margin: 0 0 12px; flex: 1;
display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.read-more { font-size: 12px; font-weight: 700; color: var(--brand2); }
.divider { border: none; border-top: 2px solid #e0eaff; margin: 0 0 24px; }
.rest-label { font-size: 11px; font-weight: 800; letter-spacing: 1.2px; text-transform: uppercase; color: #7a9bc0; margin-bottom: 16px; }
.event-row {
background: #fff; border-radius: 14px; border: 1px solid #dae6ff;
display: flex; gap: 0; overflow: hidden; margin-bottom: 12px;
text-decoration: none; color: inherit;
transition: .18s ease; box-shadow: 0 4px 14px rgba(16,43,95,.06);
}
.event-row:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(16,43,95,.12); }
.event-row img { width: 120px; height: 90px; object-fit: cover; flex-shrink: 0; }
.event-row .no-img-row { width: 80px; flex-shrink: 0; background: linear-gradient(135deg, #e8f4f0, #d0eee4); }
.event-row-body { padding: 12px 16px; display: flex; flex-direction: column; justify-content: center; }
.event-row-date { font-size: 11px; color: #3978e0; font-weight: 700; margin-bottom: 4px; }
.event-row-title { font-size: 14px; font-weight: 800; color: #1a3e79; line-height: 1.35; margin: 0 0 6px; }
.event-row-excerpt { font-size: 12px; color: #5a7090; line-height: 1.5; margin: 0;
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.empty { text-align: center; padding: 60px 0; color: #9ab0d0; font-size: 15px; }
@media (max-width: 700px) { .top3-grid { grid-template-columns: 1fr; } .event-row img { width: 90px; height: 70px; } }
</style>
</head>
<body>
<div class="wrap">
<a href="/" class="back-btn">← На главную</a>
<h1>Мероприятия MONT</h1>
{% if events %}
{% set top3 = events[:3] %}
{% set rest = events[3:] %}
<div class="top3">
<div class="top3-label">Ближайшие мероприятия</div>
<div class="top3-grid">
{% for e in top3 %}
<a class="top3-card" href="/events/{{ e.slug }}">
{% if e.image %}
<img src="/static/{{ e.image }}" alt="{{ e.title }}" />
{% else %}
<div class="no-img"><span class="no-img-icon">📅</span></div>
{% endif %}
<div class="top3-card-body">
<span class="event-date-badge">{{ e.event_date[8:10] }}.{{ e.event_date[5:7] }}.{{ e.event_date[:4] }}</span>
<h2 class="top3-card-title">{{ e.title }}</h2>
<p class="top3-card-excerpt">{{ e.body | striptags | truncate(180, true, '...') }}</p>
<span class="read-more">Подробнее →</span>
</div>
</a>
{% endfor %}
</div>
</div>
{% if rest %}
<hr class="divider" />
<div class="rest-label">Все мероприятия</div>
{% for e in rest %}
<a class="event-row" href="/events/{{ e.slug }}">
{% if e.image %}
<img src="/static/{{ e.image }}" alt="{{ e.title }}" />
{% else %}
<div class="no-img-row"></div>
{% endif %}
<div class="event-row-body">
<div class="event-row-date">📅 {{ e.event_date[8:10] }}.{{ e.event_date[5:7] }}.{{ e.event_date[:4] }}</div>
<div class="event-row-title">{{ e.title }}</div>
<p class="event-row-excerpt">{{ e.body | striptags | truncate(160, true, '...') }}</p>
</div>
</a>
{% endfor %}
{% endif %}
{% else %}
<div class="empty">Актуальных мероприятий нет</div>
{% endif %}
</div>
</body>
</html>
+138 -35
View File
@@ -3,17 +3,17 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Вендоры МОНТ — матрица продуктов и категорий дистрибьютора MONT</title>
<meta name="description" content="Корзина продуктов МОНТ: актуальная матрица вендоров, категорий и продуктовых линеек дистрибьютора MONT. Инфраструктура и информационная безопасность. МОНТ Казань, Москва." />
<meta name="keywords" content="вендоры МОНТ, вендоры MONT, дистрибьютор МОНТ, корзина продуктов МОНТ, матрица МОНТ, МОНТ Казань, партнёры МОНТ, ИБ вендоры, инфраструктура ПО, MONT vendor map" />
<title>Вендоры MONT — матрица продуктов и категорий дистрибьютора MONT</title>
<meta name="description" content="Корзина продуктов MONT: актуальная матрица вендоров, категорий и продуктовых линеек дистрибьютора MONT. Инфраструктура и информационная безопасность. MONT Казань, Москва." />
<meta name="keywords" content="вендоры MONT, вендоры MONT, дистрибьютор MONT, корзина продуктов MONT, матрица MONT, MONT Казань, партнёры MONT, ИБ вендоры, инфраструктура ПО, MONT vendor map" />
<meta name="robots" content="index, follow" />
<link rel="canonical" href="{{ canonical_url }}" />
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:url" content="{{ canonical_url }}" />
<meta property="og:title" content="Вендоры МОНТ — матрица продуктов и категорий" />
<meta property="og:description" content="Актуальная матрица вендоров и продуктов дистрибьютора МОНТ. Инфраструктура и информационная безопасность." />
<meta property="og:title" content="Вендоры MONT — матрица продуктов и категорий" />
<meta property="og:description" content="Актуальная матрица вендоров и продуктов дистрибьютора MONT. Инфраструктура и информационная безопасность." />
<meta property="og:image" content="{{ canonical_url }}/static/mont_logo.png" />
<meta property="og:locale" content="ru_RU" />
@@ -22,7 +22,7 @@
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "МОНТ",
"name": "MONT",
"alternateName": "MONT",
"description": "Дистрибьютор программного обеспечения. Корзина продуктов: вендоры, категории, продуктовые линейки в сфере инфраструктуры и информационной безопасности.",
"url": "{{ canonical_url }}",
@@ -31,6 +31,7 @@
}
</script>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg" />
<link rel="icon" type="image/png" href="/static/favicon.png" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -41,21 +42,21 @@
<main class="wrap">
<section class="brand-strip">
<div class="brand-logo">
<img src="/static/mont_logo.png?v=2" alt="MONT logo" />
<a id="logo-link" href="https://4mont.ru"><img src="/static/mont_logo.png?v=3" alt="MONT logo" /></a>
</div>
</section>
<section class="hero">
<div class="hero-layout">
<div>
<h1>Вендоры в корзине МОНТ</h1>
<h1>Вендоры в портфеле MONT</h1>
<p>Актуальная матрица вендоров, продуктов и категорий. Выбирайте вендоров или категории, чтобы видеть релевантные продуктовые линейки в Инфраструктуре и ИБ.</p>
<div class="mode-switch-row">
<div class="mode-switch">
<button id="modeInfra" class="mode-btn active" type="button">Инфраструктура</button>
<button id="modeIb" class="mode-btn" type="button">ИБ</button>
</div>
<a href="https://stend.4mont.ru/" target="_blank" rel="noopener" class="polygon-btn">Инфраструктурный полигон МОНТ</a>
<a id="polygon-link" href="https://stend.4mont.ru/" target="_blank" rel="noopener" class="polygon-btn">Инфраструктурный полигон MONT</a>
</div>
</div>
</div>
@@ -63,39 +64,108 @@
<!-- SSR-контент для поисковых роботов -->
<div style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;" aria-hidden="true">
<h2>Вендоры дистрибьютора МОНТ (MONT)</h2>
<p>Матрица вендоров, продуктов и категорий компании МОНТ — ведущего дистрибьютора программного обеспечения в России. Инфраструктура и информационная безопасность.</p>
<h2>Вендоры дистрибьютора MONT (MONT)</h2>
<p>Матрица вендоров, продуктов и категорий компании MONT — ведущего дистрибьютора программного обеспечения в России. Инфраструктура и информационная безопасность.</p>
<ul>{% for v in ssr_vendors %}<li>{{ v }}</li>{% endfor %}</ul>
<h2>Категории продуктов МОНТ</h2>
<h2>Категории продуктов MONT</h2>
<ul>{% for c in ssr_categories %}<li>{{ c }}</li>{% endfor %}</ul>
</div>
<section class="board">
<article class="card">
<div class="card-header"><h2>Вендоры</h2><span class="count-badge" id="vendorBadge">...</span></div>
<input id="vendorSearch" class="search" placeholder="Поиск вендора..." />
<div id="vendorList" class="chip-grid"></div>
</article>
<div class="page-layout">
<div class="main-col">
<section class="board">
<article class="card">
<div class="card-header"><h2>Вендоры</h2><span class="count-badge" id="vendorBadge">...</span></div>
<input id="vendorSearch" class="search" placeholder="Поиск вендора..." />
<div id="vendorList" class="chip-grid"></div>
</article>
<article class="card">
<div class="card-header"><h2>Категории</h2><span class="count-badge" id="categoryBadge">...</span></div>
<input id="categorySearch" class="search" placeholder="Поиск категории..." />
<div id="categoryList" class="chip-grid"></div>
</article>
</section>
<article class="card">
<div class="card-header"><h2>Категории</h2><span class="count-badge" id="categoryBadge">...</span></div>
<input id="categorySearch" class="search" placeholder="Поиск категории..." />
<div id="categoryList" class="chip-grid"></div>
</article>
</section>
<div class="footer-bar">
<div id="stats">Загрузка...</div>
<button class="action" id="clearBtn">Сбросить фильтры</button>
<div class="footer-bar">
<div id="stats">Загрузка...</div>
<button class="action" id="clearBtn">Сбросить фильтры</button>
</div>
<section class="result">
<div class="result-head">
<h3>Вендоры и продукты (после фильтрации)</h3>
</div>
<div id="activeFilters" class="active-filters"></div>
<div id="resultRows" class="rows"></div>
</section>
</div>
<aside class="news-sidebar">
<div class="news-widget">
<div class="news-widget-head">
<h2>Новости MONT</h2>
<div style="display:flex;gap:6px;align-items:center;">
{% if is_news_editor %}
<a href="/news-admin" class="news-all-link" style="background:linear-gradient(135deg,#1f4ea3,#3978e0);color:#fff;border-color:transparent;">✏️ Редактор</a>
{% endif %}
<a href="/news" class="news-all-link">Все →</a>
</div>
</div>
{% if latest_news %}
<div class="news-list">
{% for n in latest_news %}
<a class="news-card" href="/news/{{ n.slug }}">
<div class="news-card-img-wrap">
{% if n.image %}
<img class="news-card-img" src="/static/{{ n.image }}" alt="{{ n.title }}" />
{% else %}
<span class="news-card-no-img">📰</span>
{% endif %}
</div>
<div class="news-card-body">
<div class="news-card-date">{{ n.created_at[8:10] }}.{{ n.created_at[5:7] }}.{{ n.created_at[:4] }}</div>
<h3 class="news-card-title">{{ n.title }}</h3>
<span class="news-card-btn">Подробнее →</span>
</div>
</a>
{% endfor %}
</div>
{% else %}
<p class="news-empty">Новостей пока нет</p>
{% endif %}
</div>
<div class="news-widget" style="margin-top:14px;">
<div class="news-widget-head">
<h2>Мероприятия MONT</h2>
<a href="/events" class="news-all-link">Все →</a>
</div>
{% if upcoming_events %}
<div class="news-list">
{% for e in upcoming_events %}
<a class="news-card" href="/events/{{ e.slug }}">
<div class="news-card-img-wrap">
{% if e.image %}
<img class="news-card-img" src="/static/{{ e.image }}" alt="{{ e.title }}" />
{% else %}
<span class="news-card-no-img">📅</span>
{% endif %}
</div>
<div class="news-card-body">
<div class="news-card-date">{{ e.event_date[8:10] }}.{{ e.event_date[5:7] }}.{{ e.event_date[:4] }}</div>
<h3 class="news-card-title">{{ e.title }}</h3>
<span class="news-card-btn">Подробнее →</span>
</div>
</a>
{% endfor %}
</div>
{% else %}
<p class="news-empty">Актуальных мероприятий нет</p>
{% endif %}
</div>
</aside>
</div>
<section class="result">
<div class="result-head">
<h3>Вендоры и продукты (после фильтрации)</h3>
</div>
<div id="activeFilters" class="active-filters"></div>
<div id="resultRows" class="rows"></div>
</section>
<div class="credit">
<button type="button" id="btn-contact-ruslan">Made by Galyaviev</button>
</div>
@@ -224,10 +294,43 @@
}
}
document.getElementById('btn-contact-ruslan').addEventListener('click', openModal);
document.getElementById('btn-contact-ruslan').addEventListener('click', () => { window.location.href = 'mailto:ruslan@ipcom.su'; });
overlay.addEventListener('click', e => { if (e.target === overlay) window._closeContact(); });
document.addEventListener('keydown', e => { if (e.key === 'Escape') window._closeContact(); });
})();
</script>
<script>
(function(){
var isMont = location.hostname === 'maps.mont.ru';
var poly = document.getElementById('polygon-link');
if (poly) poly.href = isMont ? 'https://stand.mont.ru/' : 'https://stend.4mont.ru/';
var logo = document.getElementById('logo-link');
if (logo) logo.href = isMont ? 'https://www.mont.ru' : 'https://4mont.ru';
})();
</script>
<div id="cookie-banner" style="display:none;position:fixed;bottom:0;left:0;right:0;z-index:9999;background:#1a3060;color:#fff;padding:1rem 1.5rem;box-shadow:0 -2px 16px rgba(0,0,0,.2);align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;">
<span style="font-size:.9rem;line-height:1.5;">
Мы используем файлы cookie, чтобы сделать работу с сайтом удобнее. Нажмите «Принять», чтобы согласиться с использованием файлов cookie в соответствии с
<a href="https://www.mont.ru/ru-ru/confidential" target="_blank" rel="noopener"
style="color:#7eb3ff;text-decoration:underline;">политикой конфиденциальности</a>.
</span>
<button id="cookie-accept" style="background:linear-gradient(135deg,#3978e0,#5a9ef5);border:none;border-radius:9px;
padding:.55rem 1.4rem;color:#fff;font-weight:700;font-family:Manrope,sans-serif;cursor:pointer;white-space:nowrap;">
Принять
</button>
</div>
<script>
(function(){
if (localStorage.getItem('cookie_accepted')) return;
var b = document.getElementById('cookie-banner');
b.style.display = 'flex';
document.getElementById('cookie-accept').addEventListener('click', function(){
localStorage.setItem('cookie_accepted', '1');
b.style.display = 'none';
});
})();
</script>
</body>
</html>
+306
View File
@@ -0,0 +1,306 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Редактор новостей — MONT</title>
<meta name="robots" content="noindex, nofollow" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700;800&display=swap" rel="stylesheet">
<style>
:root { --brand: #1f4ea3; --brand2: #3978e0; --bg: #eef4ff; }
* { box-sizing: border-box; }
body { margin: 0; font-family: Manrope, sans-serif; background: var(--bg); color: #15203b; min-height: 100vh; }
.wrap { width: min(860px, calc(100% - 32px)); margin: 0 auto; padding: 28px 0 60px; }
.top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24px; flex-wrap: wrap; gap: 10px; }
.top strong { font-size: 20px; font-weight: 800; color: #1a3e79; }
.top small { font-size: 13px; color: #7a9bc0; }
.btn { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 700;
padding: 8px 18px; border-radius: 10px; border: none; cursor: pointer; font-family: Manrope, sans-serif; transition: .15s; }
.btn-primary { background: linear-gradient(135deg, var(--brand), var(--brand2)); color: #fff; }
.btn-primary:hover { opacity: .88; }
.btn-danger { background: #fff0f0; color: #c0392b; border: 1px solid #fcc; }
.btn-danger:hover { background: #ffe0e0; }
.btn-logout { background: #f0f5ff; color: #526079; border: 1px solid #c8d8f7; }
.alert { padding: 10px 16px; border-radius: 10px; font-size: 13px; font-weight: 600; margin-bottom: 16px; }
.alert.ok { background: #edfaf3; color: #1a6b40; border: 1px solid #b3e8cc; }
.alert.error { background: #fff0f0; color: #c0392b; border: 1px solid #fcc; }
.box { background: #fff; border-radius: 18px; border: 1px solid #dae6ff; box-shadow: 0 8px 28px rgba(16,43,95,.08); padding: 24px 28px; margin-bottom: 20px; }
.box h3 { font-size: 16px; font-weight: 800; color: #1a3e79; margin: 0 0 16px; }
input[type=text], textarea {
width: 100%; padding: 10px 14px; border: 1px solid #cfd9f0; border-radius: 10px;
font-size: 14px; font-family: Manrope, sans-serif; color: #1a3060; outline: none; transition: .2s;
}
input[type=text]:focus, textarea:focus { border-color: #5b91f6; box-shadow: 0 0 0 3px rgba(91,145,246,.14); }
textarea { resize: vertical; min-height: 160px; }
.field { margin-bottom: 14px; }
.field label { display: block; font-size: 12px; font-weight: 700; color: #526079; margin-bottom: 5px; letter-spacing: .2px; }
.upload-zone {
border: 2px dashed #c8d8f7; border-radius: 12px; padding: 18px;
text-align: center; cursor: pointer; transition: .2s; background: #f8fbff;
position: relative;
}
.upload-zone:hover, .upload-zone.dragover { border-color: #5b91f6; background: #eef4ff; }
.upload-zone input[type=file] { position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%; }
.upload-zone .uz-label { font-size: 13px; color: #7a9bc0; font-weight: 600; pointer-events: none; }
.upload-zone .uz-label span { color: var(--brand); }
.img-preview { max-width: 100%; max-height: 200px; border-radius: 10px; margin-top: 10px; display: none; object-fit: cover; }
.current-img { width: 100%; max-height: 160px; object-fit: cover; border-radius: 10px; margin-bottom: 8px; }
.news-list { display: flex; flex-direction: column; gap: 12px; }
.news-item { border: 1px solid #dae6ff; border-radius: 12px; padding: 14px 18px; background: #f8fbff; }
.news-item-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
.news-item-title { font-size: 15px; font-weight: 800; color: #1a3e79; }
.news-item-meta { font-size: 12px; color: #8aa0c0; }
.news-item-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.badge-pub { font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 999px; background: #edfaf3; color: #1a6b40; border: 1px solid #b3e8cc; }
.badge-unpub { font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 999px; background: #fff8e6; color: #7a5500; border: 1px solid #f0d080; }
details summary { cursor: pointer; font-size: 13px; font-weight: 700; color: var(--brand); margin-top: 8px; }
details .edit-form { margin-top: 12px; padding-top: 12px; border-top: 1px solid #e0eaff; }
.pagination { display: flex; justify-content: center; align-items: center; gap: 6px; margin-top: 20px; flex-wrap: wrap; }
.pagination a, .pagination span {
display: inline-flex; align-items: center; justify-content: center;
width: 34px; height: 34px; border-radius: 8px;
font-size: 13px; font-weight: 700; text-decoration: none;
border: 1px solid #c8d8f7; background: #fff; color: #1f4ea3; transition: .15s;
}
.pagination a:hover { background: #eef4ff; }
.pagination span.current { background: linear-gradient(135deg,#1f4ea3,#3978e0); color: #fff; border-color: transparent; }
.pagination span.dots { background: none; border-color: transparent; color: #9ab0d0; }
.login-box { max-width: 360px; margin: 60px auto; background: #fff; border-radius: 20px; border: 1px solid #dae6ff; box-shadow: 0 12px 40px rgba(16,43,95,.1); padding: 32px; }
.login-box h2 { font-size: 20px; font-weight: 800; color: #1a3e79; margin: 0 0 20px; }
</style>
</head>
<body>
<div class="wrap">
{% if not is_news_editor %}
<div class="login-box">
<h2>Вход для редактора новостей</h2>
{% with messages = get_flashed_messages(with_categories=true) %}
{% for cat, msg in messages %}
<div class="alert {{ cat }}">{{ msg }}</div>
{% endfor %}
{% endwith %}
<form method="post">
<input type="hidden" name="action" value="news_login" />
<div class="field"><label>Логин</label><input type="text" name="username" required autofocus /></div>
<div class="field"><label>Пароль</label><input type="text" name="password" required /></div>
<button class="btn btn-primary" type="submit" style="width:100%;justify-content:center;">Войти</button>
</form>
</div>
{% else %}
<div class="top">
<div>
<strong>Редактор новостей MONT</strong><br>
<small>{{ admin_login }}</small>
</div>
<div style="display:flex;gap:8px;">
<a href="/" style="text-decoration:none;"><button class="btn btn-logout" type="button">На сайт</button></a>
<form method="post" style="margin:0;">
<input type="hidden" name="action" value="logout" />
<button class="btn btn-logout" type="submit">Выйти</button>
</form>
</div>
</div>
{% with messages = get_flashed_messages(with_categories=true) %}
{% for cat, msg in messages %}
<div class="alert {{ cat }}">{{ msg }}</div>
{% endfor %}
{% endwith %}
<div class="box">
<h3>Добавить новость</h3>
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="action" value="create_news" />
<div class="field"><label>Заголовок *</label><input type="text" name="title" placeholder="Заголовок новости" required /></div>
<div class="field">
<label>Картинка (jpg, png, webp)</label>
<div class="upload-zone" id="uzCreate">
<input type="file" name="image" accept=".jpg,.jpeg,.png,.webp,.gif" onchange="previewImg(this,'prevCreate')" />
<div class="uz-label">Перетащите файл, <span>выберите</span> или кликните в «Заголовок» и нажмите Ctrl+V</div>
</div>
<img id="prevCreate" class="img-preview" alt="" />
</div>
<div class="field">
<label>Текст новости * (разделяйте абзацы пустой строкой)</label>
<textarea name="body" placeholder="Текст новости..."></textarea>
</div>
<button class="btn btn-primary" type="submit">Опубликовать</button>
</form>
</div>
<div class="box">
<h3>Все новости</h3>
{% if all_news %}
<div class="news-list">
{% for n in all_news %}
<div class="news-item">
<div class="news-item-head">
<div>
<div class="news-item-title">{{ n.title }}</div>
<div class="news-item-meta">{{ n.created_at[:10] }} · /news/{{ n.slug }}</div>
</div>
<div class="news-item-actions">
<form method="post" style="margin:0;display:flex;align-items:center;gap:6px;">
<input type="hidden" name="action" value="toggle_published" />
<input type="hidden" name="news_id" value="{{ n.id }}" />
<input type="checkbox" name="published" id="tog_{{ n.id }}"
{% if n.published %}checked{% endif %}
onchange="this.form.submit()"
style="width:16px;height:16px;cursor:pointer;accent-color:#1f4ea3;" />
<label for="tog_{{ n.id }}" style="font-size:12px;font-weight:700;cursor:pointer;
color:{% if n.published %}#1a6b40{% else %}#7a5500{% endif %};">
{% if n.published %}Опубликована{% else %}Скрыта{% endif %}
</label>
</form>
<form method="post" onsubmit="return confirm('Удалить новость?')">
<input type="hidden" name="action" value="delete_news" />
<input type="hidden" name="news_id" value="{{ n.id }}" />
<button class="btn btn-danger" type="submit" style="padding:5px 12px;font-size:12px;">Удалить</button>
</form>
</div>
</div>
<details>
<summary>Редактировать</summary>
<div class="edit-form">
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="action" value="update_news" />
<input type="hidden" name="news_id" value="{{ n.id }}" />
<div class="field"><label>Заголовок</label><input type="text" name="title" value="{{ n.title }}" required /></div>
<div class="field">
<label>Картинка (оставьте пустым, чтобы не менять)</label>
{% if n.image %}
<img src="/static/{{ n.image }}" class="current-img" alt="" />
{% endif %}
<div class="upload-zone">
<input type="file" name="image" accept=".jpg,.jpeg,.png,.webp,.gif" onchange="previewImg(this,'prev_{{ n.id }}')" />
<div class="uz-label">Перетащите файл, <span>выберите</span> или кликните в «Заголовок» и нажмите Ctrl+V</div>
</div>
<img id="prev_{{ n.id }}" class="img-preview" alt="" />
</div>
<div class="field">
<label>Текст</label>
<textarea name="body">{{ n.body if n.body is defined else '' }}</textarea>
</div>
<div class="field">
<label>Дата публикации (ДД.ММ.ГГГГ ЧЧ:ММ)</label>
<input type="text" name="created_at_edit"
value="{{ n.created_at[8:10] }}.{{ n.created_at[5:7] }}.{{ n.created_at[:4] }} {{ n.created_at[11:16] }}"
placeholder="29.05.2026 14:30" style="width:200px;" />
</div>
<div class="field" style="display:flex;align-items:center;gap:8px;">
<input type="checkbox" name="published" id="pub_{{ n.id }}" {% if n.published %}checked{% endif %} style="width:auto;" />
<label for="pub_{{ n.id }}" style="margin:0;font-size:13px;">Опубликована</label>
</div>
<button class="btn btn-primary" type="submit">Сохранить</button>
</form>
</div>
</details>
</div>
{% endfor %}
</div>
{% else %}
<p style="color:#8aa0c0;font-size:14px;">Новостей пока нет.</p>
{% endif %}
{% if total_adm_pages > 1 %}
<div class="pagination">
{% if adm_page > 1 %}
<a href="?page={{ adm_page - 1 }}">&#8249;</a>
{% endif %}
{% for p in range(1, total_adm_pages + 1) %}
{% if p == adm_page %}
<span class="current">{{ p }}</span>
{% elif p == 1 or p == total_adm_pages or (p >= adm_page - 2 and p <= adm_page + 2) %}
<a href="?page={{ p }}">{{ p }}</a>
{% elif p == adm_page - 3 or p == adm_page + 3 %}
<span class="dots"></span>
{% endif %}
{% endfor %}
{% if adm_page < total_adm_pages %}
<a href="?page={{ adm_page + 1 }}">&#8250;</a>
{% endif %}
</div>
{% endif %}
</div>
{% endif %}
</div>
<script>
function previewImg(input, previewId) {
const prev = document.getElementById(previewId);
if (!prev) return;
if (input.files && input.files[0]) {
const reader = new FileReader();
reader.onload = e => { prev.src = e.target.result; prev.style.display = 'block'; };
reader.readAsDataURL(input.files[0]);
}
}
function applyFileToZone(file, zone) {
if (!file || !file.type.startsWith('image/')) return false;
const input = zone.querySelector('input[type=file]');
if (!input) return false;
const dt = new DataTransfer();
dt.items.add(file);
input.files = dt.files;
input.dispatchEvent(new Event('change'));
zone.classList.add('dragover');
setTimeout(() => zone.classList.remove('dragover'), 600);
return true;
}
function getActiveZone() {
// Prefer zone inside an open <details>
const openDetails = document.querySelector('details[open]');
if (openDetails) {
const z = openDetails.querySelector('.upload-zone');
if (z) return z;
}
// Fallback: create form zone
return document.getElementById('uzCreate');
}
document.querySelectorAll('.upload-zone').forEach(zone => {
zone.addEventListener('dragover', e => { e.preventDefault(); zone.classList.add('dragover'); });
zone.addEventListener('dragleave', () => zone.classList.remove('dragover'));
zone.addEventListener('drop', e => {
e.preventDefault(); zone.classList.remove('dragover');
const input = zone.querySelector('input[type=file]');
if (input && e.dataTransfer.files.length) {
input.files = e.dataTransfer.files;
input.dispatchEvent(new Event('change'));
}
});
// Mark as active on click/focus
zone.addEventListener('mouseenter', () => zone.dataset.lastActive = Date.now());
});
function showToast(msg) {
const hint = document.createElement('div');
hint.textContent = msg;
hint.style.cssText = 'position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:#1f4ea3;color:#fff;padding:10px 22px;border-radius:10px;font-size:13px;font-weight:700;z-index:9999;box-shadow:0 4px 16px rgba(0,0,0,.2);pointer-events:none;';
document.body.appendChild(hint);
setTimeout(() => hint.remove(), 2500);
}
// Intercept paste in any field: if clipboard has image — apply to upload zone
document.addEventListener('paste', e => {
const items = Array.from((e.clipboardData || {}).items || []);
showToast('paste: ' + items.map(i=>i.type).join(', ') || 'нет items');
const imgItem = items.find(i => i.type.startsWith('image/'));
if (!imgItem) return;
const file = imgItem.getAsFile();
if (!file) { showToast('getAsFile вернул null'); return; }
const zone = getActiveZone();
if (!zone) { showToast('зона не найдена'); return; }
e.preventDefault();
applyFileToZone(file, zone);
showToast('📋 Картинка вставлена из буфера');
});
</script>
</body>
</html>
+252
View File
@@ -0,0 +1,252 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ article.title }} — MONT</title>
<meta name="description" content="{{ article.body[:160] | replace('\n',' ') }}" />
<meta name="robots" content="index, follow" />
<link rel="canonical" href="{{ base_url }}/news/{{ article.slug }}" />
<meta property="og:type" content="article" />
<meta property="og:url" content="{{ base_url }}/news/{{ article.slug }}" />
<meta property="og:title" content="{{ article.title }} — MONT" />
<meta property="og:description" content="{{ article.body[:200] | replace('\n',' ') }}" />
{% if article.image %}<meta property="og:image" content="{{ base_url }}/static/{{ article.image }}" />{% endif %}
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg" />
<link rel="icon" type="image/png" href="/static/favicon.png" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Caveat:wght@600;700&family=Manrope:wght@400;600;700;800&display=swap" rel="stylesheet">
<style>
:root { --brand: #1f4ea3; --brand2: #3978e0; --bg: #eef4ff; --radius: 16px; }
* { box-sizing: border-box; }
body { margin: 0; font-family: Manrope, sans-serif; background: var(--bg); color: #15203b; min-height: 100vh; }
.wrap { width: min(1100px, calc(100% - 32px)); margin: 0 auto; padding: 28px 0 60px; }
.back-btn {
display: inline-flex; align-items: center; gap: 6px;
font-size: 13px; font-weight: 700; color: var(--brand);
text-decoration: none; margin-bottom: 20px;
padding: 8px 16px; border-radius: 999px;
background: #fff; border: 1px solid #c8d8f7; transition: .15s ease;
}
.back-btn:hover { background: #eef4ff; transform: translateX(-2px); }
.article-card {
background: #fff; border-radius: 20px;
border: 1px solid #dae6ff;
box-shadow: 0 12px 40px rgba(16,43,95,.1);
overflow: hidden;
}
.article-header { display: flex; gap: 20px; align-items: flex-start; padding: 28px 32px 0; }
.article-hero-img {
flex-shrink: 0;
width: 160px; height: 106px;
object-fit: cover;
border-radius: 12px;
display: block;
}
.article-header-text { flex: 1; min-width: 0; }
.article-body-wrap { padding: 20px 32px 36px; }
.article-date { font-size: 12px; color: #8aa0c0; font-weight: 600; margin-bottom: 8px; }
.article-title { font-size: 24px; font-weight: 800; color: #1a3e79; margin: 0; line-height: 1.25; }
.article-body { font-size: 15px; color: #2e3f58; line-height: 1.75; }
.article-body p { margin: 0 0 16px; }
.article-body p:last-child { margin-bottom: 0; }
.article-body a { color: var(--brand); font-weight: 600; text-decoration: none; }
.article-body a:hover { text-decoration: underline; }
.article-body strong, .article-body b { font-weight: 700; color: #1a3e79; }
.article-body em, .article-body i { font-style: italic; }
.article-body ul, .article-body ol { margin: 0 0 16px 20px; padding: 0; }
.article-body li { margin-bottom: 6px; }
.article-body h2, .article-body h3, .article-body h4 { font-weight: 800; color: #1a3e79; margin: 20px 0 10px; line-height: 1.3; }
.article-body h2 { font-size: 20px; }
.article-body h3 { font-size: 17px; }
.article-body blockquote { border-left: 3px solid var(--brand); margin: 16px 0; padding: 8px 16px; background: #f0f5ff; border-radius: 0 8px 8px 0; color: #526079; }
.article-body table { width: 100%; border-collapse: collapse; margin: 0 0 16px; font-size: 14px; }
.article-body th, .article-body td { border: 1px solid #dae6ff; padding: 8px 12px; text-align: left; }
.article-body th { background: #eef4ff; font-weight: 700; color: #1a3e79; }
.article-contact {
margin-top: 28px; padding-top: 20px;
border-top: 1px solid #e8f0ff;
font-size: 13px; color: #526079;
}
.article-contact a { color: var(--brand); font-weight: 700; text-decoration: none; }
.article-contact a:hover { text-decoration: underline; }
.credit {
text-align: center; margin-top: 24px;
}
.credit button {
font-family: 'Caveat', cursive; font-size: 18px;
color: #1c3f7c; background: none; border: none;
cursor: pointer; padding: 0;
transition: opacity .15s;
}
.credit button:hover { opacity: .7; }
.edit-btn {
display: inline-flex; align-items: center; gap: 6px;
font-size: 13px; font-weight: 700; color: #1f4ea3;
text-decoration: none; padding: 7px 16px;
border-radius: 999px; background: #eef4ff;
border: 1px solid #c8d8f7; transition: .15s;
margin-bottom: 16px; float: right;
}
.edit-btn:hover { background: #dceeff; }
@media (max-width: 640px) {
.article-header { flex-direction: column; padding: 20px 20px 0; }
.article-hero-img { width: 100%; height: 140px; }
.article-body-wrap { padding: 16px 20px 28px; }
.article-title { font-size: 20px; }
}
</style>
</head>
<body>
<div class="wrap">
<a href="/" class="back-btn">← На главную</a>
{% if is_news_editor %}
<a href="/news-admin" class="edit-btn">✏ Редактировать</a>
<div style="clear:both;"></div>
{% endif %}
<article class="article-card">
<div class="article-header">
{% if article.image %}
<img class="article-hero-img" src="/static/{{ article.image }}" alt="{{ article.title }}" />
{% endif %}
<div class="article-header-text">
<div class="article-date">{{ article.created_at[8:10] }}.{{ article.created_at[5:7] }}.{{ article.created_at[:4] }}</div>
<h1 class="article-title">{{ article.title }}</h1>
</div>
</div>
<div class="article-body-wrap">
<div class="article-body">
{% if '<' in article.body %}
{{ article.body | safe }}
{% else %}
{% for paragraph in article.body.split('\n\n') %}
<p>{{ paragraph }}</p>
{% endfor %}
{% endif %}
</div>
<div class="article-contact">
По вопросам сотрудничества: <a href="mailto:mshamov@mont.com">mshamov@mont.com</a>
</div>
</div>
</article>
<div class="credit">
<button type="button" id="btn-contact-ruslan">Made by Galyaviev</button>
</div>
</div>
<!-- Contact modal -->
<div id="contact-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:9000;align-items:center;justify-content:center;padding:1rem;">
<div style="background:#fff;border-radius:20px;width:100%;max-width:460px;max-height:90vh;overflow-y:auto;box-shadow:0 24px 64px rgba(16,43,95,.22);">
<div style="padding:1.5rem 1.75rem .75rem;display:flex;align-items:center;justify-content:space-between;">
<div style="font-size:1.15rem;font-weight:800;color:#1a3e79;">Связаться с Русланом</div>
<button onclick="window._closeContact()" style="background:none;border:none;font-size:1.4rem;color:#8aa;cursor:pointer;line-height:1;padding:0 4px;">×</button>
</div>
<div id="cm-body" style="padding:.5rem 1.75rem;display:flex;flex-direction:column;gap:.9rem;"></div>
<div id="cm-footer" style="display:flex;justify-content:flex-end;gap:.75rem;padding:.75rem 1.75rem 1.5rem;"></div>
</div>
</div>
<script>
(function() {
const overlay = document.getElementById('contact-modal');
const fieldStyle = 'width:100%;box-sizing:border-box;margin-top:.3rem;padding:.6rem .85rem;' +
'background:#f7faff;border:1px solid #c8d8f7;border-radius:9px;color:#1a3060;' +
'font-size:.92rem;outline:none;font-family:Manrope,sans-serif;';
const labelStyle = 'color:#526079;font-size:.8rem;font-weight:700;letter-spacing:.2px;';
function buildForm() {
document.getElementById('cm-body').innerHTML =
'<div><label style="' + labelStyle + '">Ваше имя *</label>' +
'<input id="cm-name" type="text" placeholder="Иван Иванов" style="' + fieldStyle + '"/></div>' +
'<div><label style="' + labelStyle + '">Email *</label>' +
'<input id="cm-email" type="email" placeholder="ivan@company.ru" style="' + fieldStyle + '"/></div>' +
'<div><label style="' + labelStyle + '">Телефон *</label>' +
'<input id="cm-phone" type="tel" placeholder="+7 (999) 000-00-00" style="' + fieldStyle + '"/></div>' +
'<div><label style="' + labelStyle + '">Сообщение *</label>' +
'<textarea id="cm-text" rows="4" placeholder="Ваш вопрос или предложение..." style="' + fieldStyle + 'resize:vertical;"></textarea></div>' +
'<div id="cm-error" style="display:none;background:#fff0f0;border:1px solid #fcc;border-radius:8px;' +
'padding:.5rem .85rem;color:#c0392b;font-size:.85rem;"></div>';
document.getElementById('cm-footer').innerHTML =
'<button onclick="window._closeContact()" style="background:#f0f5ff;border:1px solid #c8d8f7;border-radius:9px;' +
'padding:.6rem 1.25rem;color:#526079;cursor:pointer;font-family:Manrope,sans-serif;font-weight:600;">Отмена</button>' +
'<button id="cm-submit" style="background:linear-gradient(135deg,#1f4ea3,#3978e0);border:none;border-radius:9px;' +
'padding:.6rem 1.5rem;color:#fff;font-weight:700;cursor:pointer;font-family:Manrope,sans-serif;">Отправить</button>';
document.getElementById('cm-submit').addEventListener('click', submit);
}
function openModal() {
buildForm();
overlay.style.display = 'flex';
document.body.style.overflow = 'hidden';
setTimeout(() => { const n = document.getElementById('cm-name'); if (n) n.focus(); }, 80);
}
window._closeContact = function() {
overlay.style.display = 'none';
document.body.style.overflow = '';
};
async function submit() {
const name = document.getElementById('cm-name').value.trim();
const email = document.getElementById('cm-email').value.trim();
const phone = document.getElementById('cm-phone').value.trim();
const text = document.getElementById('cm-text').value.trim();
const errEl = document.getElementById('cm-error');
const btn = document.getElementById('cm-submit');
const emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const phoneRe = /^[\+\d][\d\s\-\(\)]{6,18}$/;
const errors = [];
[[!name, 'cm-name'], [!emailRe.test(email), 'cm-email'],
[!phoneRe.test(phone), 'cm-phone'], [!text, 'cm-text']].forEach(([bad, id]) => {
document.getElementById(id).style.borderColor = bad ? '#e74c3c' : '#c8d8f7';
if (bad) errors.push(id);
});
if (errors.length) {
errEl.textContent = 'Пожалуйста, заполните все поля корректно';
errEl.style.display = 'block';
return;
}
btn.disabled = true;
btn.textContent = 'Отправка...';
errEl.style.display = 'none';
try {
const res = await fetch('/api/contact', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name, email, phone, text}),
});
if (!res.ok) {
const d = await res.json().catch(() => ({}));
throw new Error(d.detail || 'Ошибка отправки');
}
document.getElementById('cm-body').innerHTML =
'<div style="text-align:center;padding:2rem 0">' +
'<div style="width:56px;height:56px;border-radius:50%;background:linear-gradient(135deg,#1f4ea3,#3978e0);' +
'color:#fff;font-size:1.8rem;display:flex;align-items:center;justify-content:center;margin:0 auto 1rem;">✓</div>' +
'<div style="font-size:1.1rem;font-weight:800;color:#1a3e79;">Отправлено!</div>' +
'<div style="color:#526079;font-size:.9rem;margin-top:.4rem;">Постараюсь ответить в ближайшее время</div></div>';
document.getElementById('cm-footer').innerHTML =
'<button onclick="window._closeContact()" style="background:linear-gradient(135deg,#1f4ea3,#3978e0);border:none;' +
'border-radius:9px;padding:.6rem 1.5rem;color:#fff;font-weight:700;cursor:pointer;font-family:Manrope,sans-serif;">Закрыть</button>';
} catch(e) {
errEl.textContent = e.message || 'Ошибка отправки';
errEl.style.display = 'block';
btn.disabled = false;
btn.textContent = 'Отправить';
}
}
document.getElementById('btn-contact-ruslan').addEventListener('click', openModal);
overlay.addEventListener('click', e => { if (e.target === overlay) window._closeContact(); });
document.addEventListener('keydown', e => { if (e.key === 'Escape') window._closeContact(); });
})();
</script>
</body>
</html>
+191
View File
@@ -0,0 +1,191 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Новости MONT</title>
<meta name="description" content="Последние новости дистрибьютора MONT — вендоры, продукты, партнёрские программы." />
<meta name="robots" content="index, follow" />
<link rel="canonical" href="{{ base_url }}/news" />
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg" />
<link rel="icon" type="image/png" href="/static/favicon.png" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700;800&display=swap" rel="stylesheet">
<style>
:root { --brand: #1f4ea3; --brand2: #3978e0; --bg: #eef4ff; }
* { box-sizing: border-box; }
body { margin: 0; font-family: Manrope, sans-serif; background: var(--bg); color: #15203b; min-height: 100vh; }
.wrap { width: min(900px, calc(100% - 32px)); margin: 0 auto; padding: 28px 0 60px; }
.back-btn {
display: inline-flex; align-items: center; gap: 6px;
font-size: 13px; font-weight: 700; color: var(--brand);
text-decoration: none; margin-bottom: 24px;
padding: 8px 16px; border-radius: 999px;
background: #fff; border: 1px solid #c8d8f7; transition: .15s;
}
.back-btn:hover { background: #eef4ff; transform: translateX(-2px); }
h1 { font-size: 28px; font-weight: 800; color: #1a3e79; margin: 0 0 24px; }
.top3 { margin-bottom: 32px; }
.top3-label {
font-size: 11px; font-weight: 800; letter-spacing: 1.2px;
text-transform: uppercase; color: #7a9bc0; margin-bottom: 14px;
}
.top3-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
.top3-card {
background: #fff; border-radius: 16px;
border: 1px solid #dae6ff;
box-shadow: 0 6px 20px rgba(16,43,95,.08);
overflow: hidden; text-decoration: none; color: inherit;
transition: .18s ease; display: flex; flex-direction: column;
}
.top3-card:hover { transform: translateY(-3px); box-shadow: 0 12px 32px rgba(16,43,95,.14); }
.top3-card img { width: 100%; height: 160px; object-fit: cover; display: block; }
.top3-card .no-img { width: 100%; height: 100px; background: linear-gradient(135deg, #e8f0ff, #d0e2ff); display: flex; align-items: center; justify-content: center; }
.top3-card .no-img-icon { font-size: 32px; opacity: .4; }
.top3-card-body { padding: 14px 16px 18px; flex: 1; display: flex; flex-direction: column; }
.top3-card-date { font-size: 11px; color: #9ab0d0; font-weight: 600; margin-bottom: 6px; }
.top3-card-title { font-size: 14px; font-weight: 800; color: #1a3e79; line-height: 1.4; flex: 1; margin: 0 0 12px; }
.top3-card-excerpt { font-size: 12px; color: #5a7090; line-height: 1.55; margin: 0 0 12px; flex: 1;
display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.read-more { font-size: 12px; font-weight: 700; color: var(--brand2); }
.divider { border: none; border-top: 2px solid #e0eaff; margin: 0 0 24px; }
.rest-label {
font-size: 11px; font-weight: 800; letter-spacing: 1.2px;
text-transform: uppercase; color: #7a9bc0; margin-bottom: 16px;
}
.news-row {
background: #fff; border-radius: 14px; border: 1px solid #dae6ff;
display: flex; gap: 0; overflow: hidden; margin-bottom: 12px;
text-decoration: none; color: inherit;
transition: .18s ease; box-shadow: 0 4px 14px rgba(16,43,95,.06);
}
.news-row:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(16,43,95,.12); }
.news-row img { width: 120px; height: 90px; object-fit: cover; flex-shrink: 0; }
.news-row .no-img-row { width: 80px; flex-shrink: 0; background: linear-gradient(135deg, #e8f0ff, #d0e2ff); }
.news-row-body { padding: 12px 16px; display: flex; flex-direction: column; justify-content: center; }
.news-row-date { font-size: 11px; color: #9ab0d0; font-weight: 600; margin-bottom: 4px; }
.news-row-title { font-size: 14px; font-weight: 800; color: #1a3e79; line-height: 1.35; margin: 0 0 6px; }
.news-row-excerpt { font-size: 12px; color: #5a7090; line-height: 1.5; margin: 0;
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.empty { text-align: center; padding: 60px 0; color: #9ab0d0; font-size: 15px; }
.pagination { display: flex; justify-content: center; align-items: center; gap: 6px; margin-top: 28px; flex-wrap: wrap; }
.pagination a, .pagination span {
display: inline-flex; align-items: center; justify-content: center;
width: 36px; height: 36px; border-radius: 10px;
font-size: 13px; font-weight: 700; text-decoration: none;
border: 1px solid #c8d8f7; background: #fff; color: #1f4ea3; transition: .15s;
}
.pagination a:hover { background: #eef4ff; }
.pagination span.current { background: linear-gradient(135deg,#1f4ea3,#3978e0); color: #fff; border-color: transparent; }
.pagination span.dots { background: none; border-color: transparent; color: #9ab0d0; }
@media (max-width: 700px) {
.top3-grid { grid-template-columns: 1fr; }
.news-row img { width: 90px; height: 70px; }
}
</style>
</head>
<body>
<div class="wrap">
<a href="/" class="back-btn">← На главную</a>
<h1>Новости MONT</h1>
{% if articles %}
{% if page == 1 %}
{% set top3 = articles[:3] %}
{% set rest = articles[3:] %}
<div class="top3">
<div class="top3-label">Последние новости</div>
<div class="top3-grid">
{% for a in top3 %}
<a class="top3-card" href="/news/{{ a.slug }}">
{% if a.image %}
<img src="/static/{{ a.image }}" alt="{{ a.title }}" />
{% else %}
<div class="no-img"><span class="no-img-icon">📰</span></div>
{% endif %}
<div class="top3-card-body">
<div class="top3-card-date">{{ a.created_at[8:10] }}.{{ a.created_at[5:7] }}.{{ a.created_at[:4] }}</div>
<h2 class="top3-card-title">{{ a.title }}</h2>
<p class="top3-card-excerpt">{{ a.body | striptags | truncate(180, true, '...') }}</p>
<span class="read-more">Читать →</span>
</div>
</a>
{% endfor %}
</div>
</div>
{% if rest %}
<hr class="divider" />
<div class="rest-label">Ещё новости</div>
{% for a in rest %}
<a class="news-row" href="/news/{{ a.slug }}">
{% if a.image %}
<img src="/static/{{ a.image }}" alt="{{ a.title }}" />
{% else %}
<div class="no-img-row"></div>
{% endif %}
<div class="news-row-body">
<div class="news-row-date">{{ a.created_at[8:10] }}.{{ a.created_at[5:7] }}.{{ a.created_at[:4] }}</div>
<div class="news-row-title">{{ a.title }}</div>
<p class="news-row-excerpt">{{ a.body | striptags | truncate(160, true, '...') }}</p>
</div>
</a>
{% endfor %}
{% endif %}
{% else %}
<hr class="divider" />
<div class="rest-label">Новости — страница {{ page }}</div>
{% for a in articles %}
<a class="news-row" href="/news/{{ a.slug }}">
{% if a.image %}
<img src="/static/{{ a.image }}" alt="{{ a.title }}" />
{% else %}
<div class="no-img-row"></div>
{% endif %}
<div class="news-row-body">
<div class="news-row-date">{{ a.created_at[8:10] }}.{{ a.created_at[5:7] }}.{{ a.created_at[:4] }}</div>
<div class="news-row-title">{{ a.title }}</div>
<p class="news-row-excerpt">{{ a.body | striptags | truncate(160, true, '...') }}</p>
</div>
</a>
{% endfor %}
{% endif %}
{% else %}
<div class="empty">Новостей пока нет</div>
{% endif %}
{% if total_pages > 1 %}
<div class="pagination">
{% if page > 1 %}
<a href="?page={{ page - 1 }}">&#8249;</a>
{% endif %}
{% for p in range(1, total_pages + 1) %}
{% if p == page %}
<span class="current">{{ p }}</span>
{% elif p == 1 or p == total_pages or (p >= page - 2 and p <= page + 2) %}
<a href="?page={{ p }}">{{ p }}</a>
{% elif p == page - 3 or p == page + 3 %}
<span class="dots"></span>
{% endif %}
{% endfor %}
{% if page < total_pages %}
<a href="?page={{ page + 1 }}">&#8250;</a>
{% endif %}
</div>
{% endif %}
</div>
</body>
</html>
+194 -9
View File
@@ -3,15 +3,15 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ vendor.name }} — вендор МОНТ | матрица продуктов MONT</title>
<meta name="description" content="{{ vendor.description[:160] if vendor.description else 'Вендор ' + vendor.name + ' в корзине продуктов дистрибьютора МОНТ. Продукты, категории, ссылки.' }}" />
<title>{{ vendor.name }} — вендор MONT | матрица продуктов MONT</title>
<meta name="description" content="{{ vendor.description[:160] if vendor.description else 'Вендор ' + vendor.name + ' в корзине продуктов дистрибьютора MONT. Продукты, категории, ссылки.' }}" />
<meta name="robots" content="index, follow" />
<link rel="canonical" href="{{ canonical_url }}" />
<meta property="og:type" content="website" />
<meta property="og:url" content="{{ canonical_url }}" />
<meta property="og:title" content="{{ vendor.name }} — вендор МОНТ" />
<meta property="og:description" content="{{ vendor.description[:200] if vendor.description else 'Вендор ' + vendor.name + ' в корзине продуктов дистрибьютора МОНТ.' }}" />
<meta property="og:title" content="{{ vendor.name }} — вендор MONT" />
<meta property="og:description" content="{{ vendor.description[:200] if vendor.description else 'Вендор ' + vendor.name + ' в корзине продуктов дистрибьютора MONT.' }}" />
{% if vendor.logo %}<meta property="og:image" content="{{ base_url }}/static/{{ vendor.logo }}" />{% endif %}
<script type="application/ld+json">
@@ -24,12 +24,54 @@
{% if vendor.logo %}"logo": "{{ base_url }}/static/{{ vendor.logo }}",{% endif %}
"distributor": {
"@type": "Organization",
"name": "МОНТ",
"name": "MONT",
"url": "{{ base_url }}"
}
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Где купить {{ vendor.name }} в России?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Продукты {{ vendor.name }} можно приобрести через официального дистрибьютора MONT (MONT). Главный офис — Москва, филиал — Казань; партнёры работают по всей России. Для заказа напишите BDM MONT Максиму Шамову: mshamov@mont.com или на общую почту mont@mont.com."
}
},
{
"@type": "Question",
"name": "Как стать партнёром {{ vendor.name }} через MONT?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Для получения партнёрского статуса {{ vendor.name }} через дистрибьютора MONT свяжитесь с BDM Максимом Шамовым: mshamov@mont.com или mont@mont.com. MONT является официальным дистрибьютором и помогает партнёрам получить авторизацию, обучение и техническую поддержку по продуктам {{ vendor.name }}."
}
},
{
"@type": "Question",
"name": "Какие продукты {{ vendor.name }} доступны через MONT?",
"acceptedAnswer": {
"@type": "Answer",
"text": "{% if products %}В корзине MONT представлены следующие продукты {{ vendor.name }}: {{ products | map(attribute='name') | join(', ') }}.{% else %}MONT предлагает широкую линейку продуктов {{ vendor.name }}. Актуальный список доступен на странице вендора в корзине MONT.{% endif %} Для уточнения наличия и цен обращайтесь к дистрибьютору."
}
},
{
"@type": "Question",
"name": "Есть ли официальный дистрибьютор {{ vendor.name }} в Казани?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Да. MONT имеет главный офис в Москве и филиал в Казани. Партнёры работают во всех крупных городах России. Свяжитесь с BDM MONT Максимом Шамовым: mshamov@mont.com или mont@mont.com."
}
}
]
}
</script>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg" />
<link rel="icon" type="image/png" href="/static/favicon.png" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -127,6 +169,36 @@
}
.back-btn:hover { background: #eef4ff; transform: translateX(-2px); }
.seo-block {
background: #fff;
border-radius: 16px;
border: 1px solid #dae6ff;
padding: 24px 28px;
margin-top: 20px;
}
.seo-block h2 { font-size: 17px; font-weight: 800; color: #1a3e79; margin: 0 0 10px; }
.seo-block p { font-size: 14px; color: #3a4f6e; line-height: 1.7; margin: 0 0 10px; }
.seo-block p:last-child { margin-bottom: 0; }
.cities-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.city-tag {
font-size: 12px; font-weight: 600; padding: 4px 12px;
border-radius: 999px; background: #f0f6ff; color: #22427a;
border: 1px solid #c8dcff;
}
.faq-list { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 10px; }
.faq-item { border: 1px solid #dae6ff; border-radius: 12px; overflow: hidden; }
.faq-q {
width: 100%; text-align: left; background: #f5f9ff;
border: none; padding: 14px 18px; cursor: pointer;
font-size: 14px; font-weight: 700; color: #1a3e79;
font-family: Manrope, sans-serif; display: flex; justify-content: space-between; align-items: center;
}
.faq-q:hover { background: #eaf2ff; }
.faq-q .arr { transition: transform .2s; font-size: 12px; color: #7a9bd0; }
.faq-q.open .arr { transform: rotate(180deg); }
.faq-a { display: none; padding: 12px 18px 16px; font-size: 14px; color: #3a4f6e; line-height: 1.7; }
.faq-a.open { display: block; }
@media (max-width: 640px) {
.vendor-card { flex-direction: column; padding: 20px; }
.vendor-logo-box { width: 100%; height: 100px; }
@@ -136,10 +208,10 @@
</head>
<body>
<div class="wrap">
<a href="/" class="back-btn">← Все вендоры МОНТ</a>
<a href="/" class="back-btn">← Все вендоры MONT</a>
<nav class="breadcrumb" aria-label="breadcrumb">
<a href="/">Корзина МОНТ</a> &rsaquo; {{ vendor.name }}
<a href="/">Корзина MONT</a> &rsaquo; {{ vendor.name }}
</nav>
<div class="vendor-card">
@@ -157,7 +229,7 @@
{% endif %}
<div class="vendor-links">
{% if vendor.mont_page %}
<a class="vlink mont" href="{{ vendor.mont_page }}" target="_blank" rel="noopener">Страница на МОНТ</a>
<a class="vlink mont" href="{{ vendor.mont_page }}" target="_blank" rel="noopener">Страница на MONT</a>
{% endif %}
{% if vendor.website %}
<a class="vlink site" href="{{ vendor.website }}" target="_blank" rel="noopener">Официальный сайт ↗</a>
@@ -168,7 +240,7 @@
{% if products %}
<div class="products-box">
<h2 class="section-title">Продукты {{ vendor.name }} в корзине МОНТ</h2>
<h2 class="section-title">Продукты {{ vendor.name }} в корзине MONT</h2>
<div class="products-grid">
{% for p in products %}
{% if p.url %}
@@ -189,6 +261,119 @@
{% endfor %}
</div>
{% endif %}
<div class="seo-block">
<h2>Официальный дистрибьютор {{ vendor.name }} в России — MONT</h2>
<p>
MONT (MONT) является официальным дистрибьютором {{ vendor.name }} на территории России.
Через MONT доступны лицензии, техническая поддержка и партнёрские программы
{{ vendor.name }}. Дистрибьютор работает с юридическими лицами, системными
интеграторами и реселлерами по всей стране.
</p>
<p>
Купить {{ vendor.name }} или получить консультацию можно через партнёров MONT в
вашем регионе. Для связи пишите на
<a href="mailto:mont@mont.com" style="color:var(--brand);font-weight:600;">mont@mont.com</a>
или напрямую BDM MONT Максиму Шамову —
<a href="mailto:mshamov@mont.com" style="color:var(--brand);font-weight:600;">mshamov@mont.com</a>.
Заявки принимаются из всех городов России.
</p>
<div class="cities-row">
<span class="city-tag">{{ vendor.name }} Москва</span>
<span class="city-tag">{{ vendor.name }} Казань</span>
<span class="city-tag">{{ vendor.name }} Санкт-Петербург</span>
<span class="city-tag">{{ vendor.name }} Екатеринбург</span>
<span class="city-tag">{{ vendor.name }} Новосибирск</span>
<span class="city-tag">{{ vendor.name }} Краснодар</span>
<span class="city-tag">{{ vendor.name }} Нижний Новгород</span>
<span class="city-tag">{{ vendor.name }} Ростов-на-Дону</span>
</div>
</div>
<div class="seo-block">
<h2>Часто задаваемые вопросы о {{ vendor.name }} и MONT</h2>
<ul class="faq-list" id="faqList">
<li class="faq-item">
<button class="faq-q" type="button">
Где купить {{ vendor.name }} в России?
<span class="arr"></span>
</button>
<div class="faq-a">
Продукты {{ vendor.name }} доступны через официального дистрибьютора MONT.
Главный офис MONT находится в Москве, филиал — в Казани; партнёры работают
в Санкт-Петербурге, Екатеринбурге, Новосибирске, Краснодаре и других городах.
Для оформления заказа напишите BDM MONT Максиму Шамову:
<a href="mailto:mshamov@mont.com" style="color:var(--brand);font-weight:600;">mshamov@mont.com</a>
или на общую почту
<a href="mailto:mont@mont.com" style="color:var(--brand);font-weight:600;">mont@mont.com</a>.
</div>
</li>
<li class="faq-item">
<button class="faq-q" type="button">
Как стать партнёром {{ vendor.name }} через MONT?
<span class="arr"></span>
</button>
<div class="faq-a">
MONT помогает компаниям получить партнёрский статус {{ vendor.name }}:
авторизацию, доступ к обучению и технической поддержке. Для начала
сотрудничества свяжитесь с BDM MONT Максимом Шамовым:
<a href="mailto:mshamov@mont.com" style="color:var(--brand);font-weight:600;">mshamov@mont.com</a>
или напишите на общую почту
<a href="mailto:mont@mont.com" style="color:var(--brand);font-weight:600;">mont@mont.com</a>
специалисты подберут подходящий партнёрский уровень и помогут пройти все этапы авторизации.
</div>
</li>
<li class="faq-item">
<button class="faq-q" type="button">
Какие продукты {{ vendor.name }} представлены в корзине MONT?
<span class="arr"></span>
</button>
<div class="faq-a">
{% if products %}
В корзине MONT представлены следующие продукты {{ vendor.name }}:
{{ products | map(attribute='name') | join(', ') }}.
Актуальность наличия и цены уточняйте у BDM MONT Максима Шамова:
<a href="mailto:mshamov@mont.com" style="color:var(--brand);font-weight:600;">mshamov@mont.com</a>.
{% else %}
MONT предлагает актуальную линейку продуктов {{ vendor.name }}.
Для уточнения наличия и условий поставки напишите BDM MONT Максиму Шамову:
<a href="mailto:mshamov@mont.com" style="color:var(--brand);font-weight:600;">mshamov@mont.com</a>.
{% endif %}
</div>
</li>
<li class="faq-item">
<button class="faq-q" type="button">
Есть ли официальный дистрибьютор {{ vendor.name }} в Казани?
<span class="arr"></span>
</button>
<div class="faq-a">
Да. MONT имеет филиал в Казани и главный офис в Москве — компания является
одним из ведущих дистрибьюторов ПО в России. Партнёры MONT работают во
всех федеральных округах. Для заказа {{ vendor.name }} в Казани или другом
регионе свяжитесь с BDM MONT Максимом Шамовым:
<a href="mailto:mshamov@mont.com" style="color:var(--brand);font-weight:600;">mshamov@mont.com</a>
или на общую почту
<a href="mailto:mont@mont.com" style="color:var(--brand);font-weight:600;">mont@mont.com</a>.
</div>
</li>
</ul>
</div>
</div>
<script>
document.getElementById('faqList').addEventListener('click', function(e) {
const btn = e.target.closest('.faq-q');
if (!btn) return;
const isOpen = btn.classList.contains('open');
document.querySelectorAll('.faq-q.open').forEach(b => {
b.classList.remove('open');
b.nextElementSibling.classList.remove('open');
});
if (!isOpen) {
btn.classList.add('open');
btn.nextElementSibling.classList.add('open');
}
});
</script>
</body>
</html>