54 Commits
dev ... front

Author SHA1 Message Date
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 1848 additions and 120 deletions

View File

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

1
.gitignore vendored
View File

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

View File

@@ -1,9 +1,10 @@
- hosts: servers - name: Preflight checks and deploy wedding-site
hosts: servers
become: yes become: yes
vars: vars:
env_file: $(pwd)/.env env_file: $(pwd)/.env
tasks:
tasks:
- name: Install wget - name: Install wget
apt: apt:
name: wget 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: services:
caddy: caddy:
image: caddy:<version> image: caddy:wedding
restart: unless-stopped restart: unless-stopped
cap_add:
- NET_ADMIN
ports: ports:
- "80:80" - "80:80"
- "443:443" - "443:443"
- "443:443/udp"
volumes: volumes:
- $PWD/conf:/etc/caddy
- $PWD/site:/srv
- caddy_data:/data - caddy_data:/data
- caddy_config:/config - caddy_config:/config
networks: 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: volumes:
caddy_data: caddy_data:
caddy_config: caddy_config:
networks: networks:
wedding-site-network: docker_wedding-site-network :
external: true external: true

View File

@@ -0,0 +1,41 @@
http://ru.homyk.space {
redir https://ru.homyk.space{uri} permanent
}
https://ru.homyk.space {
encode gzip
# --- API ---
handle /api/* {
reverse_proxy backend:{$PORT}
}
# --- Frontend ---
root * /srv
file_server
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 image: back:latest
container_name: wedding-site container_name: wedding-site
volumes: volumes:
- wedding_volume:/home/backend - /home/sevice/DB:/home/backend/server/backend/database/DB
- /var/run/docker.sock:/var/run/docker.sock expose:
ports: - "${PORT}"
- "${PORT}:${PORT}"
networks: networks:
- wedding-site-network - wedding-site-network
healthcheck:
test: "curl -f http://localhost:${PORT}/docs"
interval: 5s
timeout: 30s
retries: 1
networks: networks:
wedding-site-network: wedding-site-network:
driver: bridge driver: bridge
volumes:
wedding_volume: {}

View File

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

View File

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

View File

@@ -1,18 +1,15 @@
# Dockerfile.runner # Dockerfile.runner
FROM node:20-bullseye FROM node:20-bullseye
# Устанавливаем зависимости
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
curl \ curl \
lsb-release \ lsb-release \
&& rm -rf /var/lib/apt/lists/* && 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 \ 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" \ && 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 \ > /etc/apt/sources.list.d/docker.list \
&& apt-get update \ && apt-get update \
&& apt-get install -y docker-ce-cli docker-compose-plugin && apt-get install -y docker-ce-cli docker-compose-plugin
# Проверка версий
RUN node -v && npm -v && docker -v && docker compose version RUN node -v && npm -v && docker -v && docker compose version

View File

@@ -1,4 +1,3 @@
#!/bin/sh #!/bin/sh
alembic -c server/backend/database/alembic/alembic.ini revision --autogenerate
alembic -c server/backend/database/alembic/alembic.ini upgrade head 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; 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

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.database.db import create_user, list_users
from server.backend.schema.pydantic import UserCreate
import asyncio
import uvicorn import uvicorn
def start(log_level:str): import asyncio
if __name__ == "__main__": 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( uvicorn.run(
"server.backend.endpoints.endpoints:api", "server.backend.endpoints.endpoints:api",
host="0.0.0.0", host="0.0.0.0",
port=settings.PORT, port=settings.PORT,
reload=True, reload=False,
log_level=log_level, log_level=log_level,
access_log=True access_log=True
) )
import argparse
parser = argparse.ArgumentParser(description="logging and admin creation") parser = argparse.ArgumentParser(description="logging and admin creation")
parser.add_argument( parser.add_argument(
"--mode", "--mode",
choices=["debug","info"], choices=["debug", "info"],
default="info", default="info",
help="Режим логирования (по умолчанию: info)" help="Режим логирования"
) )
parser.add_argument( parser.add_argument(
"--user_name", "--user_name",
type=str, type=str,
required=True, required=True,
help="Создание первого пользователя)" help="Создание первого пользователя"
) )
args = parser.parse_args() args = parser.parse_args()
async def arguments(args):
admin_user = { if __name__ == "__main__":
"code": "123456", asyncio.run(init_admin(args.user_name))
"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":
print("Режим:", args.mode) print("Режим:", args.mode)
start(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) 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())
@@ -46,7 +46,8 @@ async def create_user(user_info):
session.add(new_user) session.add(new_user)
await session.commit() await session.commit()
await session.refresh(new_user) await session.refresh(new_user)
return user else: return None
return new_user
async def update_user(user_info): async def update_user(user_info):
async with AsyncSessionLocal() as session: async with AsyncSessionLocal() as session:

View File

@@ -3,7 +3,7 @@ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import server.backend.schema.pydantic as pydantic import server.backend.schema.pydantic as pydantic
import server.backend.database.db as db import server.backend.database.db as db
from server.backend.auth.JWT import signJWT, decodeJWT from server.backend.auth.JWT import signJWT, decodeJWT
api = FastAPI() api = FastAPI(openapi_url="/api/openapi.json",docs_url="/api/docs", redoc_url="/api/redoc")
security = HTTPBearer() security = HTTPBearer()
async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)): async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
@@ -13,11 +13,14 @@ async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(s
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
return user return user
async def check_roles(user=Depends(get_current_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") raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied")
return user 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)): async def update_user(data: pydantic.UserUpdate, user=Depends(get_current_user)):
user_check = await db.list_user(user["user_id"]) user_check = await db.list_user(user["user_id"])
if not user_check: if not user_check:
@@ -40,17 +43,22 @@ async def update_user(data: pydantic.UserUpdate, user=Depends(get_current_user))
updated_data = await db.update_user(updated_data) updated_data = await db.update_user(updated_data)
return 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)): 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 return user_info
@api.get("/list") @api.get("/api/list")
async def list_users(user=Depends(check_roles)): async def list_users(user=Depends(check_roles)):
list_of_users = await db.list_users() list_of_users = await db.list_users()
return list_of_users return list_of_users
@api.post("/auth",response_model=pydantic.Token) @api.post("/api/auth",response_model=pydantic.Token)
async def auth(code:pydantic.UserAccess): async def auth(code:pydantic.UserAccess):
login = await db.login_user(code) login = await db.login_user(code)
if login == None: if login == None:

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

@@ -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

@@ -1 +0,0 @@
test

View File

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

@@ -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,529 @@
<!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>
</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 {
}