72 Commits
dev ... main

Author SHA1 Message Date
66b6878e50 cache images
All checks were successful
Build Docker / deploy (push) Successful in 40s
Build Docker / build (push) Successful in 35s
2026-03-19 03:06:41 +03:00
7bdf422c32 fix js
All checks were successful
Build Docker / deploy (push) Successful in 51s
Build Docker / build (push) Successful in 34s
2026-03-19 03:00:41 +03:00
9a259cb637 fix logout button
All checks were successful
Build Docker / deploy (push) Successful in 40s
Build Docker / build (push) Successful in 35s
2026-03-19 02:57:02 +03:00
54072eb87d logout button fix
All checks were successful
Build Docker / deploy (push) Successful in 40s
Build Docker / build (push) Successful in 35s
2026-03-19 02:53:17 +03:00
ecf374685f error handle 0.152
All checks were successful
Build Docker / deploy (push) Successful in 38s
Build Docker / build (push) Successful in 34s
2026-03-19 02:48:16 +03:00
62d58e30cd remake apis 0.151
All checks were successful
Build Docker / deploy (push) Successful in 44s
Build Docker / build (push) Successful in 34s
2026-03-19 02:41:39 +03:00
a13b88bd32 fix font types 0.150
All checks were successful
Build Docker / deploy (push) Successful in 49s
Build Docker / build (push) Successful in 36s
2026-03-19 02:25:51 +03:00
ea7bfc64f2 fix jss 0.149
All checks were successful
Build Docker / deploy (push) Successful in 55s
Build Docker / build (push) Successful in 35s
2026-03-19 02:09:25 +03:00
0e2a17a3e3 fix login.js 0.148
All checks were successful
Build Docker / deploy (push) Successful in 38s
Build Docker / build (push) Successful in 39s
2026-03-19 02:00:48 +03:00
185cefe250 fix login.js 0.147
All checks were successful
Build Docker / deploy (push) Successful in 45s
Build Docker / build (push) Successful in 44s
2026-03-19 01:58:13 +03:00
25ec2c7bcb fix jss 0.146
All checks were successful
Build Docker / deploy (push) Successful in 41s
Build Docker / build (push) Successful in 38s
2026-03-19 01:52:57 +03:00
b667cde3d7 fix caddyfile 0.145
All checks were successful
Build Docker / deploy (push) Successful in 1m40s
Build Docker / build (push) Successful in 36s
2026-03-19 01:47:41 +03:00
d30af82765 fix caddy file 0.134
All checks were successful
Build Docker / deploy (push) Successful in 48s
Build Docker / build (push) Successful in 42s
2026-03-19 01:43:57 +03:00
efd8510853 fix caddy file 0.133
All checks were successful
Build Docker / deploy (push) Successful in 38s
Build Docker / build (push) Successful in 34s
2026-03-19 01:32:00 +03:00
e558aa6bdd fix caddy 0.132
All checks were successful
Build Docker / deploy (push) Successful in 50s
Build Docker / build (push) Successful in 38s
2026-03-19 01:29:09 +03:00
bd9210a3b5 fix caddy 0.131
All checks were successful
Build Docker / deploy (push) Successful in 39s
Build Docker / build (push) Successful in 34s
2026-03-19 01:22:49 +03:00
1eba5623a3 fix caddyfile 0.130
All checks were successful
Build Docker / deploy (push) Successful in 50s
Build Docker / build (push) Successful in 32s
2026-03-19 01:16:07 +03:00
c762662231 fix caddyfile
All checks were successful
Build Docker / deploy (push) Successful in 1m6s
Build Docker / build (push) Successful in 36s
2026-03-19 01:12:24 +03:00
9c92a9c760 music test
All checks were successful
Build Docker / deploy (push) Successful in 59s
Build Docker / build (push) Successful in 38s
2026-03-14 23:32:55 +03:00
468bdf817a api js 2026-03-14 00:11:21 +03:00
b2139ed440 Merge commit '525e116489a484fd07662c4105667834e07df94e' as 'server/frontend'
Some checks failed
Build Docker / deploy (push) Has been cancelled
Build Docker / build (push) Has been cancelled
2026-03-13 14:31:28 +03:00
525e116489 Squashed 'server/frontend/' content from commit e9a2027
git-subtree-dir: server/frontend
git-subtree-split: e9a2027fe64d17ad074cbc4724676eb41f19682f
2026-03-13 14:31:28 +03:00
61657b95e8 preparations front 2026-03-13 14:30:00 +03:00
cc8e21c430 update endpoints
All checks were successful
Build Docker / deploy (push) Successful in 44s
Build Docker / build (push) Successful in 30s
2026-03-07 18:59:16 +03:00
0f92159735 caddy handle path
All checks were successful
Build Docker / deploy (push) Successful in 44s
Build Docker / build (push) Successful in 29s
2026-03-07 18:54:05 +03:00
0eb142696a run remake
All checks were successful
Build Docker / deploy (push) Successful in 40s
Build Docker / build (push) Successful in 29s
2026-03-07 18:51:00 +03:00
ed5128f75d 2
All checks were successful
Build Docker / deploy (push) Successful in 37s
Build Docker / build (push) Successful in 30s
2026-03-07 18:43:19 +03:00
02a2c17531 1
All checks were successful
Build Docker / deploy (push) Successful in 47s
Build Docker / build (push) Successful in 33s
2026-03-07 18:36:02 +03:00
6cde2363ba port issue
All checks were successful
Build Docker / deploy (push) Successful in 41s
Build Docker / build (push) Successful in 32s
2026-03-07 18:30:04 +03:00
28758f11b0 sasf
All checks were successful
Build Docker / deploy (push) Successful in 41s
Build Docker / build (push) Successful in 31s
2026-03-07 18:21:45 +03:00
b2cd7f3a14 docker compose caddy
All checks were successful
Build Docker / deploy (push) Successful in 42s
Build Docker / build (push) Successful in 31s
2026-03-07 18:14:57 +03:00
5b224f8c83 fix caddyconf
All checks were successful
Build Docker / deploy (push) Successful in 42s
Build Docker / build (push) Successful in 32s
2026-03-07 18:02:47 +03:00
93df670182 final
All checks were successful
Build Docker / deploy (push) Successful in 40s
Build Docker / build (push) Successful in 1m11s
2026-03-07 17:55:28 +03:00
24fadc8add final test
Some checks failed
Build Docker / deploy (push) Failing after 44s
Build Docker / build (push) Has been skipped
2026-03-07 17:34:34 +03:00
9883a7e798 test2
All checks were successful
Build Docker / deploy (push) Successful in 44s
Build Docker / build (push) Successful in 28s
2026-03-07 17:31:06 +03:00
5ada2fb58f test2
All checks were successful
Build Docker / deploy (push) Successful in 37s
Build Docker / build (push) Successful in 27s
2026-03-07 17:25:54 +03:00
0f013e0949 test
Some checks failed
Build Docker / deploy (push) Successful in 55s
Build Docker / build (push) Failing after 25s
2026-03-07 17:19:48 +03:00
328cc2eae6 final
All checks were successful
Build Docker / deploy (push) Successful in 42s
Build Docker / build (push) Successful in 1m8s
2026-03-07 03:11:59 +03:00
974db928c7 check
All checks were successful
Build Docker / deploy (push) Successful in 41s
Build Docker / build (push) Successful in 28s
2026-03-07 02:36:01 +03:00
9d95049976 test
All checks were successful
Build Docker / deploy (push) Successful in 2m33s
Build Docker / build (push) Successful in 28s
2026-03-07 02:21:20 +03:00
d8cafdbdf0 test paths
All checks were successful
Build Docker / deploy (push) Successful in 39s
Build Docker / build (push) Successful in 30s
2026-03-07 02:07:36 +03:00
7716257c5b create fix 2
All checks were successful
Build Docker / deploy (push) Successful in 41s
Build Docker / build (push) Successful in 38s
2026-03-07 01:21:23 +03:00
869d2d5199 create fix
Some checks failed
Build Docker / build (push) Has been cancelled
Build Docker / deploy (push) Has been cancelled
2026-03-07 01:11:35 +03:00
ea0b339d69 final fix
All checks were successful
Build Docker / deploy (push) Successful in 42s
Build Docker / build (push) Successful in 28s
2026-03-07 01:05:48 +03:00
518ba194ae final
All checks were successful
Build Docker / deploy (push) Successful in 39s
Build Docker / build (push) Successful in 28s
2026-03-07 01:01:28 +03:00
10fdda3ec1 test fix
All checks were successful
Build Docker / deploy (push) Successful in 37s
Build Docker / build (push) Successful in 28s
2026-03-07 00:31:15 +03:00
bbfe68c533 fix
All checks were successful
Build Docker / deploy (push) Successful in 37s
Build Docker / build (push) Successful in 28s
2026-03-07 00:06:10 +03:00
1a312fb7a3 ftst
All checks were successful
Build Docker / deploy (push) Successful in 38s
Build Docker / build (push) Successful in 29s
2026-03-07 00:03:30 +03:00
2b836f081c db
All checks were successful
Build Docker / deploy (push) Successful in 38s
Build Docker / build (push) Successful in 44s
2026-03-06 23:58:43 +03:00
97ccd99b75 test4
All checks were successful
Build Docker / deploy (push) Successful in 39s
Build Docker / build (push) Successful in 40s
2026-03-06 23:32:48 +03:00
95f4cc4e6e test3
Some checks failed
Build Docker / deploy (push) Successful in 54s
Build Docker / build (push) Failing after 13s
2026-03-06 23:07:05 +03:00
236585afb1 test2
Some checks failed
Build Docker / deploy (push) Successful in 50s
Build Docker / build (push) Failing after 13s
2026-03-06 23:02:09 +03:00
5ad65b75da test
Some checks failed
Build Docker / deploy (push) Successful in 50s
Build Docker / build (push) Failing after 16s
2026-03-06 22:56:36 +03:00
ee453bf652 fix docker
All checks were successful
Build Docker / deploy (push) Successful in 42s
Build Docker / build (push) Successful in 29s
2026-03-06 22:54:13 +03:00
39359d7066 check env
All checks were successful
Build Docker / deploy (push) Successful in 37s
Build Docker / build (push) Successful in 28s
2026-03-06 22:46:13 +03:00
0b3a18d75a ref 2
All checks were successful
Build Docker / deploy (push) Successful in 44s
Build Docker / build (push) Successful in 30s
2026-03-06 22:25:03 +03:00
9b9e0a5466 refactoring ci/cd
Some checks failed
Build Docker / deploy (push) Failing after 1s
Build Docker / build (push) Has been skipped
2026-03-06 22:22:41 +03:00
8f6b8c99a9 test19
All checks were successful
Build Docker / deploy (push) Successful in 55s
Build Docker / build (push) Successful in 28s
2026-03-06 21:56:06 +03:00
f945cdd58f test 16
All checks were successful
Build Docker / deploy (push) Successful in 55s
Build Docker / build (push) Successful in 28s
2026-03-06 21:45:04 +03:00
edde30b539 test14
Some checks failed
Build Docker / deploy (push) Successful in 46s
Build Docker / build (push) Failing after 28s
2026-03-06 21:41:35 +03:00
28fe9c29df test13
Some checks failed
Build Docker / deploy (push) Successful in 51s
Build Docker / build (push) Failing after 28s
2026-03-06 21:30:37 +03:00
289ec8b067 test
All checks were successful
Build Docker / deploy (push) Successful in 53s
Build Docker / build (push) Successful in 27s
2026-03-06 21:12:10 +03:00
6ee981a63c test 12
All checks were successful
Build Docker / deploy (push) Successful in 41s
Build Docker / build (push) Successful in 28s
2026-03-06 18:10:30 +03:00
cf4e9a743a test 11
All checks were successful
Build Docker / deploy (push) Successful in 36s
Build Docker / build (push) Successful in 27s
2026-03-06 18:06:50 +03:00
b92e7cfd63 fix paths
All checks were successful
Build Docker / deploy (push) Successful in 1m8s
Build Docker / build (push) Successful in 28s
2026-03-06 15:10:32 +03:00
a0f2603b3a fix
All checks were successful
Build Docker / deploy (push) Successful in 37s
Build Docker / build (push) Successful in 33s
2026-03-06 15:00:37 +03:00
922ca80d23 fix dirs test7
All checks were successful
Build Docker / deploy (push) Successful in 2m26s
Build Docker / build (push) Successful in 1m10s
2026-03-06 14:14:06 +03:00
621398db24 test6 network fix
All checks were successful
Build Docker / deploy (push) Successful in 33s
Build Docker / build (push) Successful in 35s
2026-03-06 14:07:51 +03:00
a8b2bf2c38 test5 fix caddy.yaml
Some checks failed
Build Docker / deploy (push) Successful in 33s
Build Docker / build (push) Failing after 34s
2026-03-06 14:04:42 +03:00
5a0b68c9a9 test4
Some checks failed
Build Docker / deploy (push) Successful in 36s
Build Docker / build (push) Failing after 31s
2026-03-06 14:03:01 +03:00
b04bf1ea73 test3
Some checks failed
Build Docker / deploy (push) Successful in 34s
Build Docker / build (push) Failing after 42s
2026-03-06 13:56:06 +03:00
4953909070 test2
Some checks failed
Build Docker / deploy (push) Failing after 37s
Build Docker / build (push) Has been skipped
2026-03-06 13:53:47 +03:00
93 changed files with 1877 additions and 128 deletions

