This commit is contained in:
Vladislav 2026-08-02 12:08:08 -04:00
parent c1efb9d36f
commit d60dc1f8c2
68 changed files with 479 additions and 280 deletions

48
auth.py
View File

@ -266,6 +266,8 @@ async def get_user_details(access: str, scopes, response: Response, request: Req
{"hashed_password": 0} {"hashed_password": 0}
) )
if user: if user:
if user.get("blocked"):
raise HTTPException(status_code=403, detail="Аккаунт заблокирован администратором")
user['scopes'] = scopes user['scopes'] = scopes
user['id'] = user_id_str user['id'] = user_id_str
user = bson_clear(user) user = bson_clear(user)
@ -302,6 +304,8 @@ async def get_user(
user = await users_collection.find_one({"_id": ObjectId(user_id_str)}, {"hashed_password": 0}) user = await users_collection.find_one({"_id": ObjectId(user_id_str)}, {"hashed_password": 0})
if not user: if not user:
return None return None
if user.get("blocked"):
return None
scopes = get_all_user_scopes(user.get("roles", []), user.get("extra_scopes", [])) scopes = get_all_user_scopes(user.get("roles", []), user.get("extra_scopes", []))
if request.cookies.get("admin_access_token"): if request.cookies.get("admin_access_token"):
@ -315,3 +319,47 @@ async def get_user(
return None return None
except jwt.InvalidTokenError: except jwt.InvalidTokenError:
return None return None
async def issue_session(user_id: str, request: Request, response: Response,
client_id: str = 'web', scopes=None):
"""Выдаёт новую сессию: JWT access/refresh, запись в Redis и cookie.
Переиспользуемый хелпер (например, для входа/регистрации через VK).
Если scopes не переданы — вычисляются из ролей пользователя.
"""
if scopes is None:
user = await users_collection.find_one({"_id": ObjectId(user_id)})
scopes = get_all_user_scopes(user.get("roles", []), user.get("extra_scopes", []))
client_ip = request.headers.get('X-Real-IP') or (request.client.host if request.client else None)
access_exp = timedelta(days=ACCESS_TOKEN_EXPIRE_MINUTES)
refresh_exp = timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)
access_token = create_jwt_token(
{"sub": user_id, "scopes": scopes, "client_id": client_id, "ip": client_ip},
access_exp,
)
refresh_token = create_jwt_token(
{"sub": user_id, "type": "refresh", "client_id": client_id, "ip": client_ip},
refresh_exp,
)
await redis_client.setex(f"access:{access_token}", int(access_exp.total_seconds()), user_id)
await redis_client.setex(f"refresh:{hash_token(refresh_token)}", int(refresh_exp.total_seconds()), user_id)
session_key = f"user_sessions:{user_id}"
await redis_client.sadd(session_key, f"access:{access_token}")
await redis_client.sadd(session_key, f"refresh:{hash_token(refresh_token)}")
response.set_cookie(
key="access_token", value=access_token, httponly=True, secure=PROD,
samesite="Strict", max_age=int(access_exp.total_seconds()),
)
response.set_cookie(
key="refresh_token", value=refresh_token, httponly=True, secure=PROD,
samesite="Strict", max_age=int(refresh_exp.total_seconds()),
)
return access_token, refresh_token

View File

@ -773,6 +773,74 @@
], ],
"display_name": ".venv" "display_name": ".venv"
}, },
{
"cell_type": "markdown",
"id": "4c434852",
"metadata": {},
"source": [
"### local service"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "45bddd01",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"%%bash\n",
"'tee /etc/systemd/system/staging.service >/dev/null' <<'EOF'\n",
"[Unit]\n",
"Description=FastAPI stag service\n",
"After=network.target\n",
"\n",
"[Service]\n",
"WorkingDirectory=$pwd\n",
"ExecStart=$pwd/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 30082 --reload\n",
"Restart=always\n",
"EnvironmentFile=$pwd/env\n",
"\n",
"[Install]\n",
"WantedBy=default.target\n",
"EOF"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "837698d9",
"metadata": {
"vscode": {
"languageId": "shellscript"
}
},
"outputs": [],
"source": [
"%%bash\n",
"ssh kultmost 'tee /etc/systemd/system/staging.service >/dev/null' <<'EOF'\n",
"[Unit]\n",
"Description=FastAPI stag service\n",
"After=network.target\n",
"\n",
"[Service]\n",
"WorkingDirectory=/app/staging\n",
"ExecStart=/app/staging/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 30082 --reload\n",
"Restart=always\n",
"EnvironmentFile=/app/staging/env\n",
"\n",
"[Install]\n",
"WantedBy=default.target\n",
"EOF\n",
"\n",
"ssh kultmost 'systemctl daemon-reload && systemctl enable --now staging.service && systemctl status --no-pager -l staging.service'\n"
],
"display_name": ".venv"
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "0474516d", "id": "0474516d",

View File

@ -1056,12 +1056,18 @@
" identifier = safe_strip(login_data.identifier)\n", " identifier = safe_strip(login_data.identifier)\n",
" await check_bruteforce(identifier)\n", " await check_bruteforce(identifier)\n",
"\n", "\n",
" users = [doc async for doc in users_collection.find({\n", " or_conditions = [\n",
" \"$or\": [\n",
" {\"email\": identifier},\n", " {\"email\": identifier},\n",
" {\"email\": identifier.lower()}\n", " {\"email\": identifier.lower()},\n",
" ]\n", " ]\n",
" })]\n", " \n",
" try:\n",
" normalized_phone = normalize_phone(identifier)\n",
" if normalized_phone:\n",
" or_conditions.append({\"phone\": normalized_phone})\n",
" except:\n",
" pass\n",
" users = [doc async for doc in users_collection.find({\"$or\": or_conditions})]\n",
"\n", "\n",
"\n", "\n",
" if not users:\n", " if not users:\n",
@ -1790,43 +1796,77 @@
" request: Request,\n", " request: Request,\n",
" restore_data: RestoreRequest = Body(...),\n", " restore_data: RestoreRequest = Body(...),\n",
"):\n", "):\n",
" email = restore_data.identifier.strip().lower()\n", " # 1. Валидация и очистка входных данных\n",
" next = restore_data.next\n", " identifier = restore_data.identifier.strip().lower()\n",
" \n", " \n",
" user = await users_collection.find_one({'$or': [\n", " if not identifier or not is_valid_email(identifier):\n",
" {'email': email}, \n", " raise HTTPException(status_code=400, detail=\"Некорректный формат email\")\n",
" {'email': email.lower()}\n", "\n",
" ]})\n", " # 2. Проверка анти-спама / ограничения частоты запросов\n",
" client_ip = request.client.host\n",
" rate_limit_key = f\"rate_limit:restore:{identifier}:{client_ip}\"\n",
" \n",
" # Проверяем, запрашивал ли пользователь сброс недавно (например, за последние 60 секунд)\n",
" existing_request = await redis_client.get(rate_limit_key)\n",
" \n",
" if existing_request:\n",
" # Опционально: получаем TTL, чтобы сообщить пользователю, когда можно повторить попытку\n",
" ttl = await redis_client.ttl(rate_limit_key)\n",
" raise HTTPException(\n",
" status_code=429, \n",
" detail=f\"Слишком много запросов. Попробуйте через {ttl} секунд.\"\n",
" )\n",
"\n",
" # Устанавливаем флаг ограничения частоты (истекает через 60 секунд)\n",
" await redis_client.set(rate_limit_key, \"1\", ex=60)\n",
"\n",
" # 3. Поиск пользователя\n",
" user = await users_collection.find_one({\"email\": identifier})\n",
" \n", " \n",
" if not user:\n", " if not user:\n",
" raise HTTPException(status_code=404, detail='Email не найден')\n", " # Безопасность: всегда возвращать одинаковое время ответа/структуру сообщения, если возможно\n",
" # Но для удобства использования мы можем сказать \"не найден\".\n",
" # Чтобы предотвратить перечисление, некоторые приложения всегда возвращают \"OK\", но отправляют письмо только если найдено.\n",
" # Здесь мы придерживаемся вашей оригинальной логики, но помните о риске перечисления.\n",
" raise HTTPException(status_code=404, detail=\"Пользователь с таким email не найден\")\n",
" \n", " \n",
" # 4. Генерация токена\n",
" token = secrets.token_urlsafe(32)\n", " token = secrets.token_urlsafe(32)\n",
" expires_at = datetime.utcnow() + timedelta(hours=1)\n", " expires_at = datetime.now(timezone.utc) + timedelta(hours=1)\n",
" \n", " \n",
" token_data = {\n", " token_data = {\n",
" \"user_id\": str(user[\"_id\"]),\n", " \"user_id\": str(user[\"_id\"]),\n",
" \"email\": user[\"email\"],\n", " \"email\": user[\"email\"],\n",
" \"next\": next,\n", " \"next\": restore_data.next,\n",
" \"expires_at\": expires_at.isoformat()\n", " \"expires_at\": expires_at.isoformat()\n",
" }\n", " }\n",
" \n", " \n",
" await redis_client.set(\n", " # 5. Сохранение токена сброса в Redis\n",
" f\"reset_token:{token}\", \n", " reset_token_key = f\"reset_token:{token}\"\n",
" json.dumps(token_data), \n", " await redis_client.set(reset_token_key, json.dumps(token_data), ex=3600)\n",
" ex=3600 \n",
" )\n",
" \n",
" reset_link = f\"{str(request.base_url).rstrip('/')}/restore/{token}\"\n",
"\n", "\n",
" # 6. Генерация ссылки для сброса\n",
" base_url = str(request.base_url).rstrip('/')\n",
" reset_link = f\"{base_url}/restore/{token}\"\n",
"\n",
" # 7. Асинхронная отправка email\n",
" try:\n",
" email_task.notify.delay(\n",
" recipient=user['email'],\n",
" context={\n",
" 'link': reset_link, \n",
" 'email': user['email']\n",
" },\n",
" subject='Восстановление пароля',\n",
" template_name='restore.html'\n",
" )\n",
" except Exception as e:\n",
" # Логирование ошибки\n",
" print(f\"Не удалось отправить письмо: {e}\")\n",
" # В зависимости от политики, вы можете захотеть удалить токен, если письмо не отправлено\n",
" await redis_client.delete(reset_token_key)\n",
" raise HTTPException(status_code=500, detail=\"Ошибка отправки письма\")\n",
"\n", "\n",
" email_task.notify.delay(\n",
" user['email'], \n",
" {'link': reset_link, 'email': user['email']}, \n",
" 'Восстановление пароля', \n",
" 'restore.html'\n",
" )\n",
" \n",
" return {'ok': True}\n", " return {'ok': True}\n",
"\n", "\n",
"\n", "\n",
@ -1880,7 +1920,7 @@
" )\n", " )\n",
"\n", "\n",
"\n", "\n",
"app.include_router(router, tags=['Restore passwrod'])" "app.include_router(router, tags=['Восстановление пароля'])"
], ],
"display_name": ".venv" "display_name": ".venv"
}, },
@ -1894,13 +1934,13 @@
"@app.get(\"/login\", response_class=HTMLResponse)\n", "@app.get(\"/login\", response_class=HTMLResponse)\n",
"async def login_(\n", "async def login_(\n",
" request: Request,\n", " request: Request,\n",
" next: str | None = None,\n", " next_url: str | None = None,\n",
"):\n", "):\n",
" if not next:\n", " if not next_url:\n",
" next = request.headers.get(\"Referer\")\n", " next_url = request.headers.get(\"Referer\")\n",
" context={\"next\": next}\n", " context={\"next\": next_url}\n",
" print(next)\n", " print(next_url)\n",
" if 'profile/competition' in next:\n", " if next_url and 'profile/competition' in next_url:\n",
" context.update({'notify':{'text':'Участвовать в конкурсе могут только пользователи сервиса! Пожалуйста, войдите или зарегистрируйтесь.', 'status':'info','duration':8000}})\n", " context.update({'notify':{'text':'Участвовать в конкурсе могут только пользователи сервиса! Пожалуйста, войдите или зарегистрируйтесь.', 'status':'info','duration':8000}})\n",
" return templates.TemplateResponse(\n", " return templates.TemplateResponse(\n",
" request=request,\n", " request=request,\n",
@ -2749,7 +2789,7 @@
" users = [doc async for doc in users_collection.find({'roles':'user'}).limit(1000)]\n", " users = [doc async for doc in users_collection.find({'roles':'user'}).limit(1000)]\n",
" return templates.TemplateResponse(\n", " return templates.TemplateResponse(\n",
" request=request, name=\"users/index.html\", context={\"id\": 1, \"user\":user, \"users\": users}\n", " request=request, name=\"users/index.html\", context={\"id\": 1, \"user\":user, \"users\": users}\n",
" )" " )\n"
], ],
"display_name": ".venv" "display_name": ".venv"
}, },
@ -3448,7 +3488,7 @@
" \"serviceSocial\": payload.serviceSocial,\n", " \"serviceSocial\": payload.serviceSocial,\n",
" \"userSocial\": payload.userSocial,\n", " \"userSocial\": payload.userSocial,\n",
" \"short_desc\": safe_strip(payload.short_desc) if payload.short_desc else None,\n", " \"short_desc\": safe_strip(payload.short_desc) if payload.short_desc else None,\n",
" \"poster_file_id\": poster_id,\n", " \"poster_file_id\": po/api/restorster_id,\n",
" \"poster_orig_id\": poster_id,\n", " \"poster_orig_id\": poster_id,\n",
" \"rules_file_id\": rules_id,\n", " \"rules_file_id\": rules_id,\n",
" \"rules_link\": payload.rules_link,\n", " \"rules_link\": payload.rules_link,\n",

