This commit is contained in:
2026-03-14 00:11:21 +03:00
parent b2139ed440
commit 468bdf817a
70 changed files with 337 additions and 47 deletions

View File

@@ -1,6 +1,6 @@
VENV=source ./.venv/bin/activate; VENV=source ./.venv/bin/activate;
ALEMBIC=alembic -c ./server/backend/database/alembic/alembic.ini ALEMBIC=alembic -c ./server/backend/database/alembic/alembic.ini
.PHONY: run run_debug migrate_head migrate_down migrate_history migrate_current migrate .PHONY: run run_debug migrate_head migrate_down migrate_history migrate_current migrate_heads migrate_stamp migrate
run: run:
$(VENV) python run.py --user_name admin $(VENV) python run.py --user_name admin
run_debug: run_debug:
@@ -13,5 +13,9 @@ migrate_history:
$(VENV) $(ALEMBIC) history $(VENV) $(ALEMBIC) history
migrate_current: migrate_current:
$(VENV) $(ALEMBIC) current $(VENV) $(ALEMBIC) current
migrate_heads:
$(VENV) $(ALEMBIC) heads
migrate_stamp:
$(VENV) $(ALEMBIC) stamp head
migrate: migrate:
$(VENV) $(ALEMBIC) revision --autogenerate $(VENV) $(ALEMBIC) revision --autogenerate

1
run.py
View File

@@ -8,6 +8,7 @@ async def init_admin(user_name: str):
admin_user = { admin_user = {
"code": "123456", "code": "123456",
"name": user_name, "name": user_name,
"middlename": user_name,
"surname": user_name, "surname": user_name,
"admin": True "admin": True
} }

View File