View File

@@ -13,7 +13,7 @@ jobs:
run: |
echo "${{ secrets.SSH_PRIVATE_KEY }}" > /tmp/id_fin
chmod 600 /tmp/id_fin
#ссылка на репо
#ключ машины
- name: Create .env file
run: echo "${{ secrets.RUNNER_ENV }}" > .env
#env для runners
@@ -26,7 +26,7 @@ jobs:
sparse-checkout: |
ansible/
- name: Run Ansible playbook
run: ansible-playbook -i inventory.ini tmp-repo/ansible/deploy.yml -e @secrets.yml -e env_file="$(pwd)/.env"
run: ansible-playbook -i tmp-repo/ansible/inventory.ini tmp-repo/ansible/deploy.yml -e @tmp-repo/ansible/secrets.yml -e env_file="$(pwd)/.env"
env:
ANSIBLE_PRIVATE_KEY_FILE: /tmp/id_fin
ANSIBLE_HOST_KEY_CHECKING: "False"
@@ -39,29 +39,18 @@ jobs:
- name: Create .env file
run: echo "${{ secrets.WEDDING_SITE_ENV }}" > .env
- name: Check env
run: cat .env
#env для проекта
- name: Build image
run: docker build -t back:latest -f docker/dockerfile .
run: docker build -t back:latest -f docker/dockerfile.project .
- name: Start docker-compose of project
run: docker compose --env-file .env -f docker/docker-compose.yaml up -d
- name: Checkout only caddy.yml
uses: actions/checkout@v4
with:
repository: MH.Dmitrii/wedding-site
ref: main
path: caddy
sparse-checkout: |
docker/caddy/
- name: Checkout web-site
uses: actions/checkout@v4
with:
repository: MH.Dmitrii/wedding-site
ref: main
path: caddy/site
sparse-checkout: |
server/frontend
- name: Start docker-compose caddy
run: docker compose -f caddy/caddy.yaml up -d
- name: Build image caddy
run: docker build -t caddy:wedding -f docker/caddy/dockerfile.caddy .
- name: Start docker-compose caddy
run: docker compose --env-file .env -f docker/caddy/caddy.yaml up -d