25
main.py
View File

@ -788,6 +788,18 @@ async def login(
{"email": identifier.lower()} {"email": identifier.lower()}
] ]
})] })]
or_conditions = [
{"email": identifier},
{"email": identifier.lower()},
]
try:
normalized_phone = normalize_phone(identifier)
if normalized_phone:
or_conditions.append({"phone": normalized_phone})
except:
pass
users = [doc async for doc in users_collection.find({"$or": or_conditions})]
if not users: if not users:
@ -1417,13 +1429,13 @@ app.include_router(router, tags=['Restore passwrod'])
@app.get("/login", response_class=HTMLResponse) @app.get("/login", response_class=HTMLResponse)
async def login_( async def login_(
request: Request, request: Request,
next: str | None = None, next_url: str | None = None,
): ):
if not next: if not next_url:
next = request.headers.get("Referer") next_url = request.headers.get("Referer")
context={"next": next} context={"next": next_url}
print(next) print(next_url)
if 'profile/competition' in next: if next_url and 'profile/competition' in next_url:
context.update({'notify':{'text':'Участвовать в конкурсе могут только пользователи сервиса! Пожалуйста, войдите или зарегистрируйтесь.', 'status':'info','duration':8000}}) context.update({'notify':{'text':'Участвовать в конкурсе могут только пользователи сервиса! Пожалуйста, войдите или зарегистрируйтесь.', 'status':'info','duration':8000}})
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, request=request,
@ -2041,6 +2053,7 @@ async def artists(
request=request, name="users/index.html", context={"id": 1, "user":user, "users": users} request=request, name="users/index.html", context={"id": 1, "user":user, "users": users}
) )
@app.get("/my-paints", response_class=HTMLResponse) @app.get("/my-paints", response_class=HTMLResponse)
async def My_paints( async def My_paints(
request: Request, request: Request,

3
migrate.py Normal file
View File

@ -0,0 +1,3 @@
from db import *
await posts_collection.update_many({'status':'publish'},{'$set':{'status':'В каталоге'}})

View File

@ -74,7 +74,7 @@ class LoginRequest(BaseModel):
class RestoreRequest(BaseModel): class RestoreRequest(BaseModel):
identifier: str identifier: str
next: str next: str | None = None
class ResetPasswordRequest(BaseModel): class ResetPasswordRequest(BaseModel):

View File

@ -5,7 +5,7 @@
viewBox="0 0 0.37 0.37" viewBox="0 0 0.37 0.37"
xmlns:xlink="http://www.w3.org/1999/xlink"> xmlns:xlink="http://www.w3.org/1999/xlink">
<defs> <defs>
<style type="text/css"> <style>
<![CDATA[ <![CDATA[
.str0 {stroke:#2B2A29;stroke-width:0.04;stroke-miterlimit:22.9256} .str0 {stroke:#2B2A29;stroke-width:0.04;stroke-miterlimit:22.9256}
.fil0 {fill:none} .fil0 {fill:none}

Before

Width:  |  Height:  |  Size: 891 B

After

Width:  |  Height:  |  Size: 875 B

View File

@ -5,7 +5,7 @@
viewBox="0 0 0.37 0.37" viewBox="0 0 0.37 0.37"
xmlns:xlink="http://www.w3.org/1999/xlink"> xmlns:xlink="http://www.w3.org/1999/xlink">
<defs> <defs>
<style type="text/css"> <style>
<![CDATA[ <![CDATA[
.str0 {stroke:#2B2A29;stroke-width:0.04;stroke-miterlimit:22.9256} .str0 {stroke:#2B2A29;stroke-width:0.04;stroke-miterlimit:22.9256}
.fil0 {fill:none} .fil0 {fill:none}

Before

Width:  |  Height:  |  Size: 891 B

After

Width:  |  Height:  |  Size: 875 B

View File

@ -7,13 +7,13 @@
{% block title %}Сервис Автопостинга{% endblock %} {% block title %}Сервис Автопостинга{% endblock %}
{% block head %} {% block head %}
<meta hid="og:title" property="og:title" content="Сервис Автопостинга"> <meta property="og:title" content="Сервис Автопостинга">
<meta hid="og:description" property="og:description" content="Сервис создан для художников, скульпторов, искусствоведов, журналистов, галеристов, музеев и разных выставочных пространств с возможностью выложить пост картины, скульптуры, новостей, анонсов выставок или любую другую информацию, разместив сразу в нескольких Ваших социальных сетях интернета, арт-маркетплейсах и сообществах партнёров сервиса одним нажатием!"> <meta property="og:description" content="Сервис создан для художников, скульпторов, искусствоведов, журналистов, галеристов, музеев и разных выставочных пространств с возможностью выложить пост картины, скульптуры, новостей, анонсов выставок или любую другую информацию, разместив сразу в нескольких Ваших социальных сетях интернета, арт-маркетплейсах и сообществах партнёров сервиса одним нажатием!">
<meta hid="og:image" property="og:image" content="https://avtopost.kultmost.ru/favicon.ico"> <meta property="og:image" content="https://avtopost.kultmost.ru/favicon.ico">
<meta name="facebook-domain-verification" content="uwjj4p3kr1xlxvcux4ujv7b6qujhl8"> <meta name="facebook-domain-verification" content="uwjj4p3kr1xlxvcux4ujv7b6qujhl8">
<meta hid="og:url" property="og:url" content="https://avtopost.kultmost.ru/"> <meta property="og:url" content="https://avtopost.kultmost.ru/">
<meta hid="description" name="description" content="Сервис создан для художников, скульпторов, искусствоведов, журналистов, галеристов, музеев и разных выставочных пространств с возможностью выложить пост картины, скульптуры, новостей, анонсов выставок или любую другую информацию, разместив сразу в нескольких Ваших социальных сетях интернета, арт-маркетплейсах и сообществах партнёров сервиса одним нажатием!"> <meta name="description" content="Сервис создан для художников, скульпторов, искусствоведов, журналистов, галеристов, музеев и разных выставочных пространств с возможностью выложить пост картины, скульптуры, новостей, анонсов выставок или любую другую информацию, разместив сразу в нескольких Ваших социальных сетях интернета, арт-маркетплейсах и сообществах партнёров сервиса одним нажатием!">
<meta hid="og:site_name" property="og:site_name" content="KULTMOST.АВТОПОСТ"> <meta property="og:site_name" content="KULTMOST.АВТОПОСТ">
{{ header.style() }} {{ header.style() }}
<style> <style>
.newAbout .logotip { .newAbout .logotip {

View File

@ -32,7 +32,7 @@
{%- endmacro %} {%- endmacro %}
{% macro style() -%} {% macro style() -%}
<style type="text/css"> <style>
h2,p { h2,p {
max-width: 100%; max-width: 100%;
overflow-wrap: break-word; overflow-wrap: break-word;

File diff suppressed because one or more lines are too long

View File

@ -110,7 +110,7 @@
{%- endmacro %} {%- endmacro %}
{% macro style() -%} {% macro style() -%}
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
.c_aq9et { .c_aq9et {
cursor: pointer; cursor: pointer;
transition: transform 0.1s linear, box‑shadow 0.1s linear; transition: transform 0.1s linear, box‑shadow 0.1s linear;

View File

@ -206,7 +206,8 @@
</article> </article>
</div> </div>
</form> </form>
<script defer> <script>
document.addEventListener('DOMContentLoaded', () => {
/* ---------- Utility helpers (removed) ---------- */ /* ---------- Utility helpers (removed) ---------- */
/* Toggle visibility of a block (flex / none) */ /* Toggle visibility of a block (flex / none) */
@ -340,9 +341,10 @@ function bindLinkValidation({
linkErrorSelector: '#link-error' linkErrorSelector: '#link-error'
}); });
});
</script> </script>
<script defer> <script>
document.addEventListener('DOMContentLoaded', () => {
const competition = JSON.parse({{competition|tojson}}) const competition = JSON.parse({{competition|tojson}})
const competitionId = competition._id const competitionId = competition._id
@ -429,6 +431,7 @@ document.getElementById('preview_poster').src = `/b/competition/${competition.po
// input.value = moscowInstant.toISOString().slice(0, 16); // input.value = moscowInstant.toISOString().slice(0, 16);
// date_finish.value = date_finish_date.toISOString().slice(0, 10); // date_finish.value = date_finish_date.toISOString().slice(0, 10);
});
</script> </script>
<script> <script>
var type = competition.type_; var type = competition.type_;
@ -447,7 +450,8 @@ document.getElementById('preview_poster').src = `/b/competition/${competition.po
} }
} }
</script> </script>
<script defer> <script>
document.addEventListener('DOMContentLoaded', () => {
const minInput = document.getElementById('min_val'); const minInput = document.getElementById('min_val');
const maxInput = document.getElementById('max_val'); const maxInput = document.getElementById('max_val');
@ -633,15 +637,14 @@ document.getElementById('preview_poster').src = `/b/competition/${competition.po
} }
}); });
// Инициализация });
// updateRange();
</script> </script>
{%- endmacro %} {%- endmacro %}
{% macro style() -%} {% macro style() -%}
{{ competition.style() }} {{ competition.style() }}
{{ checkbox.style() }} {{ checkbox.style() }}
<style type="text/css"> <style>
.toggle-group { .toggle-group {
display: flex; display: flex;
margin-bottom: 15px; margin-bottom: 15px;

View File

@ -574,7 +574,7 @@ document.addEventListener('DOMContentLoaded', () => {
{% macro style() -%} {% macro style() -%}
{{ competition.style() }} {{ competition.style() }}
{{ checkbox.style() }} {{ checkbox.style() }}
<style type="text/css"> <style>
.toggle-group { .toggle-group {
display: flex; display: flex;
margin-bottom: 15px; margin-bottom: 15px;

View File

@ -190,15 +190,15 @@
{% endblock %} {% endblock %}
{% block head %} {% block head %}
<meta hid="og:title" property="og:title" content="{{competition.title}}"> <meta property="og:title" content="{{competition.title}}">
<meta hid="og:description" property="og:description" content="{{competition.short_desc}}"> <meta property="og:description" content="{{competition.short_desc}}">
<!-- <meta hid="og:type" property="og:type" content="product"> --> <!-- <meta property="og:type" content="product"> -->
<meta hid="og:image" property="og:image" content="https://avtopost.kultmost.ru/b/competition/{{competition.poster_file_id}}"> <meta property="og:image" content="https://avtopost.kultmost.ru/b/competition/{{competition.poster_file_id}}">
<meta hid="og:url" property="og:url" content="https://avtopost.kultmost.ru/competition/{{competition._id}}"> <meta property="og:url" content="https://avtopost.kultmost.ru/competition/{{competition._id}}">
<meta hid="description" name="description" content="{{competition.short_desc}}"> <meta name="description" content="{{competition.short_desc}}">
<meta hid="og:site_name" property="og:site_name" content="Конкурсы | KULTMOST.АВТОПОСТ"> <meta property="og:site_name" content="Конкурсы | KULTMOST.АВТОПОСТ">
{{ header.style() }} {{ header.style() }}
<style type="text/css"> <style>
.b_thlt1 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; } .b_thlt1 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }
.b_5dr47 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; } .b_5dr47 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_thlt1_2 { align-items: center; background: rgba(95, 23, 152, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; } .b_thlt1_2 { align-items: center; background: rgba(95, 23, 152, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }
@ -271,7 +271,7 @@ picture>img{width: 100%;object-fit: contain;}
.c_fyi26 { align-content: flex-start; align-items: flex-start; align-self: stretch; display: flex; flex-wrap: wrap; gap: 20px; padding-left: 100px; } .c_fyi26 { align-content: flex-start; align-items: flex-start; align-self: stretch; display: flex; flex-wrap: wrap; gap: 20px; padding-left: 100px; }
.c_r75b6 { align-items: flex-start; background: #FFF; display: flex; justify-content: center; width: 1600px; } .c_r75b6 { align-items: flex-start; background: #FFF; display: flex; justify-content: center; width: 1600px; }
}</style> }</style>
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
.inptS{height: 48px;display: flex;align-items: center;gap: 10px; border-bottom: 1px solid #5F1798;} .inptS{height: 48px;display: flex;align-items: center;gap: 10px; border-bottom: 1px solid #5F1798;}
.b_x41jk { fill: #5F1798; height: 12px; width: 12px; } .b_x41jk { fill: #5F1798; height: 12px; width: 12px; }
.b_rroqu { height: 18px; width: 18px; } .b_rroqu { height: 18px; width: 18px; }

View File

@ -17,7 +17,7 @@
{% macro style() -%} {% macro style() -%}
{{card.style()}} {{card.style()}}
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
.b_p0hpm { color: #5F1798; font-family: Inter; font-size: 42px; font-style: normal; font-weight: 700; line-height: normal; text-transform: uppercase; } .b_p0hpm { color: #5F1798; font-family: Inter; font-size: 42px; font-style: normal; font-weight: 700; line-height: normal; text-transform: uppercase; }
/* Mobile */ /* Mobile */
@media (max-width: 699px) { @media (max-width: 699px) {

View File

@ -147,7 +147,7 @@
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
<style type="text/css"> <style>
.b_thlt1 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; } .b_thlt1 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }
.b_5dr47 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; } .b_5dr47 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_thlt1_2 { align-items: center; background: rgba(95, 23, 152, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; } .b_thlt1_2 { align-items: center; background: rgba(95, 23, 152, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }

View File

@ -1,5 +1,5 @@
{% macro style() %} {% macro style() %}
<style type="text/css"> <style>
.offer-label { .offer-label {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -12,15 +12,15 @@
{% endif %} {% endif %}
<footer class="c_t5jtn"> <footer class="c_t5jtn">
<div class="c_00gkd"><span class="c_62bje">KULTMOST</span><span class="c_jtxml">.АВТОПОСТ </span></div> <div class="c_00gkd"><span class="c_62bje">KULTMOST</span><span class="c_jtxml">.АВТОПОСТ </span></div>
<nav class="c_wmdgs"><a class="c_bygqa">Главная</a><a class="c_bygqa">Маркетплейс</a><a class="c_bygqa">Мои <nav class="c_wmdgs"><a class="c_bygqa" href="/">Главная</a><a class="c_bygqa" href="https://kultmost.ru/post">Маркетплейс</a><a class="c_bygqa" href="/my-paints">Мои
картины</a><a class="c_bygqa">Художники</a><a class="c_bygqa">Партнеры</a><a class="c_bygqa">О картины</a><a class="c_bygqa" href="/artists">Художники</a><a class="c_bygqa" href="/partner">Партнеры</a><a class="c_bygqa" href="/about">О
платформе</a><a class="c_bygqa">Новости</a><a class="c_bygqa">Админ-панель</a></nav> платформе</a><a class="c_bygqa" href="/news">Новости</a></nav>
<p class="c_vii7u">Автономная некоммерческая организация развития художественного творчества и ремесел «Культурный мост «Передвижные выставки» © 2026</p> <p class="c_vii7u">Автономная некоммерческая организация развития художественного творчества и ремесел «Культурный мост «Передвижные выставки» © 2026</p>
</footer> </footer>
{%- endmacro %} {%- endmacro %}
{% macro style() -%} {% macro style() -%}
<style type="text/css"> <style>
.st { .st {
position: sticky; position: sticky;
bottom: 0; bottom: 0;

View File

@ -159,22 +159,10 @@ document.addEventListener('DOMContentLoaded', () => {
</script> </script>
{% endif %} {% endif %}
{% endif %} {% endif %}
<style>
.nav-icon {display: flex;flex-direction: column;justify-content: space-around;height: 24px;cursor: pointer;position: relative;width: 100%;}
.line {right: 0;display: block;width: 42px;height: 0;transition: all 0.3s ease;position: absolute;border: 1px solid;}
.line1{top:3px}
.line2{width: 32px;top: 10px;}
.line3{top: 18px;}
.line1.active {max-width: 30px;top: 9px;transform: rotate(135deg);}
.line2.active {transform: translateX(-500px);}
.line3.active {max-width: 30px;top: 9px;transform: rotate(-135deg);}
</style>
<header class="c_xirl1"> <header class="c_xirl1">
<div class="c_uh9b6"> <div class="c_uh9b6">
<a class="c_7dj6c" href="/"> <a class="c_7dj6c" href="/">
<img src="/static/logo375.jpeg" class="c_vnonn b_cj7sg" crossorigin="anonymous" fetchpriority="high"> <img src="/static/logo375.jpeg" alt="logo avtopost kultmost" class="c_vnonn b_cj7sg" crossorigin="anonymous" fetchpriority="high">
</a> </a>
<div class="avatar"> <div class="avatar">
{% if user %} {% if user %}
@ -183,15 +171,14 @@ document.addEventListener('DOMContentLoaded', () => {
</a> </a>
{% endif %} {% endif %}
<button id="menu_button" class="c_pcz1l b_oxx99" aria-expanded="false"> <button id="menu_button" class="c_pcz1l b_oxx99" aria-expanded="false">
<div class="nav-icon"> <span class="nav-icon">
<span class="line line1"></span> <span class="line line1"></span>
<span class="line line2"></span> <span class="line line2"></span>
<span class="line line3"></span> <span class="line line3"></span>
</div> </span>
</button> </button>
</div> </div>
</div> </div>
<nav id="menu" class="c_olmk1"> <nav id="menu" class="c_olmk1">
<a class="c_qmp95 b_fokm1" href="/">Главная</a> <a class="c_qmp95 b_fokm1" href="/">Главная</a>
<a class="c_qmp95 b_fokm1" href="https://kultmost.ru/post">Маркетплейс</a> <a class="c_qmp95 b_fokm1" href="https://kultmost.ru/post">Маркетплейс</a>
@ -210,7 +197,8 @@ document.addEventListener('DOMContentLoaded', () => {
{% endif %} {% endif %}
{% if user and 'back' in user.get('scopes',[]) %} {% if user and 'back' in user.get('scopes',[]) %}
<span id="backBtn" class="c_qmp95 b_fokm1 btn">Переключиться на Админа</span> <span id="backBtn" class="c_qmp95 b_fokm1 btn">Переключиться на Админа</span>
<script defer> <script>
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('backBtn').addEventListener('click', async () => { document.getElementById('backBtn').addEventListener('click', async () => {
try { try {
const resp = await fetch('/api/admin/back', { method: 'POST' }); const resp = await fetch('/api/admin/back', { method: 'POST' });
@ -224,6 +212,7 @@ document.addEventListener('DOMContentLoaded', () => {
console.error('Network error:', err); console.error('Network error:', err);
} }
}); });
});
</script> </script>
{% endif %} {% endif %}
<span <span
@ -288,7 +277,17 @@ document.addEventListener('DOMContentLoaded', () => {
{%- endmacro %} {%- endmacro %}
{% macro style() -%} {% macro style() -%}
<style type="text/css">/* Base (common) styles */ <style>
.nav-icon {display: flex;flex-direction: column;justify-content: space-around;height: 24px;cursor: pointer;position: relative;width: 100%;}
.line {right: 0;display: block;width: 42px;height: 0;transition: all 0.3s ease;position: absolute;border: 1px solid;}
.line1{top:3px}
.line2{width: 32px;top: 10px;}
.line3{top: 18px;}
.line1.active {max-width: 30px;top: 9px;transform: rotate(135deg);}
.line2.active {transform: translateX(-500px);}
.line3.active {max-width: 30px;top: 9px;transform: rotate(-135deg);}
</style>
<style>/* Base (common) styles */
.closeNotify{display: none;position: fixed; .closeNotify{display: none;position: fixed;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;

View File

@ -111,7 +111,7 @@
{% endmacro %} {% endmacro %}
{% macro style() -%} {% macro style() -%}
<style type="text/css"> <style>
.b_xsvl1 { color: #5F1798; font-family: Inter; font-size: 16px; font-style: normal; font-weight: 700; line-height: normal; text-align: center; padding: 0 10px;} .b_xsvl1 { color: #5F1798; font-family: Inter; font-size: 16px; font-style: normal; font-weight: 700; line-height: normal; text-align: center; padding: 0 10px;}
.b_u9ode { align-items: center; align-self: stretch; background: #FFF; border-radius: 5px; display: flex; justify-content: center; padding: 10px 0; } .b_u9ode { align-items: center; align-self: stretch; background: #FFF; border-radius: 5px; display: flex; justify-content: center; padding: 10px 0; }
.b_al8e7 { color: #FFF; font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; } .b_al8e7 { color: #FFF; font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; }

View File

@ -6,7 +6,7 @@
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
<style type="text/css"> <style>
body {font-family: Arial, sans-serif;} body {font-family: Arial, sans-serif;}
.modal {display: none;position: fixed;z-index: 1;left: 0;top: 0;width: 100%;height: 100%;overflow: auto;background-color: rgba(0, 0, 0, 0.7);} .modal {display: none;position: fixed;z-index: 1;left: 0;top: 0;width: 100%;height: 100%;overflow: auto;background-color: rgba(0, 0, 0, 0.7);}
.modal-content {background-color: #fff;margin: 15% auto;padding: 20px;border: 1px solid #888;width: 80%;} .modal-content {background-color: #fff;margin: 15% auto;padding: 20px;border: 1px solid #888;width: 80%;}

View File

@ -148,15 +148,15 @@
{% endblock %} {% endblock %}
{% block head %} {% block head %}
<meta hid="og:title" property="og:title" content="{{exhibition.title}}"> <meta property="og:title" content="{{exhibition.title}}">
<meta hid="og:description" property="og:description" content="{{exhibition.short_desc}}"> <meta property="og:description" content="{{exhibition.short_desc}}">
<!-- <meta hid="og:type" property="og:type" content="product"> --> <!-- <meta property="og:type" content="product"> -->
<meta hid="og:image" property="og:image" content="https://avtopost.kultmost.ru/b/exhibition/{{exhibition.poster_file_id}}"> <meta property="og:image" content="https://avtopost.kultmost.ru/b/exhibition/{{exhibition.poster_file_id}}">
<meta hid="og:url" property="og:url" content="https://avtopost.kultmost.ru/exhibition/{{exhibition._id}}"> <meta property="og:url" content="https://avtopost.kultmost.ru/exhibition/{{exhibition._id}}">
<meta hid="description" name="description" content="{{exhibition.short_desc}}"> <meta name="description" content="{{exhibition.short_desc}}">
<meta hid="og:site_name" property="og:site_name" content="Конкурсы | KULTMOST.АВТОПОСТ"> <meta property="og:site_name" content="Конкурсы | KULTMOST.АВТОПОСТ">
{{ header.style() }} {{ header.style() }}
<style type="text/css"> <style>
.b_thlt1 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; } .b_thlt1 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }
.b_5dr47 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; } .b_5dr47 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_thlt1_2 { align-items: center; background: rgba(95, 23, 152, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; } .b_thlt1_2 { align-items: center; background: rgba(95, 23, 152, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }
@ -229,7 +229,7 @@ picture>img{width: 100%;object-fit: contain;}
.c_fyi26 { align-content: flex-start; align-items: flex-start; align-self: stretch; display: flex; flex-wrap: wrap; gap: 20px; padding-left: 100px; } .c_fyi26 { align-content: flex-start; align-items: flex-start; align-self: stretch; display: flex; flex-wrap: wrap; gap: 20px; padding-left: 100px; }
.c_r75b6 { align-items: flex-start; background: #FFF; display: flex; justify-content: center; width: 1600px; } .c_r75b6 { align-items: flex-start; background: #FFF; display: flex; justify-content: center; width: 1600px; }
}</style> }</style>
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
.inptS{height: 48px;display: flex;align-items: center;gap: 10px; border-bottom: 1px solid #5F1798;} .inptS{height: 48px;display: flex;align-items: center;gap: 10px; border-bottom: 1px solid #5F1798;}
.b_x41jk { fill: #5F1798; height: 12px; width: 12px; } .b_x41jk { fill: #5F1798; height: 12px; width: 12px; }
.b_rroqu { height: 18px; width: 18px; } .b_rroqu { height: 18px; width: 18px; }

View File

@ -93,7 +93,7 @@
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
.c_aq9et { .c_aq9et {
cursor: pointer; cursor: pointer;
transition: transform 0.1s linear, box‑shadow 0.1s linear; transition: transform 0.1s linear, box‑shadow 0.1s linear;
@ -134,7 +134,7 @@ picture>img{ width: 100%; object-fit: scale-down; }
.b_ornr9{height: auto;} .b_ornr9{height: auto;}
} }
</style> </style>
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
.b_p0hpm { color: #5F1798; font-family: Inter; font-size: 42px; font-style: normal; font-weight: 700; line-height: normal; text-transform: uppercase; } .b_p0hpm { color: #5F1798; font-family: Inter; font-size: 42px; font-style: normal; font-weight: 700; line-height: normal; text-transform: uppercase; }
/* Mobile */ /* Mobile */
@media (max-width: 699px) { @media (max-width: 699px) {

View File

@ -561,7 +561,7 @@ function bindLinkValidation({
{{ header.style() }} {{ header.style() }}
{{ competition.style() }} {{ competition.style() }}
{{ checkbox.style() }} {{ checkbox.style() }}
<style type="text/css"> <style>
.toggle-group { .toggle-group {
display: flex; display: flex;
margin-bottom: 15px; margin-bottom: 15px;

View File

@ -555,7 +555,7 @@ bindLinkValidation({
{{ header.style() }} {{ header.style() }}
{{ competition.style() }} {{ competition.style() }}
{{ checkbox.style() }} {{ checkbox.style() }}
<style type="text/css"> <style>
.toggle-group { .toggle-group {
display: flex; display: flex;
margin-bottom: 15px; margin-bottom: 15px;

View File

@ -7,13 +7,13 @@
{% block title %}Сервис Автопостинга{% endblock %} {% block title %}Сервис Автопостинга{% endblock %}
{% block head %} {% block head %}
<meta hid="og:title" property="og:title" content="Сервис Автопостинга"> <meta property="og:title" content="Сервис Автопостинга">
<meta hid="og:description" property="og:description" content="Сервис создан для художников, скульпторов, искусствоведов, журналистов, галеристов, музеев и разных выставочных пространств с возможностью выложить пост картины, скульптуры, новостей, анонсов выставок или любую другую информацию, разместив сразу в нескольких Ваших социальных сетях интернета, арт-маркетплейсах и сообществах партнёров сервиса одним нажатием!"> <meta property="og:description" content="Сервис создан для художников, скульпторов, искусствоведов, журналистов, галеристов, музеев и разных выставочных пространств с возможностью выложить пост картины, скульптуры, новостей, анонсов выставок или любую другую информацию, разместив сразу в нескольких Ваших социальных сетях интернета, арт-маркетплейсах и сообществах партнёров сервиса одним нажатием!">
<meta hid="og:image" property="og:image" content="https://avtopost.kultmost.ru/favicon.ico"> <meta property="og:image" content="https://avtopost.kultmost.ru/favicon.ico">
<meta name="facebook-domain-verification" content="uwjj4p3kr1xlxvcux4ujv7b6qujhl8"> <meta name="facebook-domain-verification" content="uwjj4p3kr1xlxvcux4ujv7b6qujhl8">
<meta hid="og:url" property="og:url" content="https://avtopost.kultmost.ru/"> <meta property="og:url" content="https://avtopost.kultmost.ru/">
<meta hid="description" name="description" content="Сервис создан для художников, скульпторов, искусствоведов, журналистов, галеристов, музеев и разных выставочных пространств с возможностью выложить пост картины, скульптуры, новостей, анонсов выставок или любую другую информацию, разместив сразу в нескольких Ваших социальных сетях интернета, арт-маркетплейсах и сообществах партнёров сервиса одним нажатием!"> <meta name="description" content="Сервис создан для художников, скульпторов, искусствоведов, журналистов, галеристов, музеев и разных выставочных пространств с возможностью выложить пост картины, скульптуры, новостей, анонсов выставок или любую другую информацию, разместив сразу в нескольких Ваших социальных сетях интернета, арт-маркетплейсах и сообществах партнёров сервиса одним нажатием!">
<meta hid="og:site_name" property="og:site_name" content="KULTMOST.АВТОПОСТ"> <meta property="og:site_name" content="KULTMOST.АВТОПОСТ">
<link rel="canonical" href="https://avtopost.kultmost.ru/"/> <link rel="canonical" href="https://avtopost.kultmost.ru/"/>
{{ header.style() }} {{ header.style() }}
{{ main.style() }} {{ main.style() }}

View File

@ -20,7 +20,7 @@
<input type="password" id="password" name="password" class="c_xhd5k" required> <input type="password" id="password" name="password" class="c_xhd5k" required>
<label for="password" class="c_z3ybu floating-label">Пароль</label> <label for="password" class="c_z3ybu floating-label">Пароль</label>
<div type="button" class="password-toggle" aria-label="Переключатель видимости пароля"> <button type="button" class="password-toggle" aria-label="Переключатель видимости пароля">
<svg class="eye eye-open" width="24" height="24" viewBox="0 0 24 24" fill="none" <svg class="eye eye-open" width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg"> xmlns="http://www.w3.org/2000/svg">
<path <path
@ -33,7 +33,7 @@
<path <path
d="m644-428-58-58q9-47-27-88t-93-32l-58-58q17-8 34.5-12t37.5-4q75 0 127.5 52.5T660-500q0 20-4 37.5T644-428Zm128 126-58-56q38-29 67.5-63.5T832-500q-50-101-143.5-160.5T480-720q-29 0-57 4t-55 12l-62-62q41-17 84-25.5t90-8.5q151 0 269 83.5T920-500q-23 59-60.5 109.5T772-302Zm20 246L624-222q-35 11-70.5 16.5T480-200q-151 0-269-83.5T40-500q21-53 53-98.5t73-81.5L56-792l56-56 736 736-56 56ZM222-624q-29 26-53 57t-41 67q50 101 143.5 160.5T480-280q20 0 39-2.5t39-5.5l-36-38q-11 3-21 4.5t-21 1.5q-75 0-127.5-52.5T300-500q0-11 1.5-21t4.5-21l-84-82Zm319 93Zm-151 75Z" /> d="m644-428-58-58q9-47-27-88t-93-32l-58-58q17-8 34.5-12t37.5-4q75 0 127.5 52.5T660-500q0 20-4 37.5T644-428Zm128 126-58-56q38-29 67.5-63.5T832-500q-50-101-143.5-160.5T480-720q-29 0-57 4t-55 12l-62-62q41-17 84-25.5t90-8.5q151 0 269 83.5T920-500q-23 59-60.5 109.5T772-302Zm20 246L624-222q-35 11-70.5 16.5T480-200q-151 0-269-83.5T40-500q21-53 53-98.5t73-81.5L56-792l56-56 736 736-56 56ZM222-624q-29 26-53 57t-41 67q50 101 143.5 160.5T480-280q20 0 39-2.5t39-5.5l-36-38q-11 3-21 4.5t-21 1.5q-75 0-127.5-52.5T300-500q0-11 1.5-21t4.5-21l-84-82Zm319 93Zm-151 75Z" />
</svg> </svg>
</div> </button>
</div> </div>
<span class="login-hint">Минимум 8 символов</span> <span class="login-hint">Минимум 8 символов</span>
@ -59,8 +59,7 @@
</div> </div>
</div> </div>
</form> </form>
<script>
<script>
function sendToNextPage(e) { function sendToNextPage(e) {
e.preventDefault(); e.preventDefault();
@ -72,8 +71,9 @@ function sendToNextPage(e) {
window.location.href = "/restore{% if next %}?next={{next}}{% endif %}"; window.location.href = "/restore{% if next %}?next={{next}}{% endif %}";
} }
</script> </script>
<script defer> <script>
document.addEventListener('DOMContentLoaded', () => {
const emailInput = document.getElementById('email'); const emailInput = document.getElementById('email');
const passwordInput = document.getElementById('password'); const passwordInput = document.getElementById('password');
const createBtn = document.getElementById('createBtn'); const createBtn = document.getElementById('createBtn');
@ -209,7 +209,7 @@ function sendToNextPage(e) {
} }
}); });
} }
});
</script> </script>
{{ footer.section() }} {{ footer.section() }}
<script> <script>
@ -234,7 +234,7 @@ fetch('/api/me')
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
<style type="text/css"> <style>
.poster-group {position: relative;} .poster-group {position: relative;}
.password-group { position: relative; } .password-group { position: relative; }
a{color: #5F1798;} a{color: #5F1798;}

View File

@ -12,7 +12,7 @@
class="c_ifykb">Видео-презентация</span> </button> class="c_ifykb">Видео-презентация</span> </button>
</div> </div>
</div> </div>
<picture class="c_qa0pb"><img crossorigin="anonymous" fetchpriority="high" class="c_qa0pb" src="/static/painter.svg"></picture> <picture class="c_qa0pb"><img crossorigin="anonymous" fetchpriority="high" class="c_qa0pb" src="/static/painter.svg" alt=""></picture>
</main> </main>
<div id="modalVideo" class="modal" style="display: none;"> <div id="modalVideo" class="modal" style="display: none;">
<span class="close" onclick="videocloseModalButton()" style="z-index: 2;">&times;</span> <span class="close" onclick="videocloseModalButton()" style="z-index: 2;">&times;</span>
@ -37,7 +37,7 @@
{%- endmacro %} {%- endmacro %}
{% macro style() -%} {% macro style() -%}
<style type="text/css"> <style>
body { body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
} }

View File

@ -355,7 +355,7 @@ display: flex;
color: white; color: white;
} }
</style> </style>
<style type="text/css"> <style>
.poster-group {position: relative;} .poster-group {position: relative;}
.file-name-display {width: 100%;padding: 16px 50px 16px 16px;border: 2px solid #ddd;border-radius: 8px;font-size: 16px;color: #555;background: #fff;min-height: 56px;display: flex;align-items: center;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;transition: all 0.3s ease;cursor: pointer;} .file-name-display {width: 100%;padding: 16px 50px 16px 16px;border: 2px solid #ddd;border-radius: 8px;font-size: 16px;color: #555;background: #fff;min-height: 56px;display: flex;align-items: center;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;transition: all 0.3s ease;cursor: pointer;}

View File

@ -354,7 +354,7 @@ display: flex;
color: white; color: white;
} }
</style> </style>
<style type="text/css"> <style>
.poster-group {position: relative;} .poster-group {position: relative;}
.file-name-display {width: 100%;padding: 16px 50px 16px 16px;border: 2px solid #ddd;border-radius: 8px;font-size: 16px;color: #555;background: #fff;min-height: 56px;display: flex;align-items: center;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;transition: all 0.3s ease;cursor: pointer;} .file-name-display {width: 100%;padding: 16px 50px 16px 16px;border: 2px solid #ddd;border-radius: 8px;font-size: 16px;color: #555;background: #fff;min-height: 56px;display: flex;align-items: center;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;transition: all 0.3s ease;cursor: pointer;}

View File

@ -5,15 +5,15 @@
{% block title %}Сервис Автопостинга{% endblock %} {% block title %}Сервис Автопостинга{% endblock %}
{% block head %} {% block head %}
<!-- <meta hid="og:title" property="og:title" content="Сервис Автопостинга"> <!-- <meta property="og:title" content="Сервис Автопостинга">
<meta hid="og:description" property="og:description" content="Сервис создан для художников, скульпторов, искусствоведов, журналистов, галеристов, музеев и разных выставочных пространств с возможностью выложить пост картины, скульптуры, новостей, анонсов выставок или любую другую информацию, разместив сразу в нескольких Ваших социальных сетях интернета, арт-маркетплейсах и сообществах партнёров сервиса одним нажатием!"> <meta property="og:description" content="Сервис создан для художников, скульпторов, искусствоведов, журналистов, галеристов, музеев и разных выставочных пространств с возможностью выложить пост картины, скульптуры, новостей, анонсов выставок или любую другую информацию, разместив сразу в нескольких Ваших социальных сетях интернета, арт-маркетплейсах и сообществах партнёров сервиса одним нажатием!">
<meta hid="og:image" property="og:image" content="https://avtopost.kultmost.ru/favicon.ico"> <meta property="og:image" content="https://avtopost.kultmost.ru/favicon.ico">
<meta name="facebook-domain-verification" content="uwjj4p3kr1xlxvcux4ujv7b6qujhl8"> <meta name="facebook-domain-verification" content="uwjj4p3kr1xlxvcux4ujv7b6qujhl8">
<meta hid="og:url" property="og:url" content="https://avtopost.kultmost.ru/"> <meta property="og:url" content="https://avtopost.kultmost.ru/">
<meta hid="description" name="description" content="Сервис создан для художников, скульпторов, искусствоведов, журналистов, галеристов, музеев и разных выставочных пространств с возможностью выложить пост картины, скульптуры, новостей, анонсов выставок или любую другую информацию, разместив сразу в нескольких Ваших социальных сетях интернета, арт-маркетплейсах и сообществах партнёров сервиса одним нажатием!"> <meta name="description" content="Сервис создан для художников, скульпторов, искусствоведов, журналистов, галеристов, музеев и разных выставочных пространств с возможностью выложить пост картины, скульптуры, новостей, анонсов выставок или любую другую информацию, разместив сразу в нескольких Ваших социальных сетях интернета, арт-маркетплейсах и сообществах партнёров сервиса одним нажатием!">
<meta hid="og:site_name" property="og:site_name" content="KULTMOST.АВТОПОСТ"> --> <meta property="og:site_name" content="KULTMOST.АВТОПОСТ"> -->
{{ header.style() }} {{ header.style() }}
<style type="text/css"> <style>
/* Base (common) styles */ /* Base (common) styles */
.b_0a7ub { .b_0a7ub {
-webkit-box-orient: vertical; -webkit-box-orient: vertical;

View File

@ -6,13 +6,13 @@
{% block title %}{{paint.title}}{% endblock %} {% block title %}{{paint.title}}{% endblock %}
{% block head %} {% block head %}
<meta hid="og:title" property="og:title" content="{{paint.title}}"> <meta property="og:title" content="{{paint.title}}">
<meta hid="og:description" property="og:description" content="{{description}}"> <meta property="og:description" content="{{description}}">
<meta hid="og:type" property="og:type" content="product"> <meta property="og:type" content="product">
<meta hid="og:image" property="og:image" content="https://avtopost.kultmost.ru/b/posts/{{paint.medias[0]}}"> <meta property="og:image" content="https://avtopost.kultmost.ru/b/posts/{{paint.medias[0]}}">
<meta hid="og:url" property="og:url" content="https://avtopost.kultmost.ru/paintings/{{paint._id}}"> <meta property="og:url" content="https://avtopost.kultmost.ru/paintings/{{paint._id}}">
<meta hid="description" name="description" content="{{description}}"> <meta name="description" content="{{description}}">
<meta hid="og:site_name" property="og:site_name" content="Картины | KULTMOST.АВТОПОСТ"> <meta property="og:site_name" content="Картины | KULTMOST.АВТОПОСТ">
{{ header.style() }} {{ header.style() }}
<style> <style>
@ -102,7 +102,7 @@
transform: scale(1.35); transform: scale(1.35);
} }
</style> </style>
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
.b_mmlhf { border-radius: 1em; .b_mmlhf { border-radius: 1em;
height: 70px; height: 70px;
min-width: 70px; min-width: 70px;

View File

@ -29,7 +29,7 @@
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
.c_o0nwu{ .c_o0nwu{
border-radius: 1em; border-radius: 1em;
height: 100px; height: 100px;

View File

@ -28,7 +28,7 @@
{%- endmacro %} {%- endmacro %}
{% macro style() -%} {% macro style() -%}
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
.b_f7c7g { align-self: stretch; background: #D9D9D9; object-fit: contain;} .b_f7c7g { align-self: stretch; background: #D9D9D9; object-fit: contain;}
.b_jh0gq { align-items: flex-start; display: flex; flex-direction: column; width: 335px; } .b_jh0gq { align-items: flex-start; display: flex; flex-direction: column; width: 335px; }
.c_2e7ix{ margin-bottom: 20px; text-transform: uppercase;} .c_2e7ix{ margin-bottom: 20px; text-transform: uppercase;}

View File

@ -387,7 +387,7 @@ display: flex;
color: white; color: white;
} }
</style> </style>
<style type="text/css"> <style>
.poster-group {position: relative;} .poster-group {position: relative;}
.file-name-display {width: 100%;padding: 16px 50px 16px 16px;border: 2px solid #ddd;border-radius: 8px;font-size: 16px;color: #555;background: #fff;min-height: 56px;display: flex;align-items: center;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;transition: all 0.3s ease;cursor: pointer;} .file-name-display {width: 100%;padding: 16px 50px 16px 16px;border: 2px solid #ddd;border-radius: 8px;font-size: 16px;color: #555;background: #fff;min-height: 56px;display: flex;align-items: center;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;transition: all 0.3s ease;cursor: pointer;}

View File

@ -20,7 +20,7 @@
<input readonly name="grade" id="grade" class="c_xhd5k" required data-toast="Поле Звание должно быть заполнено" data-select="1" data-radio="0" data-list="['Без звания', 'Заслуженный художник Российской федерации', 'Народный художник Российской федерации', 'Академик Российской академии художеств', 'Почетный член Российской академии художеств', 'Действительный член Российской Академии художеств', 'Член-корреспондент Российской академии художеств', 'Член Союза художников России', 'Член творческого союза художников России', 'Член Московского союза художников', 'Член Подмосковного союза художников', 'Член Союза русских художников', 'Творческий союз профессиональных художников', 'Евразийский союз художников', 'Международный художественный фонд', 'Творческое объединение женщин-художников']" data-label="Сохранить" data-custom_placeholder="Напишите свой вариант" data-custom="1" data-title="Выберите звание из списка"> <input readonly name="grade" id="grade" class="c_xhd5k" required data-toast="Поле Звание должно быть заполнено" data-select="1" data-radio="0" data-list="['Без звания', 'Заслуженный художник Российской федерации', 'Народный художник Российской федерации', 'Академик Российской академии художеств', 'Почетный член Российской академии художеств', 'Действительный член Российской Академии художеств', 'Член-корреспондент Российской академии художеств', 'Член Союза художников России', 'Член творческого союза художников России', 'Член Московского союза художников', 'Член Подмосковного союза художников', 'Член Союза русских художников', 'Творческий союз профессиональных художников', 'Евразийский союз художников', 'Международный художественный фонд', 'Творческое объединение женщин-художников']" data-label="Сохранить" data-custom_placeholder="Напишите свой вариант" data-custom="1" data-title="Выберите звание из списка">
<label for="grade" class="c_z3ybu floating-label">Звание</label> <label for="grade" class="c_z3ybu floating-label">Звание</label>
</div> </div>
<script async> <script>
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
showToast({message: 'Выберите из списка актуальное звание', status: 'info', duration:3000}); showToast({message: 'Выберите из списка актуальное звание', status: 'info', duration:3000});
}); });
@ -521,7 +521,7 @@
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
.b_xj1br { color: #FFF; font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; } .b_xj1br { color: #FFF; font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; }
.b_dc4rx { align-items: center; align-self: stretch; background: #5F1798; border-radius: 5px; display: flex; justify-content: center; padding: 12px 0; } .b_dc4rx { align-items: center; align-self: stretch; background: #5F1798; border-radius: 5px; display: flex; justify-content: center; padding: 12px 0; }
.b_5rck0 { align-items: center; align-self: stretch; background: #F39318; border-radius: 5px; display: flex; justify-content: center; padding: 12px 0; } .b_5rck0 { align-items: center; align-self: stretch; background: #F39318; border-radius: 5px; display: flex; justify-content: center; padding: 12px 0; }
@ -625,7 +625,7 @@ display: flex;
color: white; color: white;
} }
</style> </style>
<style type="text/css"> <style>
.poster-group {position: relative;} .poster-group {position: relative;}
.file-name-display {width: 100%;padding: 16px 50px 16px 16px;border: 2px solid #ddd;border-radius: 8px;font-size: 16px;color: #555;background: #fff;min-height: 56px;display: flex;align-items: center;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;transition: all 0.3s ease;cursor: pointer;} .file-name-display {width: 100%;padding: 16px 50px 16px 16px;border: 2px solid #ddd;border-radius: 8px;font-size: 16px;color: #555;background: #fff;min-height: 56px;display: flex;align-items: center;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;transition: all 0.3s ease;cursor: pointer;}

View File

@ -70,7 +70,7 @@
</script> </script>
{%- endmacro %} {%- endmacro %}
{% macro style() -%} {% macro style() -%}
<style type="text/css"> <style>
picture>img{ width: 100%; object-fit: scale-down; } picture>img{ width: 100%; object-fit: scale-down; }
.c_eiyz0{cursor: pointer;} .c_eiyz0{cursor: pointer;}
.b_ya0t5 { align-self: stretch; border-radius: 5px; height: 222px; } .b_ya0t5 { align-self: stretch; border-radius: 5px; height: 222px; }

View File

@ -162,7 +162,7 @@ document.querySelectorAll('.main-row').forEach(row => {
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
<style type="text/css"> <style>
/* Mobile */ /* Mobile */
@media (max-width: 699px) { @media (max-width: 699px) {

View File

@ -36,7 +36,7 @@
{{ header.style() }} {{ header.style() }}
{{ card.style() }} {{ card.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
<style type="text/css"> <style>
/* Mobile */ /* Mobile */
@media (max-width: 699px) { @media (max-width: 699px) {

View File

@ -413,7 +413,7 @@ document.addEventListener('DOMContentLoaded', () => {
{{ card.style() }} {{ card.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
{{ checkbox.style() }} {{ checkbox.style() }}
<style type="text/css"> <style>
.inptS{height: 48px; .inptS{height: 48px;
display: flex; width: 335px; display: flex; width: 335px;
align-items: center;gap: 10px; border-bottom: 1px solid #5F1798;} align-items: center;gap: 10px; border-bottom: 1px solid #5F1798;}

View File

@ -51,7 +51,7 @@
{%- endmacro %} {%- endmacro %}
{% macro style() -%} {% macro style() -%}
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
.saves{padding: 8px 10px; .saves{padding: 8px 10px;
width: 270px; width: 270px;
justify-content: center; justify-content: center;

View File

@ -328,7 +328,7 @@ async function btnSend(btn, itemId, sender, too){
{{ header.style() }} {{ header.style() }}
{{ competition.style() }} {{ competition.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
.c_aihm6{width: 100%;} .c_aihm6{width: 100%;}
.mw{margin: 0 20px; .mw{margin: 0 20px;
width: 100%;} width: 100%;}
@ -462,7 +462,7 @@ picture>img{ width: 100%; object-fit: scale-down; }
.b_ornr9{height: auto;} .b_ornr9{height: auto;}
} }
</style> </style>
<style type="text/css"> <style>
.menua{width: 100%; .menua{width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -507,7 +507,7 @@ picture>img{ width: 100%; object-fit: scale-down; }
.c_nnsh6>a>img{display: none;} .c_nnsh6>a>img{display: none;}
}</style> }</style>
<style type="text/css"> <style>
.b_thlt1 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; } .b_thlt1 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }
.b_5dr47 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; } .b_5dr47 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_thlt1_2 { align-items: center; background: rgba(95, 23, 152, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; } .b_thlt1_2 { align-items: center; background: rgba(95, 23, 152, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }

View File

@ -106,7 +106,7 @@
{{ header.style() }} {{ header.style() }}
{{ competition.style() }} {{ competition.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
.c_aihm6{width: 100%;} .c_aihm6{width: 100%;}
.mw{margin: 0 20px; .mw{margin: 0 20px;
width: 100%;} width: 100%;}
@ -240,7 +240,7 @@ picture>img{ width: 100%; object-fit: scale-down; }
.b_ornr9{height: auto;} .b_ornr9{height: auto;}
} }
</style> </style>
<style type="text/css"> <style>
.menua{width: 100%; .menua{width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -285,7 +285,7 @@ picture>img{ width: 100%; object-fit: scale-down; }
.c_nnsh6>a>img{display: none;} .c_nnsh6>a>img{display: none;}
}</style> }</style>
<style type="text/css"> <style>
.b_thlt1 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; } .b_thlt1 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }
.b_5dr47 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; } .b_5dr47 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_thlt1_2 { align-items: center; background: rgba(95, 23, 152, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; } .b_thlt1_2 { align-items: center; background: rgba(95, 23, 152, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }

View File

@ -66,7 +66,7 @@
{{ header.style() }} {{ header.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
{{ competition.style() }} {{ competition.style() }}
<style type="text/css"> <style>
/* Mobile */ /* Mobile */
@media (max-width: 699px) { @media (max-width: 699px) {

View File

@ -351,7 +351,7 @@ window.location.reload();
width: 25%; width: 25%;
text-align: center;} text-align: center;}
</style> </style>
<style type="text/css"> <style>
.c_aq9et { position: sticky; .c_aq9et { position: sticky;
bottom: 115px; bottom: 115px;
z-index: 5;display: flex; padding: 19px 33px; justify-content: center; align-items: center; align-self: stretch; border-radius: 5px; background: #5F1798; } z-index: 5;display: flex; padding: 19px 33px; justify-content: center; align-items: center; align-self: stretch; border-radius: 5px; background: #5F1798; }

View File

@ -23,7 +23,7 @@
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
<style type="text/css"> <style>
/* Mobile */ /* Mobile */
@media (max-width: 699px) { @media (max-width: 699px) {

View File

@ -315,7 +315,7 @@
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
.c_aq9et { .c_aq9et {
cursor: pointer; cursor: pointer;
transition: transform 0.1s linear, box‑shadow 0.1s linear; transition: transform 0.1s linear, box‑shadow 0.1s linear;
@ -356,7 +356,7 @@ picture>img{ width: 100%; object-fit: scale-down; }
.b_ornr9{height: auto;} .b_ornr9{height: auto;}
} }
</style> </style>
<style type="text/css"> <style>
.menua{width: 100%; .menua{width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -401,7 +401,7 @@ picture>img{ width: 100%; object-fit: scale-down; }
.c_nnsh6>a>img{display: none;} .c_nnsh6>a>img{display: none;}
}</style> }</style>
<style type="text/css"> <style>
.b_thlt1 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; } .b_thlt1 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }
.b_5dr47 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; } .b_5dr47 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_thlt1_2 { align-items: center; background: rgba(95, 23, 152, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; } .b_thlt1_2 { align-items: center; background: rgba(95, 23, 152, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }

View File

@ -150,7 +150,7 @@
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
.mw{margin: 0 20px; .mw{margin: 0 20px;
width: 100%;} width: 100%;}
.main__top{ display:flex; justify-content:space-between; gap:16px; align-items:flex-start; margin-bottom:16px;} .main__top{ display:flex; justify-content:space-between; gap:16px; align-items:flex-start; margin-bottom:16px;}
@ -283,7 +283,7 @@ picture>img{ width: 100%; object-fit: scale-down; }
.b_ornr9{height: auto;} .b_ornr9{height: auto;}
} }
</style> </style>
<style type="text/css"> <style>
.menua{width: 100%; .menua{width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -139,7 +139,7 @@ async function accept_inv(inv_id) {
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
.c_aq9et { .c_aq9et {
cursor: pointer; cursor: pointer;
transition: transform 0.1s linear, box‑shadow 0.1s linear; transition: transform 0.1s linear, box‑shadow 0.1s linear;
@ -180,7 +180,7 @@ picture>img{ width: 100%; object-fit: scale-down; }
.b_ornr9{height: auto;} .b_ornr9{height: auto;}
} }
</style> </style>
<style type="text/css"> <style>
.menua{width: 100%; .menua{width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -63,7 +63,7 @@
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
<style type="text/css"> <style>
picture>img{ width: 100%; object-fit: scale-down; } picture>img{ width: 100%; object-fit: scale-down; }
.b_ya0t5 { align-self: stretch; border-radius: 5px; height: 221px; } .b_ya0t5 { align-self: stretch; border-radius: 5px; height: 221px; }
@ -90,7 +90,7 @@ picture>img{ width: 100%; object-fit: scale-down; }
@media (min-width: 901px) { @media (min-width: 901px) {
.c_5uh64 { color: rgba(58, 58, 58, 0.75); font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; } .c_5uh64 { color: rgba(58, 58, 58, 0.75); font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; }
}</style> }</style>
<style type="text/css"> <style>
.b_rhmvx { border-radius: 1em; height: 140px; min-width: 140px; max-width: 100%; object-fit: cover; object-position: top;} .b_rhmvx { border-radius: 1em; height: 140px; min-width: 140px; max-width: 100%; object-fit: cover; object-position: top;}
.b_lgvu4 { align-items: center; background: #5F1798; border-radius: 25px; bottom: 0; display: flex; height: 50px; justify-content: center; position: absolute; right: 0; width: 50px; } .b_lgvu4 { align-items: center; background: #5F1798; border-radius: 25px; bottom: 0; display: flex; height: 50px; justify-content: center; position: absolute; right: 0; width: 50px; }

View File

@ -278,7 +278,7 @@ document.querySelectorAll('.b_k2cle button').forEach(button => {
{{ header.style() }} {{ header.style() }}
{{card.style()}} {{card.style()}}
{{ profile_nav.style() }} {{ profile_nav.style() }}
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
/* Активное состояние */ /* Активное состояние */
.c_fx6lz.active, .c_fx6lz.active,
.c_deket.active { .c_deket.active {
@ -375,7 +375,7 @@ document.querySelectorAll('.b_k2cle button').forEach(button => {
.c_b2jz8 { -webkit-box-orient: vertical; -webkit-line-clamp: 1; color: #000; display: -webkit-box; font-family: Inter; font-size: 24px; font-style: normal; font-weight: 700; line-height: normal; overflow: hidden; text-overflow: ellipsis; } .c_b2jz8 { -webkit-box-orient: vertical; -webkit-line-clamp: 1; color: #000; display: -webkit-box; font-family: Inter; font-size: 24px; font-style: normal; font-weight: 700; line-height: normal; overflow: hidden; text-overflow: ellipsis; }
.c_jcujv { align-items: flex-start; background: #FFF; display: flex; flex-direction: column; gap: 30px; padding-left: 100px; width: 1600px; } .c_jcujv { align-items: flex-start; background: #FFF; display: flex; flex-direction: column; gap: 30px; padding-left: 100px; width: 1600px; }
}</style> }</style>
<style type="text/css"> <style>
/* Mobile */ /* Mobile */
@media (max-width: 699px) { @media (max-width: 699px) {

View File

@ -279,7 +279,7 @@ document.querySelectorAll('.b_k2cle button').forEach(button => {
{{ header.style() }} {{ header.style() }}
{{card.style()}} {{card.style()}}
{{ profile_nav.style() }} {{ profile_nav.style() }}
<style type="text/css"> <style>
.b_thlt1 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; } .b_thlt1 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }
.b_5dr47 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; } .b_5dr47 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_thlt1_2 { align-items: center; background: rgba(95, 23, 152, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; } .b_thlt1_2 { align-items: center; background: rgba(95, 23, 152, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }
@ -404,7 +404,7 @@ document.querySelectorAll('.b_k2cle button').forEach(button => {
.c_b2jz8 { -webkit-box-orient: vertical; -webkit-line-clamp: 1; color: #000; display: -webkit-box; font-family: Inter; font-size: 24px; font-style: normal; font-weight: 700; line-height: normal; overflow: hidden; text-overflow: ellipsis; } .c_b2jz8 { -webkit-box-orient: vertical; -webkit-line-clamp: 1; color: #000; display: -webkit-box; font-family: Inter; font-size: 24px; font-style: normal; font-weight: 700; line-height: normal; overflow: hidden; text-overflow: ellipsis; }
.c_jcujv { align-items: flex-start; background: #FFF; display: flex; flex-direction: column; gap: 30px; padding-left: 100px; width: 1600px; } .c_jcujv { align-items: flex-start; background: #FFF; display: flex; flex-direction: column; gap: 30px; padding-left: 100px; width: 1600px; }
}</style> }</style>
<style type="text/css"> <style>
/* Mobile */ /* Mobile */
@media (max-width: 699px) { @media (max-width: 699px) {

View File

@ -97,7 +97,7 @@ document.querySelectorAll('#rePublishCompetition').forEach(btn => {
{{ header.style() }} {{ header.style() }}
{{card.style()}} {{card.style()}}
{{ profile_nav.style() }} {{ profile_nav.style() }}
<style type="text/css"> <style>
/* Mobile */ /* Mobile */
@media (max-width: 699px) { @media (max-width: 699px) {

View File

@ -500,7 +500,7 @@ genre = genres.map(g => {
{{ header.style() }} {{ header.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
{{ competition.style() }} {{ competition.style() }}
<style type="text/css"> <style>
{{label.green()}} {{label.green()}}
{{label.purple()}} {{label.purple()}}
@media (min-width: 901px) { @media (min-width: 901px) {

View File

@ -149,7 +149,7 @@
{{ header.style() }} {{ header.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
{{ competition.style() }} {{ competition.style() }}
<style type="text/css"> <style>
{{label.green()}} {{label.green()}}
{{label.purple()}} {{label.purple()}}
#preview_poster{min-height: 456px;} #preview_poster{min-height: 456px;}

View File

@ -83,7 +83,7 @@ async function deleteExhibition(btn, _id) {
{{ header.style() }} {{ header.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
{{ competition.style() }} {{ competition.style() }}
<style type="text/css"> <style>
body{background: #F6EBFF;} body{background: #F6EBFF;}
#preview_poster{min-height: 456px;} #preview_poster{min-height: 456px;}
/* Mobile */ /* Mobile */

View File

@ -28,7 +28,7 @@
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
<style type="text/css"> <style>
/* Mobile */ /* Mobile */
@media (max-width: 699px) { @media (max-width: 699px) {

View File

@ -34,7 +34,7 @@
{{ card.style() }} {{ card.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
<style type="text/css"> <style>
.b_rhmvx { border-radius: 1em; height: 140px; min-width: 140px; max-width: 100%; object-fit: cover; object-position: top;} .b_rhmvx { border-radius: 1em; height: 140px; min-width: 140px; max-width: 100%; object-fit: cover; object-position: top;}

View File

@ -163,7 +163,7 @@ document.addEventListener('change', function (e) {
{{ header.style() }} {{ header.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
{{ checkbox.style() }} {{ checkbox.style() }}
<style type="text/css"> <style>
hr{width: 100%; hr{width: 100%;
color: #5e179888;} color: #5e179888;}
.b_v6dzl { height: 12px; stroke-width: 3px; stroke: #0ED646; width: 16px; } .b_v6dzl { height: 12px; stroke-width: 3px; stroke: #0ED646; width: 16px; }
@ -198,7 +198,7 @@ document.addEventListener('change', function (e) {
.c_jkbyo { align-items: flex-start; align-self: stretch; display: flex; flex-direction: column; gap: 5px; } .c_jkbyo { align-items: flex-start; align-self: stretch; display: flex; flex-direction: column; gap: 5px; }
.c_fswiv { align-items: flex-start; background: #FFF; display: flex; flex-direction: column; width: 100%; } .c_fswiv { align-items: flex-start; background: #FFF; display: flex; flex-direction: column; width: 100%; }
}</style> }</style>
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
.b_qgav8 { height: 12px; stroke-width: 3px; stroke: #0ED646; width: 16px; } .b_qgav8 { height: 12px; stroke-width: 3px; stroke: #0ED646; width: 16px; }
.b_h1rw4 { height: 16px; width: 20px; } .b_h1rw4 { height: 16px; width: 20px; }
.b_bpfdu { flex-shrink: 0; height: 30px; width: 30px; } .b_bpfdu { flex-shrink: 0; height: 30px; width: 30px; }
@ -209,7 +209,7 @@ document.addEventListener('change', function (e) {
.b_jphak { align-items: center; background: #5F1798; border-radius: 5px; display: flex; justify-content: center; padding: 12px 30px; } .b_jphak { align-items: center; background: #5F1798; border-radius: 5px; display: flex; justify-content: center; padding: 12px 30px; }
.b_2tr5g { align-items: center; display: flex; flex-direction: column; gap: 9px; } .b_2tr5g { align-items: center; display: flex; flex-direction: column; gap: 9px; }
</style> </style>
<style type="text/css"> <style>
/* Mobile */ /* Mobile */
@media (max-width: 699px) { @media (max-width: 699px) {
.c_mrl59 { .c_mrl59 {

File diff suppressed because one or more lines are too long

View File

@ -158,7 +158,7 @@ fetch('/api/me')
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
<style type="text/css"> <style>
.poster-group {position: relative;} .poster-group {position: relative;}
.password-group { position: relative; } .password-group { position: relative; }
a{color: #5F1798;} a{color: #5F1798;}

View File

@ -116,7 +116,7 @@
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
<style type="text/css"> <style>
.poster-group {position: relative;} .poster-group {position: relative;}
.password-group { position: relative; } .password-group { position: relative; }
a{color: #5F1798;} a{color: #5F1798;}

View File

@ -98,7 +98,7 @@
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
{{ footer.style() }} {{ footer.style() }}
<style type="text/css">/* Base (common) styles */ <style>/* Base (common) styles */
.b_25g00 { align-items: flex-start; display: flex; flex-direction: column; gap: 20px; width: 335px; } .b_25g00 { align-items: flex-start; display: flex; flex-direction: column; gap: 20px; width: 335px; }
.b_7wywm { fill: #1F1F1F; height: 16px; width: 16.041px; } .b_7wywm { fill: #1F1F1F; height: 16px; width: 16.041px; }

View File

@ -97,7 +97,7 @@ document.addEventListener('DOMContentLoaded', () => {
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
{{ card.style() }} {{ card.style() }}
<style type="text/css"> <style>
.o{font-style: italic;color: #777;} .o{font-style: italic;color: #777;}
.it{padding: 0px 15px;} .it{padding: 0px 15px;}
.b_xsvl1 { color: #5F1798; font-family: Inter; font-size: 14px; font-style: normal; font-weight: 700; line-height: normal; text-align: center; padding: 0 10px;} .b_xsvl1 { color: #5F1798; font-family: Inter; font-size: 14px; font-style: normal; font-weight: 700; line-height: normal; text-align: center; padding: 0 10px;}

View File

@ -8,7 +8,7 @@ import httpx
import logging import logging
from bson import ObjectId from bson import ObjectId
from config import * from config import *
from db import mongo_client, redis_client, gridfs_map from db import mongo_client, redis_client, gridfs_map, social_collection
import asyncio import asyncio
import secrets import secrets
from utils import fetch_bytes, get_password_hash from utils import fetch_bytes, get_password_hash
@ -194,6 +194,9 @@ class TokenManager:
vk_id = vk_profile.get("id") vk_id = vk_profile.get("id")
# 1) Уже привязанный VK-аккаунт # 1) Уже привязанный VK-аккаунт
existing = await social_collection.find_one({'id':vk_id},{'user_id':1})
if existing:
return existing["user_id"], False
existing = await users.find_one({"vk_id": vk_id}, {"_id": 1}) existing = await users.find_one({"vk_id": vk_id}, {"_id": 1})
if existing: if existing:
return str(existing["_id"]), False return str(existing["_id"]), False

View File

@ -18,6 +18,7 @@ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os import os
import base64 import base64
import json import json
import re
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto") pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
@ -261,6 +262,11 @@ def display_user(user):
} }
def is_valid_email(email: str) -> bool:
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email))
def normalize_phone(phone: Optional[str]) -> Optional[str]: def normalize_phone(phone: Optional[str]) -> Optional[str]:
if not phone or len(phone) < 6: if not phone or len(phone) < 6:
return None return None

12
vk.py
View File

@ -89,12 +89,12 @@ async def oauth_callback(request: Request, response: Response, code: str, state:
return RedirectResponse(url='/login', status_code=status.HTTP_303_SEE_OTHER) return RedirectResponse(url='/login', status_code=status.HTTP_303_SEE_OTHER)
# Регистрация/вход через VK — выдаём нашу сессию и редиректим на next # Регистрация/вход через VK — выдаём нашу сессию и редиректим на next
# if result.get('purpose') == 'register': if result.get('purpose') == 'register':
# await issue_session(result['user_id'], request, response, client_id='vk') await issue_session(result['user_id'], request, response, client_id='vk')
# next_url = result.get('next') or '/profile' next_url = result.get('next') or '/profile'
# if not is_safe_redirect(next_url): if not is_safe_redirect(next_url):
# next_url = '/profile' next_url = '/profile'
# return RedirectResponse(url=next_url, status_code=status.HTTP_303_SEE_OTHER, headers=response.headers) return RedirectResponse(url=next_url, status_code=status.HTTP_303_SEE_OTHER, headers=response.headers)
# Привязка соцсети к уже залогиненному пользователю # Привязка соцсети к уже залогиненному пользователю
return RedirectResponse(url='/profile/social', status_code=status.HTTP_303_SEE_OTHER) return RedirectResponse(url='/profile/social', status_code=status.HTTP_303_SEE_OTHER)