first
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.idea
|
||||
.venv
|
||||
data
|
||||
16
main.py
Normal file
16
main.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# This is a sample Python script.
|
||||
|
||||
# Press Shift+F10 to execute it or replace it with your code.
|
||||
# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings.
|
||||
|
||||
|
||||
def print_hi(name):
|
||||
# Use a breakpoint in the code line below to debug your script.
|
||||
print(f'Hi, {name}') # Press Ctrl+F8 to toggle the breakpoint.
|
||||
|
||||
|
||||
# Press the green button in the gutter to run the script.
|
||||
if __name__ == '__main__':
|
||||
print_hi('PyCharm')
|
||||
|
||||
# See PyCharm help at https://www.jetbrains.com/help/pycharm/
|
||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
fastapi==0.104.1
|
||||
uvicorn==0.24.0
|
||||
python-multipart==0.0.6
|
||||
jinja2==3.1.2
|
||||
389
server.py
Normal file
389
server.py
Normal file
@@ -0,0 +1,389 @@
|
||||
# server.py
|
||||
import re
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from fastapi import FastAPI, Request, Form
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from typing import Optional
|
||||
|
||||
# ==================== КОНФИГУРАЦИЯ ====================
|
||||
HOST = "0.0.0.0"
|
||||
PORT = 8000
|
||||
DEBUG = True
|
||||
|
||||
BASE_DIR = Path(__file__).parent
|
||||
TEMPLATES_DIR = BASE_DIR / "templates"
|
||||
STATIC_DIR = BASE_DIR / "static"
|
||||
DATA_DIR = BASE_DIR / "data"
|
||||
|
||||
# Создаем необходимые папки
|
||||
TEMPLATES_DIR.mkdir(exist_ok=True)
|
||||
STATIC_DIR.mkdir(exist_ok=True)
|
||||
DATA_DIR.mkdir(exist_ok=True)
|
||||
|
||||
# Создаем подпапки для статики
|
||||
(STATIC_DIR / "css").mkdir(exist_ok=True)
|
||||
(STATIC_DIR / "js").mkdir(exist_ok=True)
|
||||
|
||||
# ==================== ИНИЦИАЛИЗАЦИЯ FASTAPI ====================
|
||||
app = FastAPI(title="БС Патриот - Регистрация на круглый стол", debug=DEBUG)
|
||||
|
||||
# Подключаем статические файлы
|
||||
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
|
||||
|
||||
|
||||
# ==================== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ====================
|
||||
def save_registration_to_file(data: dict) -> dict:
|
||||
"""Сохраняет заявку в JSON-файл"""
|
||||
filepath = DATA_DIR / "registrations.json"
|
||||
|
||||
registrations = []
|
||||
if filepath.exists():
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
registrations = json.load(f)
|
||||
except (json.JSONDecodeError, IOError):
|
||||
registrations = []
|
||||
|
||||
new_record = {
|
||||
"id": len(registrations) + 1,
|
||||
"created_at": datetime.now().isoformat(),
|
||||
"fullname": data.get("fullname"),
|
||||
"email": data.get("email"),
|
||||
"phone": data.get("phone"),
|
||||
"company": data.get("company"),
|
||||
"businessSize": data.get("businessSize", ""),
|
||||
"message": data.get("message", ""),
|
||||
"status": "pending"
|
||||
}
|
||||
|
||||
registrations.append(new_record)
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
json.dump(registrations, f, ensure_ascii=False, indent=2)
|
||||
|
||||
return new_record
|
||||
|
||||
|
||||
def get_html_content() -> str:
|
||||
"""Читает HTML файл или возвращает fallback"""
|
||||
html_path = TEMPLATES_DIR / "index.html"
|
||||
if html_path.exists():
|
||||
with open(html_path, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
else:
|
||||
return get_fallback_html()
|
||||
|
||||
|
||||
def get_fallback_html() -> str:
|
||||
"""Fallback HTML если файл шаблона не найден"""
|
||||
return """<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Круглый стол | БС «Патриот»</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||||
<style>
|
||||
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
||||
body {{ font-family: 'Inter', Arial, sans-serif; background: #fefcf5; color: #1a1f2b; line-height: 1.6; }}
|
||||
.container {{ max-width: 1200px; margin: 0 auto; padding: 40px 20px; text-align: center; }}
|
||||
h1 {{ color: #c52a1c; margin-bottom: 20px; font-size: 2.5rem; }}
|
||||
.badge {{ background: rgba(197,42,28,0.12); color: #c52a1c; padding: 6px 18px; border-radius: 40px; display: inline-block; margin-bottom: 20px; }}
|
||||
.btn {{ display: inline-block; background: #c52a1c; color: white; padding: 12px 30px; text-decoration: none; border-radius: 50px; margin-top: 20px; border: none; cursor: pointer; font-family: inherit; }}
|
||||
.form-card {{ background: white; max-width: 500px; margin: 40px auto; padding: 30px; border-radius: 20px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }}
|
||||
input, select, textarea {{ width: 100%; padding: 12px; margin: 10px 0; border: 1px solid #ddd; border-radius: 8px; font-family: inherit; }}
|
||||
button {{ background: #c52a1c; color: white; padding: 12px 30px; border: none; border-radius: 50px; cursor: pointer; font-size: 16px; font-family: inherit; width: 100%; }}
|
||||
.success {{ color: #1f8a4c; background: #e0f2e9; padding: 10px; border-radius: 8px; margin-top: 10px; }}
|
||||
.error {{ color: #c52a1c; background: #ffe8e5; padding: 10px; border-radius: 8px; margin-top: 10px; }}
|
||||
.stats {{ display: flex; justify-content: center; gap: 30px; margin: 30px 0; flex-wrap: wrap; }}
|
||||
.stat {{ text-align: center; }}
|
||||
.stat h4 {{ font-size: 2rem; color: #c52a1c; }}
|
||||
footer {{ background: #0a1a2f; color: #b9c7d9; padding: 30px; margin-top: 40px; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="badge"><i class="fas fa-shield-alt"></i> Закрытый клуб предпринимателей</div>
|
||||
<h1>Круглый стол с бизнес-сообществом <span style="color:#c52a1c;">«Патриот»</span></h1>
|
||||
<p><i class="far fa-calendar-check"></i> <strong>26 апреля 2026, начало в 11:00</strong></p>
|
||||
<p><i class="fas fa-lock"></i> Вход по рекомендации</p>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat"><h4>557+</h4><p>участников</p></div>
|
||||
<div class="stat"><h4>125+</h4><p>выездов в зону СВО</p></div>
|
||||
<div class="stat"><h4>30+</h4><p>комитетов</p></div>
|
||||
</div>
|
||||
|
||||
<div class="form-card">
|
||||
<h2>Регистрация на мероприятие</h2>
|
||||
<form id="registrationForm">
|
||||
<input type="text" name="fullname" id="fullname" placeholder="ФИО *" required>
|
||||
<input type="email" name="email" id="email" placeholder="E-mail *" required>
|
||||
<input type="tel" name="phone" id="phone" placeholder="Телефон *" required>
|
||||
<input type="text" name="company" id="company" placeholder="Компания *" required>
|
||||
<select name="businessSize" id="businessSize">
|
||||
<option value="">Годовой оборот (опционально)</option>
|
||||
<option value="до 50 млн руб.">до 50 млн руб.</option>
|
||||
<option value="50–500 млн руб.">50–500 млн руб.</option>
|
||||
<option value="500 млн – 1 млрд руб.">500 млн – 1 млрд руб.</option>
|
||||
<option value="более 1 млрд руб.">более 1 млрд руб.</option>
|
||||
</select>
|
||||
<textarea name="message" id="message" rows="3" placeholder="Коротко о себе"></textarea>
|
||||
<label>
|
||||
<input type="checkbox" name="agree" id="agreeCheck" value="true" required>
|
||||
Я соглашаюсь с условиями *
|
||||
</label>
|
||||
<button type="submit">Отправить заявку</button>
|
||||
<div id="formFeedback"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<p>Бизнес-сообщество «Предприниматели Патриоты»</p>
|
||||
<p>© 2026 Круглый стол с БС «Патриот»</p>
|
||||
</footer>
|
||||
<script>
|
||||
const form = document.getElementById('registrationForm');
|
||||
form.addEventListener('submit', async (e) => {{
|
||||
e.preventDefault();
|
||||
const feedback = document.getElementById('formFeedback');
|
||||
const formData = new FormData(form);
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
const originalText = submitBtn.innerHTML;
|
||||
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '<i class="fas fa-spinner fa-pulse"></i> Отправка...';
|
||||
|
||||
try {{
|
||||
const response = await fetch('/api/register', {{
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}});
|
||||
const data = await response.json();
|
||||
if (data.success) {{
|
||||
feedback.innerHTML = '<div class="success">✅ ' + data.message + '</div>';
|
||||
form.reset();
|
||||
}} else {{
|
||||
feedback.innerHTML = '<div class="error">❌ ' + data.message + '</div>';
|
||||
}}
|
||||
}} catch (err) {{
|
||||
console.error('Error:', err);
|
||||
feedback.innerHTML = '<div class="error">❌ Ошибка соединения</div>';
|
||||
}} finally {{
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = originalText;
|
||||
setTimeout(() => {{ feedback.innerHTML = ''; }}, 5000);
|
||||
}}
|
||||
}});
|
||||
</script>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
|
||||
# ==================== МАРШРУТЫ ====================
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def get_landing_page():
|
||||
"""Главная страница - посадочный лендинг мероприятия"""
|
||||
return HTMLResponse(content=get_html_content(), status_code=200)
|
||||
|
||||
|
||||
@app.post("/api/register")
|
||||
async def register(
|
||||
fullname: str = Form(...),
|
||||
email: str = Form(...),
|
||||
phone: str = Form(...),
|
||||
company: str = Form(...),
|
||||
businessSize: Optional[str] = Form(None),
|
||||
message: Optional[str] = Form(None),
|
||||
agree: Optional[str] = Form(None)
|
||||
):
|
||||
"""API-эндпоинт для регистрации участников"""
|
||||
|
||||
print(f"Received data: fullname={fullname}, email={email}, phone={phone}, company={company}, agree={agree}")
|
||||
|
||||
# Валидация
|
||||
if not agree or agree != "true":
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"success": False, "message": "Необходимо согласие с условиями"}
|
||||
)
|
||||
|
||||
if not fullname or len(fullname.strip()) < 2:
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"success": False, "message": "Введите корректное ФИО"}
|
||||
)
|
||||
|
||||
email_pattern = r'^[^\s@]+@([^\s@.,]+\.)+[^\s@.,]{2,}$'
|
||||
if not re.match(email_pattern, email):
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"success": False, "message": "Введите корректный email"}
|
||||
)
|
||||
|
||||
digits_only = re.sub(r'\D', '', phone)
|
||||
if len(digits_only) < 10:
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"success": False, "message": "Введите корректный номер телефона (минимум 10 цифр)"}
|
||||
)
|
||||
|
||||
if not company or len(company.strip()) < 1:
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"success": False, "message": "Укажите название компании"}
|
||||
)
|
||||
|
||||
data = {
|
||||
"fullname": fullname.strip(),
|
||||
"email": email.strip().lower(),
|
||||
"phone": phone.strip(),
|
||||
"company": company.strip(),
|
||||
"businessSize": businessSize if businessSize else "",
|
||||
"message": message.strip() if message else "",
|
||||
}
|
||||
|
||||
try:
|
||||
saved = save_registration_to_file(data)
|
||||
print(f"Saved registration: {saved}")
|
||||
return JSONResponse(content={
|
||||
"success": True,
|
||||
"message": "Заявка успешно отправлена! Менеджер сообщества свяжется с вами в ближайшее время."
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"Error saving registration: {e}")
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"success": False, "message": f"Ошибка сервера: {str(e)}"}
|
||||
)
|
||||
|
||||
|
||||
@app.get("/admin/registrations", response_class=HTMLResponse)
|
||||
async def admin_registrations():
|
||||
"""Админка для просмотра заявок"""
|
||||
filepath = DATA_DIR / "registrations.json"
|
||||
registrations = []
|
||||
if filepath.exists():
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
registrations = json.load(f)
|
||||
except:
|
||||
registrations = []
|
||||
|
||||
registrations.sort(key=lambda x: x.get("created_at", ""), reverse=True)
|
||||
|
||||
# Используем обычное форматирование строки без .format()
|
||||
html = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Админка | Заявки на круглый стол</title>
|
||||
<style>
|
||||
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
||||
body {{ font-family: 'Inter', Arial, sans-serif; margin: 40px; background: #f5f5f5; }}
|
||||
h1 {{ color: #c52a1c; margin-bottom: 20px; }}
|
||||
.container {{ max-width: 1400px; margin: 0 auto; background: white; border-radius: 24px; padding: 24px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }}
|
||||
table {{ border-collapse: collapse; width: 100%; margin-top: 20px; }}
|
||||
th, td {{ border: 1px solid #ddd; padding: 12px; text-align: left; vertical-align: top; }}
|
||||
th {{ background: #c52a1c; color: white; position: sticky; top: 0; }}
|
||||
tr:nth-child(even) {{ background: #f9f9f9; }}
|
||||
.status-pending {{ color: #e67e22; font-weight: bold; }}
|
||||
.count {{ margin: 20px 0; font-size: 1.1rem; }}
|
||||
.back-link {{ display: inline-block; margin-top: 20px; color: #c52a1c; text-decoration: none; font-weight: 600; }}
|
||||
.back-link:hover {{ text-decoration: underline; }}
|
||||
@media (max-width: 768px) {{
|
||||
body {{ margin: 20px; }}
|
||||
.container {{ padding: 16px; overflow-x: auto; }}
|
||||
th, td {{ padding: 8px; font-size: 12px; }}
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>📋 Заявки на круглый стол «Патриот»</h1>
|
||||
<div class="count">📊 Всего заявок: <strong>{len(registrations)}</strong></div>
|
||||
<div style="overflow-x: auto;">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Дата</th>
|
||||
<th>ФИО</th>
|
||||
<th>Email</th>
|
||||
<th>Телефон</th>
|
||||
<th>Компания</th>
|
||||
<th>Оборот</th>
|
||||
<th>Сообщение</th>
|
||||
<th>Статус</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
"""
|
||||
|
||||
for reg in registrations:
|
||||
created = reg.get("created_at", "")[:16].replace("T", " ")
|
||||
status_class = "status-pending" if reg.get('status') == 'pending' else ""
|
||||
message_text = reg.get('message', '—')[:100]
|
||||
if len(reg.get('message', '')) > 100:
|
||||
message_text += "..."
|
||||
|
||||
html += f"""
|
||||
<tr>
|
||||
<td>{reg.get('id', '—')}</td>
|
||||
<td>{created}</td>
|
||||
<td>{reg.get('fullname', '—')}</td>
|
||||
<td>{reg.get('email', '—')}</td>
|
||||
<td>{reg.get('phone', '—')}</td>
|
||||
<td>{reg.get('company', '—')}</td>
|
||||
<td>{reg.get('businessSize', '—')}</td>
|
||||
<td>{message_text}</td>
|
||||
<td class="{status_class}">{reg.get('status', 'pending')}</td>
|
||||
</tr>
|
||||
"""
|
||||
|
||||
html += """
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<a href="/" class="back-link">← Вернуться на главную</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
return HTMLResponse(content=html, status_code=200)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
"""Health check для хостинга"""
|
||||
return {"status": "ok", "timestamp": datetime.now().isoformat()}
|
||||
|
||||
|
||||
# ==================== ЗАПУСК ====================
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
print("=" * 60)
|
||||
print("🚀 Сервер для посадочной страницы БС «Патриот»")
|
||||
print(f"📁 Шаблоны: {TEMPLATES_DIR}")
|
||||
print(f"📁 Статика: {STATIC_DIR}")
|
||||
print(f"📁 Данные: {DATA_DIR}")
|
||||
print("=" * 60)
|
||||
print(f"✨ Главная страница: http://localhost:{PORT}")
|
||||
print(f"🔧 Админка: http://localhost:{PORT}/admin/registrations")
|
||||
print("=" * 60)
|
||||
print("💡 Для остановки сервера нажмите Ctrl+C")
|
||||
print("=" * 60)
|
||||
|
||||
uvicorn.run(
|
||||
app,
|
||||
host=HOST,
|
||||
port=PORT,
|
||||
log_level="info"
|
||||
)
|
||||
108
server_simple.py
Normal file
108
server_simple.py
Normal file
@@ -0,0 +1,108 @@
|
||||
# server_simple.py - упрощенная версия без Jinja2
|
||||
import re
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from fastapi import FastAPI, Form
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
# ==================== КОНФИГУРАЦИЯ ====================
|
||||
HOST = "0.0.0.0"
|
||||
PORT = 8000
|
||||
|
||||
BASE_DIR = Path(__file__).parent
|
||||
STATIC_DIR = BASE_DIR / "static"
|
||||
DATA_DIR = BASE_DIR / "data"
|
||||
|
||||
STATIC_DIR.mkdir(exist_ok=True)
|
||||
DATA_DIR.mkdir(exist_ok=True)
|
||||
(STATIC_DIR / "css").mkdir(exist_ok=True)
|
||||
(STATIC_DIR / "js").mkdir(exist_ok=True)
|
||||
|
||||
app = FastAPI()
|
||||
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
|
||||
|
||||
# Читаем HTML файл
|
||||
HTML_FILE = BASE_DIR / "templates" / "index.html"
|
||||
|
||||
|
||||
def save_registration_to_file(data: dict) -> dict:
|
||||
filepath = DATA_DIR / "registrations.json"
|
||||
registrations = []
|
||||
if filepath.exists():
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
registrations = json.load(f)
|
||||
except:
|
||||
registrations = []
|
||||
|
||||
new_record = {
|
||||
"id": len(registrations) + 1,
|
||||
"created_at": datetime.now().isoformat(),
|
||||
**data,
|
||||
"status": "pending"
|
||||
}
|
||||
registrations.append(new_record)
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
json.dump(registrations, f, ensure_ascii=False, indent=2)
|
||||
return new_record
|
||||
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def get_landing_page():
|
||||
if HTML_FILE.exists():
|
||||
with open(HTML_FILE, 'r', encoding='utf-8') as f:
|
||||
return HTMLResponse(content=f.read())
|
||||
else:
|
||||
return HTMLResponse(content="<h1>Файл templates/index.html не найден</h1>")
|
||||
|
||||
|
||||
@app.post("/api/register")
|
||||
async def register(
|
||||
fullname: str = Form(...),
|
||||
email: str = Form(...),
|
||||
phone: str = Form(...),
|
||||
company: str = Form(...),
|
||||
businessSize: str = Form(""),
|
||||
message: str = Form(""),
|
||||
agree: bool = Form(False)
|
||||
):
|
||||
if not agree:
|
||||
return JSONResponse(status_code=400, content={"success": False, "message": "Необходимо согласие"})
|
||||
|
||||
data = {
|
||||
"fullname": fullname.strip(),
|
||||
"email": email.strip().lower(),
|
||||
"phone": phone.strip(),
|
||||
"company": company.strip(),
|
||||
"businessSize": businessSize,
|
||||
"message": message.strip(),
|
||||
}
|
||||
|
||||
try:
|
||||
save_registration_to_file(data)
|
||||
return JSONResponse(content={"success": True, "message": "Заявка отправлена!"})
|
||||
except Exception as e:
|
||||
return JSONResponse(status_code=500, content={"success": False, "message": str(e)})
|
||||
|
||||
|
||||
@app.get("/admin/registrations", response_class=HTMLResponse)
|
||||
async def admin_registrations():
|
||||
filepath = DATA_DIR / "registrations.json"
|
||||
registrations = []
|
||||
if filepath.exists():
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
registrations = json.load(f)
|
||||
|
||||
html = "<h1>Заявки</h1><table border='1'>"
|
||||
for r in registrations:
|
||||
html += f"<tr><td>{r.get('fullname')}</td><td>{r.get('email')}</td></tr>"
|
||||
html += "</table><a href='/'>Назад</a>"
|
||||
return HTMLResponse(content=html)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(app, host=HOST, port=PORT)
|
||||
444
static/css/style.css
Normal file
444
static/css/style.css
Normal file
@@ -0,0 +1,444 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: #fefcf5;
|
||||
color: #1a1f2b;
|
||||
line-height: 1.4;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 80px 0;
|
||||
}
|
||||
|
||||
.section-sm {
|
||||
padding: 60px 0;
|
||||
}
|
||||
|
||||
.bg-light {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-gentle {
|
||||
background-color: #f8f6f0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px 32px;
|
||||
border-radius: 60px;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
text-decoration: none;
|
||||
transition: all 0.25s ease;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: none;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #c52a1c;
|
||||
color: white;
|
||||
box-shadow: 0 8px 20px rgba(197, 42, 28, 0.25);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #9e2015;
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 14px 28px rgba(197, 42, 28, 0.35);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
border: 2px solid #c52a1c;
|
||||
color: #c52a1c;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background: #c52a1c;
|
||||
color: white;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2.4rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
background: rgba(197, 42, 28, 0.12);
|
||||
color: #c52a1c;
|
||||
padding: 6px 18px;
|
||||
border-radius: 40px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-weight: 800;
|
||||
font-size: 1.6rem;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.logo span {
|
||||
color: #c52a1c;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
text-decoration: none;
|
||||
color: #1a1f2b;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
color: #c52a1c;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 48px;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
flex: 1.2;
|
||||
}
|
||||
|
||||
.hero-description {
|
||||
font-size: 1.2rem;
|
||||
margin: 24px 0 20px;
|
||||
color: #2e3a48;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.hero-buttons {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hero-info {
|
||||
margin-top: 32px;
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hero-stats {
|
||||
flex: 0.9;
|
||||
background: white;
|
||||
padding: 32px;
|
||||
border-radius: 32px;
|
||||
box-shadow: 0 20px 35px -12px rgba(0,0,0,0.08);
|
||||
border: 1px solid #edeae2;
|
||||
}
|
||||
|
||||
.stats-title {
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
border-bottom: 2px solid #c52a1c;
|
||||
display: inline-block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.stat-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.stat-item h4 {
|
||||
font-size: 2rem;
|
||||
font-weight: 800;
|
||||
color: #c52a1c;
|
||||
}
|
||||
|
||||
.stats-note {
|
||||
font-size: 0.8rem;
|
||||
margin-top: 20px;
|
||||
color: #5e6f8d;
|
||||
}
|
||||
|
||||
.about-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 40px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.about-content {
|
||||
flex: 1.2;
|
||||
}
|
||||
|
||||
.about-content p {
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.values-list {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.values-list span {
|
||||
background: #f4f2ec;
|
||||
padding: 6px 14px;
|
||||
border-radius: 40px;
|
||||
}
|
||||
|
||||
.about-quote {
|
||||
flex: 0.8;
|
||||
background: #fefaf2;
|
||||
border-radius: 32px;
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.about-quote p {
|
||||
font-style: italic;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.quote-author {
|
||||
font-weight: 600;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
text-align: center;
|
||||
max-width: 700px;
|
||||
margin: 0 auto 40px;
|
||||
}
|
||||
|
||||
.cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 32px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
padding: 28px;
|
||||
border-radius: 28px;
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.03);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
border: 1px solid #efebe3;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-6px);
|
||||
box-shadow: 0 20px 30px -12px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.card i {
|
||||
font-size: 2.2rem;
|
||||
color: #c52a1c;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.agenda-list {
|
||||
background: white;
|
||||
border-radius: 32px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #e9e5db;
|
||||
}
|
||||
|
||||
.agenda-item {
|
||||
display: flex;
|
||||
padding: 20px 28px;
|
||||
border-bottom: 1px solid #efebe3;
|
||||
align-items: flex-start;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.agenda-time {
|
||||
font-weight: 700;
|
||||
min-width: 100px;
|
||||
color: #c52a1c;
|
||||
}
|
||||
|
||||
.stats-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 40px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stats-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stats-info ul {
|
||||
margin-top: 20px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.stats-info li {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.stats-cta {
|
||||
flex: 1;
|
||||
background: #ffffff;
|
||||
border-radius: 32px;
|
||||
padding: 32px;
|
||||
text-align: center;
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.02);
|
||||
}
|
||||
|
||||
.stats-cta i {
|
||||
font-size: 3rem;
|
||||
color: #c52a1c;
|
||||
margin-bottom: 16px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background: white;
|
||||
border-radius: 36px;
|
||||
padding: 40px;
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.05);
|
||||
border: 1px solid #eae5db;
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
input, select, textarea {
|
||||
width: 100%;
|
||||
padding: 14px 20px;
|
||||
border-radius: 60px;
|
||||
border: 1.5px solid #e2dcd0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 1rem;
|
||||
transition: 0.2s;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
input:focus, select:focus, textarea:focus {
|
||||
outline: none;
|
||||
border-color: #c52a1c;
|
||||
box-shadow: 0 0 0 3px rgba(197,42,28,0.1);
|
||||
}
|
||||
|
||||
textarea {
|
||||
border-radius: 28px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.form-message {
|
||||
margin-top: 16px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.success-msg {
|
||||
color: #1f8a4c;
|
||||
background: #e0f2e9;
|
||||
padding: 12px;
|
||||
border-radius: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-msg {
|
||||
color: #c52a1c;
|
||||
background: #ffe8e5;
|
||||
padding: 12px;
|
||||
border-radius: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-note {
|
||||
font-size: 0.75rem;
|
||||
color: #7a7c7f;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
footer {
|
||||
background: #0a1a2f;
|
||||
color: #b9c7d9;
|
||||
padding: 48px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-logo {
|
||||
font-weight: 800;
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: #c9a87c;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
h1 { font-size: 2.4rem; }
|
||||
h2 { font-size: 1.9rem; }
|
||||
.hero { flex-direction: column; }
|
||||
.navbar { flex-direction: column; gap: 16px; }
|
||||
.nav-links { gap: 20px; flex-wrap: wrap; justify-content: center; }
|
||||
.section { padding: 50px 0; }
|
||||
.form-card { padding: 28px; }
|
||||
}
|
||||
|
||||
@media (max-width: 550px) {
|
||||
.container { padding: 0 20px; }
|
||||
.btn { padding: 10px 22px; }
|
||||
}
|
||||
63
static/js/main.js
Normal file
63
static/js/main.js
Normal file
@@ -0,0 +1,63 @@
|
||||
// Обработка формы регистрации
|
||||
const form = document.getElementById('registrationForm');
|
||||
const feedbackDiv = document.getElementById('formFeedback');
|
||||
|
||||
if (form) {
|
||||
form.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (feedbackDiv) {
|
||||
feedbackDiv.innerHTML = '';
|
||||
feedbackDiv.className = 'form-message';
|
||||
}
|
||||
|
||||
const formData = new FormData(form);
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
const originalText = submitBtn.innerHTML;
|
||||
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '<i class="fas fa-spinner fa-pulse"></i> Отправка...';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/register', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (feedbackDiv) {
|
||||
if (data.success) {
|
||||
feedbackDiv.innerHTML = '<div class="success-msg">✅ ' + data.message + '</div>';
|
||||
form.reset();
|
||||
} else {
|
||||
feedbackDiv.innerHTML = '<div class="error-msg">❌ ' + data.message + '</div>';
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error:', err);
|
||||
if (feedbackDiv) {
|
||||
feedbackDiv.innerHTML = '<div class="error-msg">❌ Ошибка соединения. Попробуйте позже.</div>';
|
||||
}
|
||||
} finally {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = originalText;
|
||||
|
||||
setTimeout(() => {
|
||||
if (feedbackDiv) feedbackDiv.innerHTML = '';
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Плавная прокрутка для якорных ссылок
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function(e) {
|
||||
const hash = this.getAttribute('href');
|
||||
if (hash === '#') return;
|
||||
const target = document.querySelector(hash);
|
||||
if (target) {
|
||||
e.preventDefault();
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
});
|
||||
});
|
||||
195
templates/index.html
Normal file
195
templates/index.html
Normal file
@@ -0,0 +1,195 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<title>Круглый стол | БС «Патриот» | Бизнес-сообщество предпринимателей</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,300;14..32,400;14..32,500;14..32,600;14..32,700;14..32,800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<div class="navbar">
|
||||
<div class="logo">ПАТРИОТ<span>•БИЗНЕС</span></div>
|
||||
<div class="nav-links">
|
||||
<a href="#about">О формате</a>
|
||||
<a href="#agenda">Программа</a>
|
||||
<a href="#stats-block">Цифры</a>
|
||||
<a href="#register">Регистрация</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="hero">
|
||||
<div class="hero-content">
|
||||
<div class="badge"><i class="fas fa-shield-alt"></i> Закрытый клуб предпринимателей</div>
|
||||
<h1>Круглый стол <br> с бизнес‑сообществом <span style="color: #c52a1c;">«Патриот»</span></h1>
|
||||
<p class="hero-description">Объединяем лидеров, которые строят будущее России. Живой диалог, реальные кейсы и партнёрство без формальностей.</p>
|
||||
<div class="hero-buttons">
|
||||
<a href="#register" class="btn btn-primary"><i class="fas fa-ticket-alt"></i> Стать участником</a>
|
||||
<a href="#agenda" class="btn btn-outline"><i class="fas fa-calendar-alt"></i> Узнать программу</a>
|
||||
</div>
|
||||
<div class="hero-info">
|
||||
<div><i class="far fa-calendar-check"></i> 26 апреля 2026, начало в 11:00</div>
|
||||
<div><i class="fas fa-lock"></i> Вход по рекомендации</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-stats">
|
||||
<div class="stats-title">Сообщество в цифрах</div>
|
||||
<div class="stat-grid">
|
||||
<div class="stat-item"><h4>557+</h4><p>участников</p></div>
|
||||
<div class="stat-item"><h4>125+</h4><p>выездов в зону СВО</p></div>
|
||||
<div class="stat-item"><h4>30+</h4><p>комитетов</p></div>
|
||||
<div class="stat-item"><h4>250+</h4><p>предпринимателей c 1 млрд+</p></div>
|
||||
</div>
|
||||
<p class="stats-note"><i class="fas fa-check-circle"></i> По данным сайта сообщества</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="about" class="section bg-light">
|
||||
<div class="container">
|
||||
<div class="badge" style="background: #edeae2;">Миссия и ценности</div>
|
||||
<div class="about-grid">
|
||||
<div class="about-content">
|
||||
<h2>Бизнес как <br> социальная миссия</h2>
|
||||
<p>Сообщество «Предприниматели Патриоты» объединяет активных владельцев бизнеса, героев СВО, олимпийских чемпионов и лидеров мнений. Мы создаём среду, где бренд «Сделано в России» становится знаком качества и силы.</p>
|
||||
<p>На круглом столе встретятся ЛПР, чтобы обсудить реальные инструменты масштабирования, социальные проекты и укрепление делового суверенитета. Закрытый клуб — только для единомышленников.</p>
|
||||
<div class="values-list">
|
||||
<span><i class="fas fa-heart"></i> Патриотизм</span>
|
||||
<span><i class="fas fa-hand-sparkles"></i> Соц. ответственность</span>
|
||||
<span><i class="fas fa-chart-line"></i> Лидерство</span>
|
||||
<span><i class="fas fa-eye"></i> Личный пример</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="about-quote">
|
||||
<i class="fas fa-quote-left"></i>
|
||||
<p>«Мы хотим, чтобы слово „патриот“ ассоциировалось с надёжным партнёром и лидером рынка. Встреча за круглым столом — место честного диалога без галстуков».</p>
|
||||
<p class="quote-author">— Вячеслав Варяников, основатель сообщества</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bg-gentle section-sm">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<div class="badge">Почему участвовать</div>
|
||||
<h2>Формат доверия и пользы</h2>
|
||||
</div>
|
||||
<div class="cards-grid">
|
||||
<div class="card"><i class="fas fa-users"></i><h3>Топ-нетворкинг</h3><p>Встреча с первыми лицами компаний, членами комитетов и экспертами с выручкой от 1 млрд.</p></div>
|
||||
<div class="card"><i class="fas fa-handshake"></i><h3>Реальные проекты</h3><p>Совместные инициативы, социальные программы и коммерческие коллаборации.</p></div>
|
||||
<div class="card"><i class="fas fa-chalkboard-user"></i><h3>Живые кейсы</h3><p>Разбор практических ситуаций от участников сообщества «Патриот» без купюр.</p></div>
|
||||
<div class="card"><i class="fas fa-shield-heart"></i><h3>Закрытый клуб</h3><p>Доступ по рекомендации — среда, где ценят репутацию и патриотизм.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="agenda" class="section bg-light">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<div class="badge">Программа круглого стола</div>
|
||||
<h2>Стратегия, партнёрство, развитие</h2>
|
||||
<p>Камерная встреча с живым обсуждением. Формат — открытый микрофон, панельные дискуссии и кофе-брейк.</p>
|
||||
</div>
|
||||
<div class="agenda-list">
|
||||
<div class="agenda-item"><div class="agenda-time">11:00 – 11:30</div><div><strong>Сбор кофе, приветственный кофе-брейк</strong><br>Неформальное знакомство участников.</div></div>
|
||||
<div class="agenda-item"><div class="agenda-time">11:30 – 12:30</div><div><strong>Панель «Бизнес и социальная ответственность»</strong><br>Как проекты помощи и поддержка СВО влияют на репутацию и прибыль.</div></div>
|
||||
<div class="agenda-item"><div class="agenda-time">12:30 – 13:30</div><div><strong>Круглый стол «Точки роста: импортозамещение и экспорт»</strong><br>Успешные стратегии участников сообщества.</div></div>
|
||||
<div class="agenda-item"><div class="agenda-time">13:30 – 14:30</div><div><strong>Обед и нетворкинг</strong><br>Живое общение, обмен контактами.</div></div>
|
||||
<div class="agenda-item"><div class="agenda-time">14:30 – 15:45</div><div><strong>Открытый диалог с основателями комитетов</strong><br>Ответы на вопросы, разбор запросов участников.</div></div>
|
||||
<div class="agenda-item"><div class="agenda-time">15:45 – 16:00</div><div><strong>Заключение, анонс совместных проектов</strong><br>Формирование рабочих групп.</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="stats-block" class="bg-gentle section-sm">
|
||||
<div class="container">
|
||||
<div class="stats-wrapper">
|
||||
<div class="stats-info">
|
||||
<div class="badge">Сообщество Патриот.РФ</div>
|
||||
<h2>Объединяя лидеров <br> для побед России</h2>
|
||||
<p>Мы растем: от 557 участников до масштабных гуманитарных миссий. 125+ выездов, 600+ детей под опекой предпринимателей и 10+ млн подписчиков в общем охвате.</p>
|
||||
<ul>
|
||||
<li><i class="fas fa-check-circle"></i> 250+ предпринимателей c оборотом от 1 млрд руб.</li>
|
||||
<li><i class="fas fa-check-circle"></i> 30 комитетов: спорт, обучение, соцпроекты</li>
|
||||
<li><i class="fas fa-check-circle"></i> Ежемесячные стратегические сессии с героями СВО</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="stats-cta">
|
||||
<i class="fas fa-chart-simple"></i>
|
||||
<h3>Присоединиться к 557+</h3>
|
||||
<p>Участники сообщества получают поддержку государства, гранты и надежные B2B-связи. Круглый стол — ваш шанс войти в закрытый пул.</p>
|
||||
<a href="#register" class="btn btn-primary">Подать заявку</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="register" class="section bg-light">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<div class="badge"><i class="fas fa-pen-alt"></i> Участие строго по отбору</div>
|
||||
<h2>Стать участником круглого стола</h2>
|
||||
<p>Заполните анкету — модератор сообщества свяжется с вами для подтверждения. Вход для предпринимателей, топ-менеджеров и партнёров, разделяющих ценности «Патриота».</p>
|
||||
</div>
|
||||
<div class="form-card">
|
||||
<form id="registrationForm">
|
||||
<div class="input-group">
|
||||
<input type="text" name="fullname" id="fullname" placeholder="ФИО *" required>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="email" name="email" id="email" placeholder="E-mail *" required>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="tel" name="phone" id="phone" placeholder="Телефон *" required>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" name="company" id="company" placeholder="Компания / Бизнес-направление *" required>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select name="businessSize" id="businessSize">
|
||||
<option value="">Годовой оборот (опционально)</option>
|
||||
<option value="до 50 млн руб.">до 50 млн руб.</option>
|
||||
<option value="50–500 млн руб.">50–500 млн руб.</option>
|
||||
<option value="500 млн – 1 млрд руб.">500 млн – 1 млрд руб.</option>
|
||||
<option value="более 1 млрд руб.">более 1 млрд руб.</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<textarea name="message" id="message" rows="3" placeholder="Коротко о себе / почему хотите участвовать? (рекомендация или бизнес-запрос)"></textarea>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>
|
||||
<input type="checkbox" name="agree" id="agreeCheck" value="true" required>
|
||||
Я соглашаюсь с политикой конфиденциальности и подтверждаю, что разделяю ценности сообщества «Патриот» *
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary submit-btn"><i class="fas fa-paper-plane"></i> Отправить заявку</button>
|
||||
<div id="formFeedback" class="form-message"></div>
|
||||
</form>
|
||||
<p class="form-note">После одобрения вы получите персональное приглашение. Количество мест ограничено.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<div class="footer-logo">ПАТРИОТ<span style="color:#c52a1c;">•</span>БИЗНЕС</div>
|
||||
<p>Бизнес-сообщество «Предприниматели Патриоты» — официальный сайт: <a href="https://ppatrioty.ru/" target="_blank">ppatrioty.ru</a></p>
|
||||
<p>© 2026 Круглый стол с БС «Патриот». Вход по рекомендации действующих участников.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user