1
.gitignore vendored
View File

@@ -24,5 +24,4 @@ hint.py
*.env
#db
*.db
versions/
data/

View File

@@ -1,9 +1,10 @@
- hosts: servers
- name: Preflight checks and deploy wedding-site
hosts: servers
become: yes
vars:
env_file: $(pwd)/.env
tasks:
tasks:
- name: Install wget
apt:
name: wget

View File

@@ -1 +1 @@
gitea_instance_url: https://git.homyk.space/MH.Dmitrii/wedding-site/src/branch/main/docker/gitea_runners.git
gitea_instance_url: https://git.homyk.space/MH.Dmitrii/wedding-site/raw/branch/main/docker/gitea_runners/node-docker.yaml

View File

@@ -1,24 +1,27 @@
services:
caddy:
image: caddy:<version>
image: caddy:wedding
restart: unless-stopped
cap_add:
- NET_ADMIN
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- $PWD/conf:/etc/caddy
- $PWD/site:/srv
- caddy_data:/data
- caddy_config:/config
networks:
- wedding-site-network
- docker_wedding-site-network
healthcheck:
test: "curl -f https://ru.homyk.space"
interval: 5s
timeout: 30s
retries: 1
environment:
- PORT=${PORT}
volumes:
caddy_data:
caddy_config:
networks:
wedding-site-network:
docker_wedding-site-network :
external: true

View File