@@ -0,0 +1,32 @@
"""empty message
Revision ID: 15b87c1584bf
Revises: 2e39b25a3b28
Create Date: 2026-03-13 22:53:40.498042
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '15b87c1584bf'
down_revision: Union[str, Sequence[str], None] = 'dd476c0dcf61'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('users', 'food')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('users', sa.Column('food', sa.BOOLEAN(), nullable=True))
# ### end Alembic commands ###

View File

@@ -0,0 +1,32 @@
"""empty message
Revision ID: 1fa13c2c4df4
Revises: 15b87c1584bf
Create Date: 2026-03-13 22:56:36.115487
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '1fa13c2c4df4'
down_revision: Union[str, Sequence[str], None] = '15b87c1584bf'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('users', sa.Column('type_of_food', sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('users', 'type_of_food')
# ### end Alembic commands ###

View File

@@ -0,0 +1,32 @@
"""empty message
Revision ID: 55f24e794643
Revises: 1fa13c2c4df4
Create Date: 2026-03-13 23:03:47.236864
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '55f24e794643'
down_revision: Union[str, Sequence[str], None] = '1fa13c2c4df4'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('users', sa.Column('middlename', sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('users', 'middlename')
# ### end Alembic commands ###

View File

@@ -0,0 +1,32 @@
"""empty message
Revision ID: dd476c0dcf61
Revises: 1e2bd98e74a5
Create Date: 2026-03-13 22:38:32.065614
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'dd476c0dcf61'
down_revision: Union[str, Sequence[str], None] = '1e2bd98e74a5'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('users', 'alco')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('users', sa.Column('alco', sa.BOOLEAN(), nullable=True))
# ### end Alembic commands ###

View File

@@ -23,11 +23,11 @@ class User(Base):
code = Column(String, unique=True, nullable=True) code = Column(String, unique=True, nullable=True)
name = Column(String, nullable=True) name = Column(String, nullable=True)
middlename=Column(String, nullable=True)
surname = Column(String, nullable=True) surname = Column(String, nullable=True)
text_field = Column(String, nullable=True) text_field = Column(String, nullable=True)
food = Column(Boolean) type_of_food = Column(String, nullable=True)
alco = Column(Boolean) types_of_alco = Column(String, default="Nothing", nullable=True)
types_of_alco = Column(String, default="Nothing")
activated = Column(Boolean) activated = Column(Boolean)
created_at = Column(DateTime(timezone=True), server_default=func.now()) created_at = Column(DateTime(timezone=True), server_default=func.now())

View File

@@ -2,6 +2,7 @@ from pydantic import BaseModel, Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic.types import StringConstraints from pydantic.types import StringConstraints
from typing_extensions import Annotated from typing_extensions import Annotated
from typing import Optional
import re import re
NameStr = Annotated[ NameStr = Annotated[
@@ -23,16 +24,18 @@ class UserOut(BaseModel):
name: NameStr = Field(..., description="Name of the guest") name: NameStr = Field(..., description="Name of the guest")
surname: NameStr = Field(..., description="Surname of the guest") surname: NameStr = Field(..., description="Surname of the guest")
class UserUpdate(UserAccess): class UserUpdate(BaseModel):
name: NameStr = Field(..., description="Name of the guest") code: Optional[str] = Field(None, min_length=6, max_length=6, description="Code of the guest")
surname: NameStr = Field(..., description="Surname of the guest") name: Optional[NameStr] = Field(None, description="Name of the guest")
text_field: str = Field("", max_length=500, description="what the guest wants") middlename: Optional[NameStr] = Field(None, description="Middlename of the guest")
activated: bool = Field(False, description="activation of the guest") surname: Optional[NameStr] = Field(None, description="Surname of the guest")
food: bool = Field(False, description="Options meat or fish") text_field: Optional[str] = Field(None, max_length=500, description="what the guest wants")
alco: bool = Field(False, description="if the guest will drink alco or not") activated: Optional[bool] = Field(None, description="activation of the guest")
types_of_alco: str = Field("", description="types of alco") type_of_food: Optional[str] = Field(None, description="meat or fish")
types_of_alco: Optional[str] = Field(None, description="types of alco")
class UserCreate(UserUpdate): class UserCreate(UserUpdate):
code: str = Field(..., min_length=6, max_length=6, description="Code of the guest")
admin:bool = Field(False, description="Admin privilegies") admin:bool = Field(False, description="Admin privilegies")
class Settings(BaseSettings): class Settings(BaseSettings):
DIR:str DIR:str

View File

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
<link rel="stylesheet" href="login.css">
</head>
<body>
<div class="glass-container">
<div class="login-box">
<h2>Login</h2>
<form id="loginForm">
<input type="password" id="password" name="password" required placeholder="Code">
<button type="submit">Login</button>
</form>
</div>
</div>
<script src="login.js"></script>
</body>
</html>

View File

@@ -0,0 +1,74 @@
function getToken() {
return localStorage.getItem("token") || sessionStorage.getItem("token");
}
function tokenCheck(){
const token = getToken();
if (token) {
window.location.href = "http://localhost:5500/server/frontend/main/index.html";
}
}
document.getElementById('loginForm').addEventListener('submit', async function (e) {
e.preventDefault();
const password = document.getElementById('password').value;
const userData = {
password
};
try {
const response = await fetch("http://localhost:8000/api/auth", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
password: userData.password
}),
});
const data = await response.json(); // читаем только один раз
if (response.ok) { // сохраняем только при успехе
// в sessionstorage до перезахода в браузер(
sessionStorage.setItem("token", data.access_token);
window.location.href = 'http://localhost:5500/server/frontend/main/index.html';
} else { //парсинг и вывод ошибок, если есть
if (Array.isArray(data.detail)) {
const messages = data.detail.map(e => {
const field = e.loc.filter(locPart => locPart !== 'body').join(' -> ');
return `${field}: ${e.msg}`;
});
showError(messages);
} else if (typeof data.detail === 'string') {
showError([data.detail]);
} else {
showError(['Unknown error']);
}
}
} catch (err) {
showError(['Connection error: ' + err.message]);
}
});
function showError(messages){ //Добавление их на form со стилями
let errorElem = document.getElementById('formError');
let container = document.getElementById('glass-container');
if (!errorElem){
errorElem = document.createElement('div');
errorElem.style.transition="3s";
errorElem.id = 'formError';
errorElem.style.color = 'red';
errorElem.style.marginTop = '20px';
errorElem.style.fontSize = '14px';
errorElem.style.fontWeight = '100';
errorElem.style.marginBottom = '20px';
errorElem.style.lineHeight="120%";
errorElem.style.height = 'auto';
const form = document.getElementById('loginForm');
form.insertAdjacentElement('afterend', errorElem);
};
errorElem.innerHTML = '';
messages.forEach(msg => {
const li = document.createElement('li');
li.style.listStyleType="none";
li.textContent = msg;
errorElem.appendChild(li);
});
}

View File

@@ -0,0 +1,69 @@
function getToken() {
return localStorage.getItem("token") || sessionStorage.getItem("token");
}
function tokenCheck(){
const token = getToken();
if (!token) {
window.location.href = "http://localhost:5500/server/frontend/auth/login.html";
}
}
tokenCheck()
document.getElementById('logoutForm').addEventListener('submit', async function (e) {
e.preventDefault();
localStorage.removeItem("token");
sessionStorage.removeItem("token");
tokenCheck();
});
document.addEventListener("DOMContentLoaded", () => {
const form = document.querySelector(".form-info");
form.addEventListener("submit", async (e) => {
e.preventDefault();
// try {
// собираем данные из формы
const name = document.getElementById('ffname').value || "";
const middlename = document.getElementById('fmname').value || "";
const surname = document.getElementById('flname').value || "";
const text_field = document.getElementById('text_field')?.value || "";
const food = document.querySelector('input[name="food"]:checked')?.value || "";
const types_of_alco = Array.from(document.querySelectorAll('input[name="drink"]:checked'))
.map(el => el.value)
.join(', ');
const guestData = {
name: name,
middlename: middlename,
surname: surname,
text_field: text_field,
activated: true,
types_of_food: food,
types_of_alco: types_of_alco
};
console.log(guestData)
// отправка на /api/update с Authorization
// const updateResponse = await fetch('/api/update', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// 'Authorization': `Bearer ${token}`
// },
// body: JSON.stringify(guestData)
// });
// if (!updateResponse.ok) {
// throw new Error('Ошибка при отправке формы');
// }
// const updateData = await updateResponse.json();
// console.log('Форма успешно отправлена:', updateData);
// } catch (err) {
// console.error(err);
// alert('Ошибка: ' + err.message);
// }
});
});

View File

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 192 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 2.2 MiB

View File

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 2.2 MiB

View File

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View File

Before

Width:  |  Height:  |  Size: 288 KiB

After

Width:  |  Height:  |  Size: 288 KiB

View File

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 137 KiB

View File

Before

Width:  |  Height:  |  Size: 221 KiB

After

Width:  |  Height:  |  Size: 221 KiB

View File

Before

Width:  |  Height:  |  Size: 175 KiB

After

Width:  |  Height:  |  Size: 175 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

View File

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 178 KiB

View File

Before

Width:  |  Height:  |  Size: 362 KiB

After

Width:  |  Height:  |  Size: 362 KiB

View File

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 197 KiB

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 525 KiB

After

Width:  |  Height:  |  Size: 525 KiB

View File

Before

Width:  |  Height:  |  Size: 920 B

After

Width:  |  Height:  |  Size: 920 B

View File

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

View File

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View File

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 171 KiB

View File

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

@@ -64,7 +64,7 @@
</nav> </nav>
</header> </header>
<content class="content"> <main class="content">
<section class="heading" id="heading"> <section class="heading" id="heading">
<article class="heading_content"> <article class="heading_content">
<div class="heading_cont"> <div class="heading_cont">
@@ -300,38 +300,27 @@
</h4> </h4>
<div class="transfer_inner"> <div class="transfer_inner">
<div class="transfer_map"> <div class="transfer_map">
<map class="map"> <div class="map">
<div style="position:relative;overflow:hidden; border-radius: 33px;"> <div style="position:relative;overflow:hidden; border-radius: 33px;">
<a href="https://yandex.com/maps/org/glavnoye_upravleniye_zapisi_aktov_grazhdanskogo_sostoyaniya/104116805635/?utm_medium=mapframe&utm_source=maps" <a href="https://www.google.com/maps/place/Glavnoye+Upravleniye+Zapisi+Aktov+Grazhdanskogo+Sostoyaniya+Tverskoy+Oblasti/@56.8571039,35.901453,17z/data=!3m1!4b1!4m6!3m5!1s0x46b687a4d4edacad:0x15df9a38c874eb5d!8m2!3d56.857101!4d35.9040279!16s%2Fg%2F11ls6j2lvj?entry=ttu&g_ep=EgoyMDI2MDMxMS4wIKXMDSoASAFQAw%3D%3D"
style="color:#eee;font-size:12px;position:absolute;top:0px;"> style="color:#eee;font-size:12px;position:absolute;top:0px;">
Главное управление записи актов гражданского состояния</a> Главное управление записи актов гражданского состояния</a>
<iframe <iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2181.349338718406!2d35.901452977352925!3d56.85710390639812!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x46b687a4d4edacad%3A0x15df9a38c874eb5d!2sGlavnoye%20Upravleniye%20Zapisi%20Aktov%20Grazhdanskogo%20Sostoyaniya%20Tverskoy%20Oblasti!5e0!3m2!1sen!2s!4v1773428540512!5m2!1sen!2s" width="760" height="500" style="border:1px;" allowfullscreen="true" loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>
src="https://yandex.com/map-widget/v1/?ll=35.903981%2C56.857281&mode=search&oid=104116805635&ol=biz&sctx=ZAAAAAgBEAAaKAoSCY3PZP88XSFAEVrW%2FWMhDklAEhIJzjgNUYU%2Fsz8RXD6Skh6Gtj8iBgABAgMEBSgKOABAgqANSAFiOnJlYXJyPXNjaGVtZV9Mb2NhbC9HZW91cHBlci9BZHZlcnRzL0N1c3RvbU1heGFkdi9FbmFibGVkPTFiOnJlYXJyPXNjaGVtZV9Mb2NhbC9HZW91cHBlci9BZHZlcnRzL0N1c3RvbU1heGFkdi9NYXhhZHY9MTViRHJlYXJyPXNjaGVtZV9Mb2NhbC9HZW91cHBlci9BZHZlcnRzL0N1c3RvbU1heGFkdi9SZWdpb25JZHM9WzEsMTAxNzRdYkByZWFycj1zY2hlbWVfTG9jYWwvR2VvdXBwZXIvQWR2ZXJ0cy9NYXhhZHZUb3BNaXgvTWF4YWR2Rm9yTWl4PTEwagJkZZ0BzczMPaABAKgBAL0BByG7v8IBBoPA4e6DA4ICE9C30LDQs9GBINGC0LLQtdGA0YyKAgCSAgIxNJoCDGRlc2t0b3AtbWFwcw%3D%3D&sll=35.903981%2C56.857281&sspn=0.001002%2C0.000999&text=%D0%B7%D0%B0%D0%B3%D1%81%20%D1%82%D0%B2%D0%B5%D1%80%D1%8C&z=19.23"
width="760" height="500" frameborder="1" allowfullscreen="true"
style="position:relative;"></iframe>
</div> </div>
<p class="map_adress">Тверь, Свободный переулок, 5</p> <p class="map_adress">Тверь, Свободный переулок, 5</p>
<h6 class="map_info">Начало в</h6> <h6 class="map_info">Начало в</h6>
<h6 class="map_time">10:30</h6> <h6 class="map_time">10:30</h6>
</map> </div>
<map class="map"> <div class="map">
<div style="position:relative;overflow:hidden; border-radius: 33px;"> <div style="position:relative;overflow:hidden; border-radius: 33px;">
<a href="https://yandex.com/maps/org/loft_1870/90344521327/?utm_medium=mapframe&utm_source=maps" <a href="https://www.google.com/maps/place/Loft+1870+%7C+%D0%9F%D0%BB%D0%BE%D1%89%D0%B0%D0%B4%D0%BA%D0%B0+%D0%B4%D0%BB%D1%8F+%D0%BC%D0%B5%D1%80%D0%BE%D0%BF%D1%80%D0%B8%D1%8F%D1%82%D0%B8%D0%B9+%D0%A2%D0%B2%D0%B5%D1%80%D1%8C+%7C+%D0%BA%D0%B5%D0%B9%D1%82%D0%B5%D1%80%D0%B8%D0%BD%D0%B3+%D0%BD%D0%B0+%D1%81%D0%B2%D0%B0%D0%B4%D1%8C%D0%B1%D1%83,+%D0%B1%D0%B0%D0%BD%D0%BA%D0%B5%D1%82%D0%BD%D1%8B%D0%B9+%D0%B7%D0%B0%D0%BB/@56.850656,35.8603082,17.32z/data=!3m1!5s0x46b687a53bac0dcf:0x5c270d07d710d5a!4m6!3m5!1s0x46b68764eb0c1387:0x7cb135f5414bc1e0!8m2!3d56.8508612!4d35.8650531!16s%2Fg%2F11p0wc5v7q?entry=ttu&g_ep=EgoyMDI2MDMxMS4wIKXMDSoASAFQAw%3D%3D"
style="color:#eee;font-size:12px;position:absolute;top:0px;">Лофт 1870</a><a style="color:#eee;font-size:12px;position:absolute;top:0px;">Лофт 1870</a>
href="https://yandex.com/maps/14/tver/category/banquet_hall/184108315/?utm_medium=mapframe&utm_source=maps" <iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d1749.675516895515!2d35.86030818806184!3d56.850656040536194!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x46b68764eb0c1387%3A0x7cb135f5414bc1e0!2zTG9mdCAxODcwIHwg0J_Qu9C-0YnQsNC00LrQsCDQtNC70Y8g0LzQtdGA0L7Qv9GA0LjRj9GC0LjQuSDQotCy0LXRgNGMIHwg0LrQtdC50YLQtdGA0LjQvdCzINC90LAg0YHQstCw0LTRjNCx0YMsINCx0LDQvdC60LXRgtC90YvQuSDQt9Cw0Ls!5e0!3m2!1sen!2s!4v1773428705434!5m2!1sen!2s" width="760" height="500" style="border:1px;" allowfullscreen="true" loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>
style="color:#eee;font-size:12px;position:absolute;top:14px;">Банкетный зал в
Твери</a><a
href="https://yandex.com/maps/14/tver/category/organization_of_events/184108329/?utm_medium=mapframe&utm_source=maps"
style="color:#eee;font-size:12px;position:absolute;top:28px;">Организация мероприятий в
Твери</a><iframe
src="https://yandex.com/map-widget/v1/?filter=alternate_vertical%3AWhatWhere&ll=35.862462%2C56.851043&mode=search&oid=90344521327&ol=biz&sctx=ZAAAAAgCEAAaKAoSCUqaP6a180FAEbg9QWK7bUxAEhIJ8wTCTrFqUD8RgXwJFRxeUD8iBgABAgMEBSgKOABAgaANSAFiOnJlYXJyPXNjaGVtZV9Mb2NhbC9HZW91cHBlci9BZHZlcnRzL0N1c3RvbU1heGFkdi9FbmFibGVkPTFiOnJlYXJyPXNjaGVtZV9Mb2NhbC9HZW91cHBlci9BZHZlcnRzL0N1c3RvbU1heGFkdi9NYXhhZHY9MTViRHJlYXJyPXNjaGVtZV9Mb2NhbC9HZW91cHBlci9BZHZlcnRzL0N1c3RvbU1heGFkdi9SZWdpb25JZHM9WzEsMTAxNzRdYkByZWFycj1zY2hlbWVfTG9jYWwvR2VvdXBwZXIvQWR2ZXJ0cy9NYXhhZHZUb3BNaXgvTWF4YWR2Rm9yTWl4PTEwagJydZ0BzczMPaABAKgBAL0BvACOKsIBKO%2F8z8fQAtiGgZHvBbCG7O6vBo79quPrAY6e17vSBOOAnfSN%2BvnKggGCAhPQu9C%2B0YTRgiDRgtCy0LXRgNGMigIAkgICMTSaAgxkZXNrdG9wLW1hcHM%3D&sll=35.862462%2C56.851043&sspn=0.007794%2C0.007771&text=%D0%BB%D0%BE%D1%84%D1%82%20%D1%82%D0%B2%D0%B5%D1%80%D1%8C&z=16.27"
width="760" height="500" frameborder="1" allowfullscreen="true"
style="position:relative;"></iframe>
</div> </div>
<p class="map_adress">Тверь, улица Двор Пролетарки, 16</p> <p class="map_adress">Тверь, улица Двор Пролетарки, 16</p>
<h6 class="map_info">Начало в</h6> <h6 class="map_info">Начало в</h6>
<h6 class="map_time">14:00</h6> <h6 class="map_time">14:00</h6>
</map> </div>
</div> </div>
</div> </div>
</section> </section>
@@ -434,20 +423,20 @@
Просим Вас заполнить форму и подтвердить своё участие Просим Вас заполнить форму и подтвердить своё участие
</h6> </h6>
<form class="form-example"> <form class="form-info">
<!-- Левая колонка --> <!-- Левая колонка -->
<div class="form_name"> <div class="form_name">
<label>Имя <label>Имя
<input type="text" name="firstName" required> <input type="text" name="firstName" id="ffname" required>
</label> </label>
<label>Отчество <label>Отчество
<input type="text" name="middleName" required> <input type="text" name="middleName" id="fmname" required>
</label> </label>
<label>Фамилия <label>Фамилия
<input type="text" name="lastName" required> <input type="text" name="lastName" id="flname" required>
</label> </label>
</div> </div>
@@ -456,12 +445,12 @@
<p class="block_title">Горячее блюдо</p> <p class="block_title">Горячее блюдо</p>
<label class="option"> <label class="option">
<input type="radio" name="food" value="meat"> <input type="radio" name="food" id="rmeat" value="meat" required>
Мясо Мясо
</label> </label>
<label class="option"> <label class="option">
<input type="radio" name="food" value="fish"> <input type="radio" name="food" id="rfish" value="fish">
Рыба Рыба
</label> </label>
</div> </div>
@@ -470,21 +459,21 @@
<div class="form_drink"> <div class="form_drink">
<p class="block_title">Напитки</p> <p class="block_title">Напитки</p>
<label class="option"><input type="checkbox" name="drink" value="champagne"> Шампанское</label> <label class="option"><input type="checkbox" name="drink" value="champagne" id="cchampagne"> Шампанское</label>
<label class="option"><input type="checkbox" name="drink" value="wine"> Вино</label> <label class="option"><input type="checkbox" name="drink" value="wine" id="cwine"> Вино</label>
<label class="option"><input type="checkbox" name="drink" value="vodka"> Водка</label> <label class="option"><input type="checkbox" name="drink" value="vodka" id="cvodka"> Водка</label>
<label class="option"><input type="checkbox" name="drink" value="whiskey"> Виски</label> <label class="option"><input type="checkbox" name="drink" value="whiskey" id="cwhiskey"> Виски</label>
<label class="option"><input type="checkbox" name="drink" value="tequila"> Текила</label> <label class="option"><input type="checkbox" name="drink" value="tequila" id="ctequila"> Текила</label>
</div> </div>
<button type="submit" class="answer_btn"> <button type="submit" class="answer_btn" id="bsubmit">
Подтвердить участие Подтвердить участие
</button> </button>
</form> </form>
</div> </div>
</section> </section>
</content> </main>
<footer class="footer"> <footer class="footer">
<div class="footer_inner"> <div class="footer_inner">
@@ -534,6 +523,7 @@
<script src="https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.js"></script>
<script src="main.js"></script> <script src="main.js"></script>
<script src="api.js"></script>
</body> </body>
</html> </html>

View File

@@ -455,7 +455,7 @@ h4 {
font-weight: 900; font-weight: 900;
} }
.form-example { .form-info {
display: grid; display: grid;
grid-template-columns: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr;
gap: 50px; gap: 50px;