@@ -0,0 +1,59 @@
http://ru.homyk.space {
redir https://ru.homyk.space{uri} permanent
}
https://ru.homyk.space {
encode gzip
handle /api/* {
reverse_proxy backend:{$PORT}
}
handle_path /main/* {
@images path_regexp \.(jpg|jpeg|png|webp|gif|svg)$
header @images Cache-Control "public, max-age=604800"
forward_auth backend:{$PORT} {
uri /api/verify
copy_headers Authorization
}
root * /srv/main
file_server
}
handle_errors {
@unauth expression {http.error.status_code} == 401
redir @unauth / 302
}
handle {
root * /srv/auth
file_server {
index login.html
}
}
log {
output file /data/logs/caddy.log {
roll_size 5mb
roll_keep 5
roll_keep_for 72h
}
format json
}
}
https://music.homyk.space {
encode gzip
# --- API ---
handle {
reverse_proxy koel:80
}
log {
output file /data/logs/caddy1.log {
roll_size 5mb
roll_keep 5
roll_keep_for 72h
}
format json
}
}

View File

@@ -1,18 +0,0 @@
ru.homyk.space {
encode gzip
# --- API ---
handle_path /api/* {
reverse_proxy backend:8000
}
# --- Frontend ---
root * /srv
file_server
log {
output stdout
format console
}
}

View File

@@ -0,0 +1,3 @@
FROM caddy:latest
COPY ./docker/caddy/conf/Caddyfile /etc/caddy/Caddyfile
COPY ./server/frontend /srv

View File

@@ -3,15 +3,16 @@ services:
image: back:latest
container_name: wedding-site
volumes:
- wedding_volume:/home/backend
- /var/run/docker.sock:/var/run/docker.sock
ports:
- "${PORT}:${PORT}"
- /home/sevice/DB:/home/backend/server/backend/database/DB
expose:
- "${PORT}"
networks:
- wedding-site-network
healthcheck:
test: "curl -f http://localhost:${PORT}/docs"
interval: 5s
timeout: 30s
retries: 1
networks:
wedding-site-network:
driver: bridge
volumes:
wedding_volume: {}

View File

@@ -1,7 +1,10 @@
FROM python:3.13-slim
WORKDIR /home/backend
COPY ../ /home/backend
RUN python -m pip install --upgrade pip \
&& python -m pip install -r requirements.txt
RUN chmod +x ./docker/start.sh
ENTRYPOINT ["./docker/start.sh"]

View File

@@ -5,5 +5,4 @@ RUN apt-get update && apt-get install -y \
ansible \
&& rm -rf /var/lib/apt/lists/*
# Проверка версий
RUN node -v && npm -v && ansible --version

View File

@@ -1,18 +1,15 @@
# Dockerfile.runner
FROM node:20-bullseye
# Устанавливаем зависимости
RUN apt-get update && apt-get install -y \
curl \
lsb-release \
&& rm -rf /var/lib/apt/lists/*
# Добавляем репозиторий Docker
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" \
> /etc/apt/sources.list.d/docker.list \
&& apt-get update \
&& apt-get install -y docker-ce-cli docker-compose-plugin
# Проверка версий
RUN node -v && npm -v && docker -v && docker compose version

View File

@@ -1,4 +1,3 @@
#!/bin/sh
alembic -c server/backend/database/alembic/alembic.ini revision --autogenerate
alembic -c server/backend/database/alembic/alembic.ini upgrade head
exec python run.py
exec python run.py --user_name admin

View File

@@ -1,6 +1,6 @@
VENV=source ./.venv/bin/activate;
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:
$(VENV) python run.py --user_name admin
run_debug:
@@ -13,5 +13,9 @@ migrate_history:
$(VENV) $(ALEMBIC) history
migrate_current:
$(VENV) $(ALEMBIC) current
migrate_heads:
$(VENV) $(ALEMBIC) heads
migrate_stamp:
$(VENV) $(ALEMBIC) stamp head
migrate:
$(VENV) $(ALEMBIC) revision --autogenerate

View File

@@ -1 +1 @@
test
test 2

54
run.py
View File

@@ -1,46 +1,52 @@
from server.backend.schema.pydantic import settings
from server.backend.schema.pydantic import settings, UserCreate
from server.backend.database.db import create_user, list_users
from server.backend.schema.pydantic import UserCreate
import asyncio
import uvicorn
def start(log_level:str):
if __name__ == "__main__":
import asyncio
import argparse
async def init_admin(user_name: str):
admin_user = {
"code": "123456",
"name": user_name,
"middlename": user_name,
"surname": user_name,
"admin": True
}
users = await list_users() or []
label = any(u.admin for u in users)
if not label:
await create_user(UserCreate(**admin_user))
def start(log_level: str):
uvicorn.run(
"server.backend.endpoints.endpoints:api",
host="0.0.0.0",
port=settings.PORT,
reload=True,
reload=False,
log_level=log_level,
access_log=True
)
import argparse
parser = argparse.ArgumentParser(description="logging and admin creation")
parser.add_argument(
"--mode",
choices=["debug","info"],
choices=["debug", "info"],
default="info",
help="Режим логирования (по умолчанию: info)"
help="Режим логирования"
)
parser.add_argument(
"--user_name",
type=str,
required=True,
help="Создание первого пользователя)"
help="Создание первого пользователя"
)
args = parser.parse_args()
async def arguments(args):
admin_user = {
"code": "123456",
"name": args.user_name,
"surname": args.user_name,
"admin": True
}
users = await list_users()
label = any(u.admin for u in users)
if not label:
await create_user(UserCreate(**admin_user))
match args.mode:
case "debug" | "info":
if __name__ == "__main__":
asyncio.run(init_admin(args.user_name))
print("Режим:", args.mode)
start(args.mode)
asyncio.run(arguments(args))

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: 1e2bd98e74a5
Revises: 4ffe643b7d40
Create Date: 2026-03-07 00:44:25.427515
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '1e2bd98e74a5'
down_revision: Union[str, Sequence[str], None] = '4ffe643b7d40'
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! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### 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: 4ffe643b7d40
Revises: 965a4fb49fae
Create Date: 2026-03-07 00:21:36.656185
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '4ffe643b7d40'
down_revision: Union[str, Sequence[str], None] = '965a4fb49fae'
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! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### 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,38 @@
"""empty message
Revision ID: 65b22fdefbe0
Revises: 8925c8439e5d
Create Date: 2026-02-23 14:28:12.782781
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '65b22fdefbe0'
down_revision: Union[str, Sequence[str], None] = '8925c8439e5d'
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('food', sa.Boolean(), nullable=True))
op.add_column('users', sa.Column('alco', sa.Boolean(), nullable=True))
op.add_column('users', sa.Column('types_of_alco', sa.String(), nullable=True))
op.drop_column('users', 'checkbox1')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('users', sa.Column('checkbox1', sa.BOOLEAN(), nullable=True))
op.drop_column('users', 'types_of_alco')
op.drop_column('users', 'alco')
op.drop_column('users', 'food')
# ### end Alembic commands ###

View File

@@ -0,0 +1,32 @@
"""empty message
Revision ID: 8925c8439e5d
Revises: c842fa3d57cd
Create Date: 2026-02-23 13:59:55.999845
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '8925c8439e5d'
down_revision: Union[str, Sequence[str], None] = 'c842fa3d57cd'
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('checkbox1', sa.Boolean(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('users', 'checkbox1')
# ### end Alembic commands ###

View File

@@ -0,0 +1,32 @@
"""empty message
Revision ID: 965a4fb49fae
Revises: 65b22fdefbe0
Create Date: 2026-02-28 13:42:28.964508
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '965a4fb49fae'
down_revision: Union[str, Sequence[str], None] = '65b22fdefbe0'
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! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

View File

@@ -0,0 +1,47 @@
"""empty message
Revision ID: c842fa3d57cd
Revises:
Create Date: 2026-02-04 19:37:17.628815
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'c842fa3d57cd'
down_revision: Union[str, Sequence[str], None] = None
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.create_table('users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('code', sa.String(), nullable=True),
sa.Column('name', sa.String(), nullable=True),
sa.Column('surname', sa.String(), nullable=True),
sa.Column('text_field', sa.String(), nullable=True),
sa.Column('activated', sa.Boolean(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('last_login', sa.DateTime(timezone=True), nullable=True),
sa.Column('admin', sa.Boolean(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code')
)
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_users_id'), table_name='users')
op.drop_table('users')
# ### 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)
name = Column(String, nullable=True)
middlename=Column(String, nullable=True)
surname = Column(String, nullable=True)
text_field = Column(String, nullable=True)
food = Column(Boolean)
alco = Column(Boolean)
types_of_alco = Column(String, default="Nothing")
type_of_food = Column(String, nullable=True)
types_of_alco = Column(String, default="Nothing", nullable=True)
activated = Column(Boolean)
created_at = Column(DateTime(timezone=True), server_default=func.now())
@@ -46,7 +46,8 @@ async def create_user(user_info):
session.add(new_user)
await session.commit()
await session.refresh(new_user)
return user
else: return None
return new_user
async def update_user(user_info):
async with AsyncSessionLocal() as session:

View File

@@ -1,23 +1,31 @@
from fastapi import FastAPI, Depends, HTTPException,status
from fastapi import FastAPI, Depends, HTTPException,status, Response, Cookie
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import server.backend.schema.pydantic as pydantic
import server.backend.database.db as db
from server.backend.auth.JWT import signJWT, decodeJWT
api = FastAPI()
security = HTTPBearer()
api = FastAPI(openapi_url="/api/openapi.json",docs_url="/api/docs", redoc_url="/api/redoc")
security = HTTPBearer(auto_error=False)
async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
token = credentials.credentials
async def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security),
token_cookie: str = Cookie(default=None)
):
token = credentials.credentials if credentials else token_cookie
if not token:
raise HTTPException(status_code=401, detail="Not authenticated")
user = decodeJWT(token)
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
raise HTTPException(status_code=401, detail="Invalid token")
return user
async def check_roles(user=Depends(get_current_user)):
if user.get("admin") != True:
user_check = await db.list_user(user["user_id"])
if not user_check:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
if user_check.admin != True:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied")
return user
@api.post("/update", response_model=pydantic.UserUpdate)
@api.post("/api/update", response_model=pydantic.UserUpdate)
async def update_user(data: pydantic.UserUpdate, user=Depends(get_current_user)):
user_check = await db.list_user(user["user_id"])
if not user_check:
@@ -40,20 +48,44 @@ async def update_user(data: pydantic.UserUpdate, user=Depends(get_current_user))
updated_data = await db.update_user(updated_data)
return updated_data
@api.post("/create", response_model=pydantic.UserAccess)
@api.post("/api/create", response_model=pydantic.UserAccess)
async def create_user(user_info: pydantic.UserCreate,user=Depends(check_roles)):
await db.create_user(user_info)
user = await db.create_user(user_info)
if user == None:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Code already exists for another user"
)
return user_info
@api.get("/list")
@api.get("/api/list")
async def list_users(user=Depends(check_roles)):
list_of_users = await db.list_users()
return list_of_users
@api.post("/auth",response_model=pydantic.Token)
async def auth(code:pydantic.UserAccess):
@api.post("/api/auth")
async def auth(code: pydantic.UserAccess, response: Response):
login = await db.login_user(code)
if login == None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Forbidden")
if login is None:
raise HTTPException(status_code=401, detail="Forbidden")
token = signJWT(login)
response.set_cookie(
key="token",
value=token,
httponly=True,
secure=True,
samesite="strict"
)
return {"access_token": token, "token_type": "bearer"}
@api.get("/api/verify")
async def verify(token: str = Cookie(default=None)):
if not token:
raise HTTPException(status_code=401, detail="No token")
user = decodeJWT(token)
if not user:
raise HTTPException(status_code=401, detail="Invalid token")
return {"status": "ok"}
@api.post("/api/logout")
async def logout(response: Response):
response.delete_cookie(key="token")
return {"status": "ok"}

View File

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

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,65 @@
document.addEventListener('DOMContentLoaded', () => {
fetch('/api/verify', { credentials: 'include' })
.then(r => { if (r.ok) window.location.href = '/main/'; })
.catch(() => {});
document.getElementById('loginForm').addEventListener('submit', async function(e) {
e.preventDefault();
const code = document.getElementById('password').value;
try {
const response = await fetch('/api/auth', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include', // ← чтобы браузер принял cookie
body: JSON.stringify({ code })
});
const data = await response.json();
if (response.ok) {
window.location.href = '/main/';
} else {
if (Array.isArray(data.detail)) {
const messages = data.detail.map(e => {
const field = e.loc.filter(p => p !== '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) {
let errorElem = document.getElementById('formError');
if (!errorElem) {
errorElem = document.createElement('div');
errorElem.id = 'formError';
errorElem.style.cssText = `
color: red;
margin-top: 20px;
margin-bottom: 20px;
font-size: 14px;
font-weight: 100;
line-height: 120%;
transition: 3s;
`;
document.getElementById('loginForm').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

@@ -1 +0,0 @@
test

View File

View File

@@ -0,0 +1,55 @@
document.addEventListener("DOMContentLoaded", () => {
// Проверка авторизации через cookie
fetch('/api/verify', { credentials: 'include' })
.then(r => { if (!r.ok) window.location.href = '/'; })
.catch(() => { window.location.href = '/'; });
// Logout — удаляем cookie на бэкенде
document.getElementById('logoutForm').addEventListener('click', async function(e) {
e.preventDefault();
await fetch('/api/logout', {
method: 'POST',
credentials: 'include'
});
window.location.href = '/';
});
document.querySelector(".form-info").addEventListener("submit", async (e) => {
e.preventDefault();
const guestData = {
name: document.getElementById('ffname').value || "",
middlename: document.getElementById('fmname').value || "",
surname: document.getElementById('flname').value || "",
text_field: document.getElementById('text_field')?.value || "",
activated: true,
types_of_food: document.querySelector('input[name="food"]:checked')?.value || "",
types_of_alco: Array.from(document.querySelectorAll('input[name="drink"]:checked'))
.map(el => el.value)
.join(', ')
};
try {
const response = await fetch('/api/update', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include', // ← токен идёт через cookie
body: JSON.stringify(guestData)
});
if (!response.ok) {
const err = await response.json();
throw new Error(JSON.stringify(err.detail || 'Ошибка при отправке'));
}
const data = await response.json();
console.log('Успешно:', data);
alert('Данные сохранены!');
} catch (err) {
console.error(err);
alert('Ошибка: ' + err.message);
}
});
});

View File

@@ -0,0 +1 @@
DoubleRus encoding by Diai; unfinished

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="14" fill="url(#paint0_linear_87_7225)"/>
<path d="M22.9866 10.2088C23.1112 9.40332 22.3454 8.76755 21.6292 9.082L7.36482 15.3448C6.85123 15.5703 6.8888 16.3483 7.42147 16.5179L10.3631 17.4547C10.9246 17.6335 11.5325 17.541 12.0228 17.2023L18.655 12.6203C18.855 12.4821 19.073 12.7665 18.9021 12.9426L14.1281 17.8646C13.665 18.3421 13.7569 19.1512 14.314 19.5005L19.659 22.8523C20.2585 23.2282 21.0297 22.8506 21.1418 22.1261L22.9866 10.2088Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_87_7225" x1="16" y1="2" x2="16" y2="30" gradientUnits="userSpaceOnUse">
<stop stop-color="#37BBFE"/>
<stop offset="1" stop-color="#007DBB"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 920 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View File

@@ -0,0 +1,531 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wedding invitation</title>
<link rel="stylesheet" href="reset.css">
<link rel="stylesheet" href="style.css">
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.css"
/>
</head>
<body>
<header class="header">
<nav class="nav">
<ul class="menu">
<!-- 1 -->
<li class="item">
<a href="#heading" class="link">
Добро пожаловать!
</a>
</li>
<!-- 2 -->
<li class="item">
<a href="#people" class="link">
Список гостей
</a>
</li>
<!-- 3 -->
<li class="item">
<a href="#timetable" class="link">
Расписание
</a>
</li>
<!-- 4 -->
<li class="item">
<a href="#transfer" class="link">
Путешествие
</a>
</li>
<!-- 5 -->
<li class="item">
<a href="#photos" class="link">
Фотографии
</a>
</li>
<!-- 6 -->
<li class="item">
<a href="#to-do-list" class="link">
Список задач
</a>
</li>
<!-- 7 -->
<li class="item">
<a href="#answer" class="link">
Прошу ответить
</a>
</li>
</ul>
</nav>
</header>
<main class="content">
<section class="heading" id="heading">
<article class="heading_content">
<div class="heading_cont">
<h1 class="heding_title">Дмитрий и Алёна</h1>
<h2 class="heading_info">
Наша история
</h2>
<h3 class="heading_date">22 августа 2026</h3>
<p class="heading_text">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Iste possimus ipsa, labore repudiandae
ratione placeat dolore vitae praesentium perspiciatis ipsam non illum reiciendis accusamus quae
quo veritatis maiores quod sequi. Ipsam at consequatur quia recusandae, rem dicta autem quaerat
placeat?
</p>
<button class="btn_share" button>
Подтвердить участие
</button>
</div>
</article>
</section>
<aside>
<div class="time">
<!-- отсчет до даты -->
00:00:00
</div>
</aside>
<section class="people" id="people">
<h4 class="people_title">
Список гостей
</h4>
<div class="newlywed">
<div class="newlywed_item">
<img class="img_newlywed" src="images/people//IMG_20260107_140110_500.jpg" alt="groom">
<div class="newlywed_text">
<h3 class="newlywed_title">Жених</h3>
<p class="newlywed_info">Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui voluptatum
odio autem, ut dolorum suscipit soluta reiciendis quam reprehenderit saepe doloribus
asperiores
architecto? Debitis magnam, exercitationem nam temporibus eos molestias?</p>
</div>
</div>
<div class="newlywed_item">
<img class="img_newlywed" src="images/people/IMG_20260107_140120_301.jpg" alt="bride">
<div class="newlywed_text">
<h3 class="newlywed_title">Невеста</h3>
<p class="newlywed_info">Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui voluptatum
odio autem, ut dolorum suscipit soluta reiciendis quam reprehenderit saepe doloribus
asperiores
architecto? Debitis magnam, exercitationem nam temporibus eos molestias?</p>
</div>
</div>
</div>
<div class="guest_list">
<div class="guest_inner">
<!-- 1 -->
<div class="guest_item">
<div class="guest_index">
Стол 1
</div>
<div class="guest_item_list">
<div class="guest_name">
Lorem, ipsum.
</div>
<div class="guest_name">
Lorem, ipsum.
</div>
<div class="guest_name">
Lorem, ipsum.
</div>
<div class="guest_name">
Lorem, ipsum.
</div>
<div class="guest_name">
Lorem, ipsum.
</div>
<div class="guest_name">
Lorem, ipsum.
</div>
<div class="guest_name">
Lorem, ipsum.
</div>
</div>
</div>
<!-- 2 -->
<div class="guest_item">
<div class="guest_index">
Стол 1
</div>
<div class="guest_item_list">
<div class="guest_name">
Lorem, ipsum.
</div>
<div class="guest_name">
Lorem, ipsum.
</div>
<div class="guest_name">
Lorem, ipsum.
</div>
<div class="guest_name">
Lorem, ipsum.
</div>
<div class="guest_name">
Lorem, ipsum.
</div>
<div class="guest_name">
Lorem, ipsum.
</div>
<div class="guest_name">
Lorem, ipsum.
</div>
</div>
</div>
<!-- 3 -->
<div class="guest_item">
<div class="guest_index">
Стол 1
</div>
<div class="guest_item_list">
<div class="guest_name">
Lorem, ipsum.
</div>
<div class="guest_name">
Lorem, ipsum.
</div>
<div class="guest_name">
Lorem, ipsum.
</div>
<div class="guest_name">
Lorem, ipsum.
</div>
<div class="guest_name">
Lorem, ipsum.
</div>
<div class="guest_name">
Lorem, ipsum.
</div>
<div class="guest_name">
Lorem, ipsum.
</div>
</div>
</div>
</div>
</div>
</section>
<section class="timetable" id="timetable">
<h4 class="people_title">
Расписание
</h4>
<div class="time_wrapper">
<div class="time_content">
<div class="time_inner">
<div class="time_row_up">
<!-- 1 -->
<div class="row_up_item">
<div class="time_img_box">
<img src="images/time/ring.png" alt="ring" class="time_img">
<h6 class="time_h6">10:40</h6>
<p class="time_p">Церемония</p>
<div class="time_comment">Начало нашего свадебного дня и самый важный момент для нас
</div>
</div>
</div>
<!-- 2 -->
<div class="row_up_item">
<div class="time_img_box">
<img src="images/time/camera.png" alt="camera" class="time_img">
<h6 class="time_h6">11:30</h6>
<p class="time_p">Фотосессия</p>
<div class="time_comment">После церемонии мы ненадолго отправимся на фотосессию,
чтобы сохранить этот день в памяти</div>
</div>
</div>
<!-- 3 -->
<div class="row_up_item">
<div class="time_img_box">
<img src="images/time/wedding-dinner_11196102.png" alt="" class="time_img">
<h6 class="time_h6">14:00</h6>
<p class="time_p">Фуршет и бармен</p>
<div class="time_comment">Берем напиток, закуски и наслаждаемся началом вечера</div>
</div>
</div>
<!-- 4 -->
<div class="row_up_item">
<div class="time_img_box">
<img src="images/time/free-icon-fireworks-7201451.png" alt="" class="time_img">
<h6 class="time_h6">14:30</h6>
<p class="time_p">Поздравления и подарки</p>
<div class="time_comment">Мы будем рады теплым словам и подаркам во время фуршета
</div>
</div>
</div>
</div>
<div class="time_line"></div>
<div class="time_row_down">
<!-- 5 -->
<div class="row_up_item">
<div class="time_img_box">
<img src="images/time/food.png" alt="" class="time_img">
<h6 class="time_h6">17:00</h6>
<p class="time_p">Банкет</p>
<div class="time_comment">Приглашаем вас за столы, чтобы продолжить вечер в тёплой и
уютной атмосфере. Приветственный тост</div>
</div>
</div>
<!-- 6 -->
<div class="row_up_item">
<div class="time_img_box">
<img src="images/time/cake.png" alt="" class="time_img">
<h6 class="time_h6">19:00</h6>
<p class="time_p">Свадебный торт</p>
<div class="time_comment">Самый сладкий момент вечера. Чай или кофе находятся в
велком зоне</div>
</div>
</div>
<!-- 7 -->
<div class="row_up_item">
<div class="time_img_box">
<img src="images/time/disco-ball_18181959.png" alt="" class="time_img">
<h6 class="time_h6">19:30</h6>
<p class="time_p">Танцы</p>
<div class="time_comment">Приглашаем вас на танцпол. Танцуйте столько, сколько
захочется</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="transfer" id="transfer">
<h4 class="transfer_title">
Путешествие
</h4>
<div class="transfer_inner">
<div class="transfer_map">
<div class="map">
<div style="position:relative;overflow:hidden; border-radius: 33px;">
<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;">
Главное управление записи актов гражданского состояния</a>
<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>
</div>
<p class="map_adress">Тверь, Свободный переулок, 5</p>
<h6 class="map_info">Начало в</h6>
<h6 class="map_time">10:30</h6>
</div>
<div class="map">
<div style="position:relative;overflow:hidden; border-radius: 33px;">
<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>
<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>
</div>
<p class="map_adress">Тверь, улица Двор Пролетарки, 16</p>
<h6 class="map_info">Начало в</h6>
<h6 class="map_time">14:00</h6>
</div>
</div>
</div>
</section>
<section class="photos" id="photos">
<h4 class="photos_title">
Фотографии
</h4>
<!-- Slider main container -->
<div class="swiper">
<!-- Additional required wrapper -->
<div class="swiper-wrapper">
<!-- Slides -->
<div class="swiper-slide">
<img class="swiper_img" src="images/photos/IMG_20260223_151005_128.jpg" alt="photo">
</div>
<div class="swiper-slide">
<img class="swiper_img" src="images/photos//IMG_20260223_151049_713.jpg" alt="photo">
</div>
<div class="swiper-slide">
<img class="swiper_img" src="images/photos/IMG_20260223_151005_128.jpg" alt="photo">
</div>
<div class="swiper-slide">
<img class="swiper_img" src="images/photos/IMG_20260223_151049_713.jpg" alt="photo">
</div>
<div class="swiper-slide">
<img class="swiper_img" src="images/photos/IMG_20260223_151005_128.jpg" alt="photo">
</div>
<div class="swiper-slide">
<img class="swiper_img" src="images/photos/IMG_20260223_151049_713.jpg" alt="photo">
</div>
<div class="swiper-slide">
<img class="swiper_img" src="images/photos/IMG_20260223_151005_128.jpg" alt="photo">
</div>
<div class="swiper-slide">
<img class="swiper_img" src="images/photos/IMG_20260223_151005_128.jpg" alt="photo">
</div>
<div class="swiper-slide">
<img class="swiper_img" src="images/photos/IMG_20260223_151005_128.jpg" alt="photo">
</div>
<div class="swiper-slide">
<img class="swiper_img" src="images/photos/IMG_20260223_151005_128.jpg" alt="photo">
</div>
<div class="swiper-slide">
<img class="swiper_img" src="images/photos/IMG_20260223_151005_128.jpg" alt="photo">
</div>
</div>
<!-- If we need pagination -->
<div class="swiper-pagination"></div>
<!-- If we need navigation buttons -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
<!-- If we need scrollbar -->
<div class="swiper-scrollbar"></div>
</div>
</section>
<section class="to-do-list" id="to-do-list">
<h4 class="do-list_title">
Список задач
</h4>
<div class="to-do-list_wrapper">
<div class="to-do-list_items">
<div class="to-do-list_item">
<img class="to-do-list_item_img" src="images/icon.png" alt="icon">
Поздравить молодых и сказать тёплые слова
</div>
<div class="to-do-list_item">
<img class="to-do-list_item_img" src="images/icon.png" alt="icon">
Вручить подарок молодожёнам
</div>
<div class="to-do-list_item">
<img class="to-do-list_item_img" src="images/icon.png" alt="icon">
Сделать фото и видео, чтобы потом было что выложить
</div>
<div class="to-do-list_item">
<img class="to-do-list_item_img" src="images/icon.png" alt="icon">
Поднять тост за любовь
</div>
<div class="to-do-list_item">
<img class="to-do-list_item_img" src="images/icon.png" alt="icon">
Добраться до свадебного торта, сфотографировать его
</div>
<div class="to-do-list_item">
<img class="to-do-list_item_img" src="images/icon.png" alt="icon">
Потанцевать хотя бы один медленный танец
</div>
<div class="to-do-list_item">
<img class="to-do-list_item_img" src="images/icon.png" alt="icon">
Повеселиться так, чтобы этот день запомнился
</div>
</div>
</div>
</section>
<section class="answer" id="answer">
<h4 class="answer_title">Прошу ответить</h4>
<div class="answer_wrapper">
<h6 class="answer_heading">
Просим Вас заполнить форму и подтвердить своё участие
</h6>
<form class="form-info">
<!-- Левая колонка -->
<div class="form_name">
<label>Имя
<input type="text" name="firstName" id="ffname" required>
</label>
<label>Отчество
<input type="text" name="middleName" id="fmname" required>
</label>
<label>Фамилия
<input type="text" name="lastName" id="flname" required>
</label>
</div>
<!-- Средняя колонка -->
<div class="form_menu">
<p class="block_title">Горячее блюдо</p>
<label class="option">
<input type="radio" name="food" id="rmeat" value="meat" required>
Мясо
</label>
<label class="option">
<input type="radio" name="food" id="rfish" value="fish">
Рыба
</label>
</div>
<!-- Правая колонка -->
<div class="form_drink">
<p class="block_title">Напитки</p>
<label class="option"><input type="checkbox" name="drink" value="champagne" id="cchampagne"> Шампанское</label>
<label class="option"><input type="checkbox" name="drink" value="wine" id="cwine"> Вино</label>
<label class="option"><input type="checkbox" name="drink" value="vodka" id="cvodka"> Водка</label>
<label class="option"><input type="checkbox" name="drink" value="whiskey" id="cwhiskey"> Виски</label>
<label class="option"><input type="checkbox" name="drink" value="tequila" id="ctequila"> Текила</label>
</div>
<button type="submit" class="answer_btn" id="bsubmit">
Подтвердить участие
</button>
</form>
<button type="logout" class="answer_btn" id="logoutForm">
Выйти
</button>
</div>
</section>
</main>
<footer class="footer">
<div class="footer_inner">
<div class="footer_title">
<h1 class="fotter_logo">
Алёна и Дмитрий
</h1>
<p class="footer_date">22 августа 2026</p>
<p class="footer_place">LOFT 1870</p>
</div>
<div class="footer_contacts">
<p class="footer_text">"С нетерпением ждем встречи!"</p>
<div class="footer_cntcts">
<a class="footer_cnt" href="https://t.me/Alena7729" target="_blank"><?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="40px" height="40px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="14" fill="url(#paint0_linear_87_7225)"/>
<path d="M22.9866 10.2088C23.1112 9.40332 22.3454 8.76755 21.6292 9.082L7.36482 15.3448C6.85123 15.5703 6.8888 16.3483 7.42147 16.5179L10.3631 17.4547C10.9246 17.6335 11.5325 17.541 12.0228 17.2023L18.655 12.6203C18.855 12.4821 19.073 12.7665 18.9021 12.9426L14.1281 17.8646C13.665 18.3421 13.7569 19.1512 14.314 19.5005L19.659 22.8523C20.2585 23.2282 21.0297 22.8506 21.1418 22.1261L22.9866 10.2088Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_87_7225" x1="16" y1="2" x2="16" y2="30" gradientUnits="userSpaceOnUse">
<stop stop-color="#37BBFE"/>
<stop offset="1" stop-color="#007DBB"/>
</linearGradient>
</defs>
</svg>
Алёна</a>
<a class="footer_cnt" href="https://t.me/DisaTylov" target="_blank"><?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="40px" height="40px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="14" fill="url(#paint0_linear_87_7225)"/>
<path d="M22.9866 10.2088C23.1112 9.40332 22.3454 8.76755 21.6292 9.082L7.36482 15.3448C6.85123 15.5703 6.8888 16.3483 7.42147 16.5179L10.3631 17.4547C10.9246 17.6335 11.5325 17.541 12.0228 17.2023L18.655 12.6203C18.855 12.4821 19.073 12.7665 18.9021 12.9426L14.1281 17.8646C13.665 18.3421 13.7569 19.1512 14.314 19.5005L19.659 22.8523C20.2585 23.2282 21.0297 22.8506 21.1418 22.1261L22.9866 10.2088Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_87_7225" x1="16" y1="2" x2="16" y2="30" gradientUnits="userSpaceOnUse">
<stop stop-color="#37BBFE"/>
<stop offset="1" stop-color="#007DBB"/>
</linearGradient>
</defs>
</svg>
Дмитрий</a>
</div>
</div>
<div class="footer_copy">
<div class="footer_hashtag">#свадьба2026</div>
<div class="footer_copyright">© 2026 Алёна и Дмитрий</div>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.js"></script>
<script src="main.js"></script>
<script src="api.js"></script>
</body>
</html>

View File

@@ -0,0 +1,30 @@
const swiper = new Swiper('.swiper', {
// Optional parameters
direction: 'horizontal',
loop: true,
// Navigation arrows
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
// And if we need scrollbar
scrollbar: {
el: '.swiper-scrollbar',
},
speed: 1000,
breakpoints: {
740: {
slidesPerView: 1,
},
1200: {
slidesPerView: 2,
},
1600: {
slidesPerView: 3,
},
}
});

View File

@@ -0,0 +1,18 @@
*{padding: 0;margin: 0;border: 0;}
*,*:before,*:after{-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;}
:focus,:active{outline: none;}
a:focus,a:active{outline: none;}
nav,footer,header,aside{display: block;}
html,body{
height:100%;width:100%;
font-size:100%;line-height:1;-ms-text-size-adjust:100%;-moz-text-size-adjust:100%;-webkit-text-size-adjust:100%;}
input,button,textarea{font-family:inherit;}
input::-ms-clear{display: none;}
button{cursor: pointer;}
button::-moz-focus-inner{padding:0;border:0;}
a,a:visited{text-decoration: none;}
a:hover{text-decoration: none;}
ul li{list-style: none;}
img{vertical-align: top;}
h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight: inherit;}
a{color:inherit;}

View File

@@ -0,0 +1,596 @@
/* --------------------------------Font-------------------------------- */
@font-face {
font-family: "MurreyC";
src: url("fonts/MurreyC/murreyc.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "MurreyC";
src: url("fonts/MurreyC/murreyc.woff") format("woff");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "MurreyC";
src: url("fonts/MurreyC/murreyc.eot") format("eot");
font-weight: 400;
font-style: normal;
font-display: swap;
}
/* --------------------------------MAIN-------------------------------- */
body {
background: url("images/ChatGPT\ Image\ 5\ янв.\ 2026\ г.\,\ 15_49_39.png") no-repeat 100% 100% fixed;
background-size: cover;
font-family: "MurreyC";
font-size: 16px;
font-weight: 400;
letter-spacing: 3px;
height: auto;
}
button {
background-color: #3a1f09;
padding: 0 15px;
border-radius: 33px;
transition: all .1s;
color: #e5b97e;
box-shadow: #3a1f09 2px 4px 10px 2px;
}
button:hover {
background-color: #f0c590;
color: #b06029;
transform: scale(1.015);
}
img {
max-width: 100%;
}
.content {
}
/* -------------------------------HEADER------------------------------ */
.header {
width: 100%;
background-color: #663c24;
padding: 20px 180px;
margin-top: 40px;
margin-bottom: 40px;
box-shadow: #3a1f09 2px 4px 10px 2px;
}
.nav {
}
.menu {
display: flex;
justify-content: space-between;
}
.item {
color: #e7b15c;
font-size: 36px;
font-weight: 900;
transition: all .3s;
text-shadow: 1px 2px #322624;
}
.item:hover {
}
.link {
transition: all .3s;
}
.link:hover {
text-decoration: underline;
}
/* --------------------------------Welcome----------------------------- */
.heading {
background: url("images/25.png") 50% 100% / cover;
margin: 0px 400px;
/* border: 10px solid rgb(184, 136, 48); */
/* border: 15px double rgb(184, 136, 48); */
height: 640px;
box-shadow: #000000 2px 4px 10px 2px;
}
.heading_content {
padding: 15px 100px;
}
.heading_cont {
position: relative;
z-index: 1;
padding: 20px 0px 20px 0px;
text-align: center;
box-shadow: #000000 2px 4px 10px 2px;
}
.heading_cont::after {
z-index: -1;
position: absolute;
content: "";
display: block;
background-color: rgba(184, 136, 48, 0.8);
height: 100%;
width: 100%;
top: 0;
border: 1px solid #42240c;
}
.heding_title {
font-size: 42px;
font-weight: 900;
/* text-decoration: underline; */
}
.heading_info {
font-size: 36px;
font-weight: 700;
text-transform: uppercase;
/* color: #eccfae; */
}
.heading_date {
font-size: 32px;
font-weight: 700;
border-top: 3px solid #000;
border-bottom: 3px solid #000;
/* background-image: linear-gradient(to left, rgb(66, 36, 12), rgb(184, 136, 48)); */
}
.heading_text {
font-size: 26px;
padding: 0 20px;
font-weight: 700;
}
.btn_share {
margin-top: 10px;
font-size: 30px;
}
.asise {
}
.time{
padding-top: 15px;
font-size: 110px;
text-align: center;
background-image: linear-gradient(90deg,rgba(26, 21, 26, 1) 0%, rgba(101, 62, 39, 1) 50%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: #000 2px 2px 6px;
}
/* -----------------------------------PEOPLE-------------------------------------- */
.people {
width: 1400px;
margin: 0 auto;
padding-bottom: 80px;
padding-top: 40px;
}
.heading_h4 {
margin-top: 40px;
}
h4 {
display: block;
border-top: 3px solid #000;
border-bottom: 3px solid #000 ;
font-size: 60px;
text-align: center;
font-weight: 900;
width: 30%;
margin: 0 auto;
line-height: 1.2;
margin-bottom: 40px;
}
.newlywed {
display: flex;
justify-content: space-between;
gap: 40px;
width: 100%;
margin-bottom: 40px;
}
.newlywed_item {
display: flex;
align-items: center;
background-color: rgba(245, 198, 124, 0.9);
padding: 20px;
box-shadow: #000000 2px 4px 10px 2px;
}
.img_newlywed {
width: 40%;
width: 240px;
height: 240px;
border-radius: 50%;
border: 1px solid #47312c;
}
.newlywed_text {
margin-left: 20px;
width: 60%;
}
.newlywed_title {
font-size: 34px;
font-weight: 700;
text-decoration:underline;
line-height: 1.5;
}
.newlywed_info {
font-size: 26px;
}
.guest_list {
}
.guest_inner {
display: flex;
justify-content: space-around;
}
.guest_item {
text-align: center;
background: url("images/55555.jpg") 100% 100% / cover;
padding: 40px 80px;
border-radius: 33px;
opacity: 0.8;
}
.guest_index {
font-size: 48px;
font-weight: 900;
font-style: italic;
color: rgb(252, 220, 180);
}
.guest_item_list {
}
.guest_name {
font-size: 36px;
font-style: italic;
color: #e5b97e;
}
/* ---------------------------------TIMETABLE----------------------------------- */
.timetable {
margin: 0 auto;
width: 80%;
padding-bottom: 60px;
padding-top: 40px;
}
.time_wrapper {
height: 700px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.time_wrapper::after {
position: absolute;
content: "";
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.3);
}
.time_inner {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.time_content {
z-index: 10;
}
.time_row_up {
display: flex;
justify-content: space-between;
margin-bottom: 40px;
}
.row_up_item {
background-color: #e5b97e;
width: 320px;
padding: 30px;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
border-radius: 33px;
margin: 0 20px;
}
.time_img_box {
}
.time_img {
width: 60px;
height: 60px;
margin-bottom: 10px;
}
.time_line {
}
.time_row_down {
display: flex;
justify-content: space-between;
}
.time_h6 {
font-size: 26px;
font-weight: 900;
}
.time_p {
font-size: 28px;
font-weight: 900;
text-decoration: underline;
margin-bottom: 10px;
}
.time_comment {
font-size: 24px;
}
/* ----------------------------------TRANSFER--------------------------------------- */
.transfer {
margin-bottom: 60px;
padding-top: 40px;
}
.transfer_title {
}
.transfer_inner {
margin-top: 60px;
}
.transfer_map {
display: flex;
justify-content: center;
align-items: center;
gap: 100px;
}
.map > div {
border: 1px solid #000;
}
.map_adress {
font-size: 50px;
text-align: center;
text-decoration: underline;
margin-top: 20px;
}
.map_info {
font-size: 50px;
text-align: center;
margin-top: 10px;
}
.map_time {
font-size: 50px;
text-align: center;
margin-top: 10px;
font-weight: 900;
}
/* --------------------------------PHOTOS-------------------------------------- */
.photos {
padding-bottom: 140px;
margin: 0 auto;
padding-top: 60px;
}
.photos_title {
margin-bottom: 100px;
}
.swiper_img {
height: 500px;
border: 5px dotted #000;
}
.swiper {
width: 95%;
height: 550px;
text-align: center;
padding: 20px;
}
.swiper-wrapper {
width: 100%;
}
.swiper-slide {
text-align: center;
}
.swiper-navigation-icon {
color: #000;
}
/* --------------------------------------------TODOLIST------------------------------------------------------ */
.to-do-list {
padding-bottom: 100px;
padding-top: 40px;
}
.do-list_title {
}
.to-do-list_wrapper {
width: 100%;
padding-top: 40px;
}
.to-do-list_items {
margin: 0 auto;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 20px;
width: 60%;
}
.to-do-list_item {
background: linear-gradient(135deg, #f3d9c6, #e8c3a4, #d9a77a);
backdrop-filter: blur(6px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08);
width: 300px;
height: 200px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
padding: 30px;
text-align: center;
font-size: 28px;
font-weight: 900;
border-radius: 20px;
border: 1px solid rgba(0,0,0,0.05);
color: #3e2a1f;
text-shadow: 0 1px 1px rgba(255,255,255,0.3);
}
.to-do-list_item_img {
width: 50px;
height: 50px;
}
/* ---------------------------ANSWER----------------------------------- */
.answer {
padding-bottom: 100px;
padding-top: 40px;
}
.answer_wrapper {
background: linear-gradient(135deg, #f3d9c6, #e8c3a4, #d9a77a);
padding: 60px;
margin: 0 400px;
border-radius: 25px;
box-shadow: 0 15px 40px rgba(0,0,0,0.08);
margin-top: 20px;
}
.answer_heading {
text-align: center;
font-size: 42px;
margin-bottom: 50px;
color: #2f1e14;
font-weight: 900;
}
.form-info {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 50px;
}
.form_name {
color: #2f1e14;
display: flex;
flex-direction: column;
gap: 20px;
font-size: 36px;
font-weight: 700;
}
.block_title {
font-weight: 700;
margin-bottom: 20px;
color: #2f1e14;
font-size: 36px;
}
.option {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
font-size: 30px;
}
input[type="text"] {
padding: 12px 15px;
border-radius: 20px;
border: none;
background: #fffaf3;
font-size: 18px;
}
input[type="radio"],
input[type="checkbox"] {
width: 18px;
height: 18px;
}
.answer_btn {
grid-column: 1 / -1;
margin-top: 40px;
padding: 18px 40px;
border-radius: 30px;
border: none;
font-size: 36px;
font-weight: 700;
background: #2f1e14;
color: white;
cursor: pointer;
transition: 0.3s ease;
}
.answer_btn:hover {
background: #1e140d;
transform: translateY(-3px);
}
.form_name input[type="text"] {
width: 320px; /* одинаковая ширина */
height: 50px; /* одинаковая высота */
padding: 0 20px;
border-radius: 25px;
border: none;
background-color: #ffffff;
outline: none;
font-size: 30px;
}
/* -----------------------------FOOTER---------------------------------- */
.footer {
background-color: #2f1e14;
color: #fff;
box-shadow: 0 -4px 10px rgba(0, 0, 0, 0.3);
}
.footer_inner {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 30px;
}
.footer_title {
display: flex;
flex-direction: column;
text-align: center;
gap: 5px;
}
.fotter_logo {
font-size: 42px;
text-decoration: underline;
}
.footer_date {
font-size: 36px;
}
.footer_place {
font-size: 30px;
}
.footer_contacts {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.footer_cnt {
display: flex;
align-items: center;
gap: 12px;
font-size: 30px;
transition: all .3s;
}
.footer_cnt:hover {
text-decoration: underline;
}
.footer_cnt svg:hover {
transform: scale(1.02);
}
.footer_text {
font-size: 50px;
}
.footer_cntcts {
display: flex;
gap: 30px;
}
.footer_copy {
font-size: 30px;
text-align: end;
}
.footer_hashtag {
text-decoration: underline;
font-size: 36px;
margin-bottom: 10px;
}
.footer_copyright {
}