This commit is contained in:
vladislav 2026-08-17 09:38:26 -04:00
parent d2ce3e60da
commit 0bd9bceaa1
Signed by: Vladislav
GPG Key ID: D97C9E5E4860EE45

View File

@ -7,546 +7,92 @@
{% block title %}Приглашения{% endblock %}
{% macro render_doc(doc) %}
<figure class="c_4b1hy b_uu2ub" data-exhibition_id="{{doc.get('exhibition_id')}}" data-delivery-id="{{doc._id}}" data-sender="{{doc.sender._id}}" data-receiver="{{doc.receiver._id}}">
<img class="b_1fvkt" src="/b/posts/{{doc.img}}">
<figcaption class="c_3g8gh b_pvw5a" data-id="{{doc._id}}">
{% if doc.status == 'Ожидает отправки' %}
<div class="b_thlt1">
<span class="b_5dr47">{{doc.status}}</span>
</div>
{% elif doc.status == 'Получено' %}
<div class="b_thlt1">
<span class="b_5dr47">{{doc.status}}</span>
</div>
{% elif doc.status == 'Ожидает получения' %}
<div class="b_thlt1_2">
<span class="b_5dr47_2">{{doc.status}}</span>
</div>
{% else %}
<div class="b_thlt1_2">
<span class="b_5dr47_2">{{doc.status}}</span>
</div>
{% endif %}
<a class="b_6spaf" href="/profile/delivery/{{doc.post_id}}#main">Посмотреть историю</a>
<h2 class="c_9hvtd b_f5fb2">{{doc.title}}</h2>
{% if doc.type == 'exhibition' and doc.get('exhibition') %}
<p class="b_6spaf">{{doc.get('exhibition').title}}</p>
<div class="b_xz6em">
<img src="/a/{{doc.receiver._id}}" class="b_ob59y">
<div class="c_9hjdk b_ffruz">
<span class="b_qla2q">Организатор</span>
<span class="c_zm0m4 b_09est">{{doc.receiver.username}}</span>
</div>
</div>
{% else %}
{% if doc.address %}
<p class="b_6spaf">Адрес: {{doc.address}}</p>
{% endif %}
{% if doc.back_address %}
<p class="b_6spaf">Адрес для возврата: {{doc.back_address}}</p>
{% endif %}
{% if doc.delivery_type %}
<p class="b_6spaf">Способ доставки: {{doc.delivery_type}}</p>
{% endif %}
{% if doc.track %}
<p class="b_6spaf">Трэк-номер: {{doc.track}}</p>
{% endif %}
{% if doc.sender %}
<div class="b_xz6em">
<img src="/a/{{doc.sender._id}}" class="b_ob59y">
<div class="c_9hjdk b_ffruz">
<span class="b_qla2q">Отправитель</span>
<span class="c_zm0m4 b_09est">{{doc.sender.username}}</span>
</div>
</div>
{% endif %}
{% if doc.receiver %}
<div class="b_xz6em">
<img src="/a/{{doc.receiver._id}}" class="b_ob59y">
<div class="c_9hjdk b_ffruz">
<span class="b_qla2q">Получатель</span>
<span class="c_zm0m4 b_09est">{{doc.receiver.username}}</span>
<span class="b_qla2q">+7 *** *** {{ doc.receiver.phone[-4:] }}</span>
</div>
</div>
{% endif %}
{% if doc.status in ['Ожидает отправки', 'Возврат художнику'] and user._id == doc.sender._id%}
<button class="b_wy3rt" onclick="btnSend(this, '{{doc._id}}', '{{doc.sender._id}}', '{{doc.receiver._id}}')">
<span class="b_bvl0d">Отправить</span>
</button>
{% endif %}
{% if doc.status == 'Ожидает получения' and user._id == doc.receiver._id%}
<button class="b_wy3rt" onclick="btnAccept(this, '{{doc._id}}')">
<span class="b_bvl0d">Получить</span>
</button>
{% endif %}
{% endif %}
<p class="b_6spaf">
{{ (doc._id.generation_time.strftime('%d.%m.%Y')) if doc._id else ''}}
<b>{{ '%02d' % ((doc._id.generation_time.hour + 3) % 24) if doc._id else '' }}:{{ '%02d' % doc._id.generation_time.minute if doc._id else '' }}</b>
</p>
</figcaption>
</figure>
{% endmacro %}
{% macro render_post(doc) %}
<figure class="c_4b1hy b_uu2ub">
<img class="b_1fvkt" src="/b/posts/{{doc.medias[0]}}">
<figcaption class="c_3g8gh b_pvw5a">
<div class="b_thlt1_2">
<span class="b_5dr47_2">Картина не выбрана</span>
</div>
<div class="b_thlt1" style="display:none;">
<span class="b_5dr47">Картина выбрана</span>
</div>
<h2 class="c_9hvtd b_f5fb2">{{doc.title}}</h2>
<label class="offer-label">
<input
type="checkbox"
class="offer-checkbox yes"
data-post-id="{{ doc._id }}"
onchange="selectPost(this)"
>
<span class="offer-text">Выбрать</span>
</label>
<label class="offer-label">
<input
type="checkbox"
class="offer-checkbox"
data-post-id="{{ doc._id }}"
onchange="uSelectPost(this)"
>
<span class="offer-text">Отклонить</span>
</label>
<p class="b_6spaf">...</p>
</figcaption>
</figure>
{% endmacro %}
{% block content %}
{{ header.section(user) }}
<section class="c_dg3k4">
<div class="c_gtoe9">
{{ profile_card.profile_card(user) }}
<div class="c_ru432">
{{ profile_nav.profile_nav(user, 'invitation') }}
<div class="c_tihtk" id="main">
{% if doc.get('exhibition')%}
<article class="c_9fgdh b_ax8vz" id="l{{doc._id}}">
<div class="c_z3dpx b_73p10">
<div class="b_vabj9_2">
<span class="b_0jst1_2">{{doc.type}}</span>
</div>
<header class="c_m876y b_9xtz9">
<h3 class="c_hysjg b_8f3uh">{{ doc.title }}</h3>
</header>
<p class="c_2fr39 b_tfnz2">{{doc.get('exhibition').title }}</p>
<p>Место проведения: {{ doc.get('exhibition').get('place') }}</p>
<div class="b_otttb">
<span class="c_2fr39 b_tfnz2">С {{ ( doc.get('exhibition').datetime_start.strftime('%d.%m.%Y')) if
doc.get('exhibition').datetime_start
else ''}}</span>
<span class="c_2fr39 b_tfnz2">По {{ ( doc.get('exhibition').date_finish.strftime('%d.%m.%Y')) if
doc.get('exhibition').date_finish else
''}}</span>
</div>
{{render_links(doc)}}
<p>Картин выбрано: <span id="lenSelected">0</span></p>
<button onclick="accept_inv('{{doc._id}}')" class="btn" style="width: 100%; box-sizing: border-box;">
Принять условия
</button>
{% set user_contact = doc.get('user') %}
{{ render_user_contacts(user_contact) }}
<p class="c_drpqj b_mp1wb">{{ (doc._id.generation_time.strftime('%d.%m.%Y')) if doc._id else ''}}</p>
</div>
</article>
{% for post in doc.get('posts')%}
{{render_post(post)}}
{% endfor %}
{%endif%}
{% if doc.get('type_')=='paint' %}
{% set statuses = ['Ожидает отправки', 'Возврат художнику'] %}
<article class="c_9fgdh b_ax8vz">
<div class="c_z3dpx b_73p10">
{% if data | selectattr('status', 'in', statuses) | list | length > 0 %}
<header class="c_m876y b_9xtz9">
<h3 class="b_8f3uh">Настройки отправки</h3>
</header>
<p class="b_tfnz2">Можно менять перед каждой отправкой картины</p>
<div class="b_73p10">
<div class="c_aihm6 floating-group">
<select class="c_xhd5k" type="text" id="delivery_type" name="delivery_type" required>
<option>СДЭК</option>
<option>Самостоятельно</option>
</select>
<label for="delivery_type" class="c_z3ybu floating-label">Способ доставки</label>
</div>
<div class="c_aihm6 floating-group">
<input placeholder="..." type="text" id="track" name="track" class="c_xhd5k" required
data-toast="Поле Трэк-номер должно быть заполнено">
<label for="track" class="c_z3ybu floating-label">Трэк-номер</label>
<div class="c_gtoe9">
{{ profile_card.profile_card(user) }}
<div class="c_ru432">
{{ profile_nav.profile_nav(user, 'invitation') }}
<div class="c_tihtk" id="main">
{% for doc in data %}
<article class="c_9fgdh b_ax8vz" id="l{{doc._id}}">
<div class="c_z3dpx b_73p10">
<div class="b_vabj9_2">
<span class="b_0jst1_2">{{doc.type}}</span>
</div>
<header class="c_m876y b_9xtz9">
<h3 class="c_hysjg b_8f3uh">{{ doc.title }}</h3>
</header>
{% if doc.get('type_')=='paint' %}
<a class="btn" href="/profile/invitation/{{doc._id}}#main" style="width: 100%;">Открыть</a>
{% endif %}
{% if doc.get('type') == 'Выставка' %}
<p class="c_2fr39 b_tfnz2">{{doc.get('exhibition').title }}</p>
<p>Место проведения: {{ doc.get('exhibition').get('place') }}</p>
<div class="b_otttb">
<span class="c_2fr39 b_tfnz2">С {{ ( doc.get('exhibition').datetime_start.strftime('%d.%m.%Y')) if
doc.get('exhibition').datetime_start
else ''}}</span>
<span class="c_2fr39 b_tfnz2">По {{ ( doc.get('exhibition').date_finish.strftime('%d.%m.%Y')) if
doc.get('exhibition').date_finish else
''}}</span>
</div>
{% if doc.get('exhibition').get('tizer_file_id') or doc.get('exhibition').get('tizer_link') %}
<a class="btn"
href="{% if doc.get('exhibition') and doc.get('exhibition').get('tizer_link') %}{{doc.get('exhibition').tizer_link}}{% else %}/b/exhibition/{{ doc.get('exhibition').tizer_file_id }}{% endif %}"
style="width: 100%;">Релиз выставки</a>
{% endif %}
<p class="c_2fr39 b_tfnz2">Принять условия нужно до {{doc.get('exhibition').datetime_start.strftime('%d.%m.%Y')}}</p>
{% if doc.get('accept') == False and now < doc.get('exhibition').datetime_start %}
<a class="btn" href="/profile/invitation/{{doc._id}}#main" style="width: 100%;">Открыть</a>
<!-- <button onclick="accept_inv('{{doc._id}}')" class="btn" href="/profile/delivery#main" style="width: 100%; box-sizing: border-box;">Принять условия</button> -->
{% else %}
<a class="btn"
href="{% if doc.get('exhibition') and doc.get('exhibition').get('rules_link') %}{{doc.get('exhibition').rules_link}}{% else %}/b/exhibition/{{ doc.get('exhibition').rules_file_id }}{% endif %}"
style="width: 100%;">Условия выставки</a>
{% endif %}
{% else %}
<p class="c_2fr39 b_tfnz2">"{{ doc.get('item') }}" просьба связаться с организатором для обсуждения
дальнейших действий</p>
{% endif %}
<!-- <p class="c_sihck b_s6ool">От</p> -->
{% set user_contact = doc.get('user') %}
{{render_user_contacts(user_contact)}}
<p class="c_drpqj b_mp1wb">{{ (doc._id.generation_time.strftime('%d.%m.%Y')) if doc._id else ''}}</p>
</div>
</article>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</article>
{% for doc in data if doc.status in statuses %}
{{ render_doc(doc) }}
{% endfor %}
{% for doc in data if doc.status not in statuses %}
{{ render_doc(doc) }}
{% endfor %}
<script defer>
const query = window.location.href.split('?')[1] || '';
let exhibition_id = null;
if (query) {
const parts = query.split('=');
if (parts[0] === 'exhibition_id' && parts[1] !== 'None') {
exhibition_id = parts[1];
}
}
function filterFigure(exhibition_id) {
document.getElementById('main').querySelectorAll('figure').forEach(figure => {
if (!exhibition_id || figure.dataset.exhibition_id === exhibition_id) {
figure.style.display = 'flex';
console.log(figure.dataset.exhibition_id, exhibition_id, 'flex')
} else {
console.log(figure.dataset.exhibition_id, exhibition_id)
figure.style.display = 'none';
}
});
}
if (exhibition_id) {
document.getElementById('exhibition_id').value = exhibition_id;
}
filterFigure(exhibition_id);
</script>
<script>
const deliveryType = document.getElementById('delivery_type');
const track = document.getElementById('track');
function toggleTrack() {
const isSelf = deliveryType.value === 'Самостоятельно';
if (isSelf) {
track.required = false;
track.value = '';
} else {
track.required = true;
}
}
deliveryType.addEventListener('change', toggleTrack);
toggleTrack();
</script>
<script>
async function btnAccept(btn, itemId){
const figcaption = btn.parentElement;
const statusDiv = figcaption.querySelectorAll('div')[0];
const statusSpan = statusDiv.querySelector('span')
try {
const res = await fetch('/api/delivery/accept', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
item: itemId,
})
});
if (!res.ok) {
let errorMessage = 'Ошибка сохранения';
try {
const errorData = await res.json();
errorMessage = errorData.detail || errorMessage;
} catch (e) {
try {
const errorText = await res.text();
errorMessage = errorText || errorMessage;
} catch (e) {
}
}
showToast({ message: errorMessage, duration: 2500 });
return;
}
statusDiv.className = 'b_thlt1';
statusSpan.className = 'b_5dr47';
statusSpan.textContent = 'Получено';
btn.style.display = 'none'
} catch (error) {
console.error('Network error:', error);
showToast({ message: 'Не удалось подключиться к серверу', duration: 2500 });
}
};
async function btnSend(btn, itemId, sender, too){
const figcaption = btn.parentElement;
const statusDiv = figcaption.querySelectorAll('div')[0];
const statusSpan = statusDiv.querySelector('span')
const delivery_type = document.getElementById('delivery_type').value;
const trackValue = document.getElementById('track');
let track = ''
if (delivery_type === 'СДЭК' && trackValue) {
track = trackValue.value
}
const requiredInputs = document.querySelectorAll('input[required]');
const requiredTextareas = document.querySelectorAll('textarea[required]');
const requiredFields = [...requiredInputs, ...requiredTextareas];
let firstErrorField = null;
let hasError = false;
requiredFields.forEach(field => {
const group = field.closest('.floating-group');
if (!group) return; // skip if no group wrapper
let isEmpty = false;
if (field.type === 'file') {
isEmpty = !field.files || field.files.length === 0;
} else {
isEmpty = !field.value || !field.value.trim();
}
if (isEmpty) {
group.classList.add('error');
hasError = true;
showToast({message: field.dataset.toast, duration:2500});
if (!firstErrorField) firstErrorField = field;
} else {
group.classList.remove('error');
}
});
if (hasError && firstErrorField) {
firstErrorField.scrollIntoView({ behavior: 'smooth', block: 'center' });
setTimeout(() => firstErrorField.focus(), 600);
return;
}
try {
const res = await fetch('/api/delivery/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sender,
too,
item: itemId,
delivery_type,
track: track
})
});
if (!res.ok) {
let errorMessage = 'Ошибка сохранения';
try {
const errorData = await res.json();
errorMessage = errorData.detail || errorMessage;
} catch (e) {
// If response isn't JSON, try to get text
try {
const errorText = await res.text();
errorMessage = errorText || errorMessage;
} catch (e) {
// Fallback to generic error
}
}
showToast({ message: errorMessage, duration: 2500 });
return;
}
statusDiv.className = 'b_thlt1_2';
statusSpan.className = 'b_5dr47_2';
statusSpan.textContent = 'Ожидает получения';
btn.style.display = 'none'
} catch (error) {
console.error('Network error:', error);
showToast({ message: 'Не удалось подключиться к серверу', duration: 2500 });
}
};
</script>
{% endif %}
</div>
</div>
</div>
</section>
<script>
const LS_KEY = 'selected_posts_ids_{{doc._id}}';
function safeParseArray(raw) {
try {
const v = JSON.parse(raw);
if (!Array.isArray(v)) return [];
return v.map(String);
} catch {
return [];
}
}
function loadSelectedIds() {
const raw = localStorage.getItem(LS_KEY);
if (!raw) return [];
return safeParseArray(raw);
}
function saveSelectedIds(ids) {
localStorage.setItem(LS_KEY, JSON.stringify(ids.map(String)));
}
function updateSelectedCount() {
const el = document.getElementById('lenSelected');
if (!el) return;
el.textContent = String(loadSelectedIds().length);
}
function setStateUI(checkbox) {
const figure = checkbox && checkbox.closest ? checkbox.closest('figure') : null;
if (!figure) return;
const chooseBlock = figure.querySelector('.b_thlt1_2');
const selectedBlock = figure.querySelector('.b_thlt1');
const checked = !!checkbox.checked;
if (chooseBlock) chooseBlock.style.display = checked ? 'none' : '';
if (selectedBlock) selectedBlock.style.display = checked ? '' : 'none';
}
function setStateUINo(checkbox) {
const figure = checkbox && checkbox.closest ? checkbox.closest('figure') : null;
if (!figure) return;
const chooseBlock = figure.querySelector('.b_thlt1_2');
const selectedBlock = figure.querySelector('.b_thlt1');
const checked = !!checkbox.checked;
if (selectedBlock) selectedBlock.style.display = checked ? 'none' : '';
if (chooseBlock) chooseBlock.style.display = checked ? '' : 'none';
}
function setAllCheckboxesFromStorage() {
const selectedSet = new Set(loadSelectedIds());
const boxes = document.querySelectorAll('.yes');
boxes.forEach(cb => {
const postId = String(cb.dataset.postId || '');
const isSelected = postId && selectedSet.has(postId);
cb.checked = isSelected;
setStateUI(cb);
async function accept_inv(inv_id) {
try {
const res = await fetch(`/api/invitation/accept/${inv_id}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
updateSelectedCount();
}
function selectPost(checkbox) {
if (!checkbox || !checkbox.dataset) return;
const postId = String(checkbox.dataset.postId || '');
if (!postId) return;
const ids = loadSelectedIds();
const set = new Set(ids);
if (checkbox.checked) set.add(postId);
else set.delete(postId);
const next = Array.from(set);
saveSelectedIds(next);
const figure = checkbox.closest('figure');
if (figure) {
const acceptBoxes = figure.querySelectorAll('.offer-checkbox');
acceptBoxes.forEach(cb => {
if (cb !== checkbox) {
cb.checked = false;
setStateUI(cb);
}
});
}
setStateUI(checkbox);
updateSelectedCount();
}
function uSelectPost(checkbox) {
if (!checkbox || !checkbox.dataset) return;
const postId = String(checkbox.dataset.postId || '');
if (!postId) return;
// Reject: remove from selected and uncheck any accept checkbox for same post
const ids = loadSelectedIds();
const set = new Set(ids);
set.delete(postId);
saveSelectedIds(Array.from(set));
// Uncheck the "Принять" checkbox for this post
const figure = checkbox.closest('figure');
if (figure) {
const acceptBoxes = figure.querySelectorAll('.offer-checkbox');
acceptBoxes.forEach(cb => {
if (cb !== checkbox) {
cb.checked = false;
setStateUI(cb);
}
});
}
setStateUINo(checkbox);
updateSelectedCount();
}
async function accept_inv(inv_id) {
const ids = loadSelectedIds();
if (!Array.isArray(ids) || ids.length === 0) {
showToast({ message: 'Выберите хотя бы одну картину', status: 'error' });
return;
}
try {
const res = await fetch(`/api/invitation/accept/${inv_id}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ posts: ids })
});
if (!res.ok) {
showToast({ message: 'Не удалось принять приглашение', status: 'error' });
return;
}
if (res.ok) {
showToast({ message: 'Приглашение принято. Перенаправляем на страницу доставки' });
setTimeout(() => {
localStorage.removeItem(LS_KEY);
window.location.href = '/profile/delivery#main?exhibition_id={{doc.get("exhibition_id")}}';
}, 5000);
} catch (e) {
showToast({ message: 'Ошибка сети/сервера', status: 'error' });
window.location.href = '/profile/delivery#main';
}, 800);
} else {
showToast({ message: 'Не удалось принять приглашение', status: 'error' });
}
} catch (e) {
showToast({ message: 'Ошибка сети/сервера', status: 'error' });
}
document.addEventListener('DOMContentLoaded', () => {
setAllCheckboxesFromStorage();
});
}
</script>
{{ footer.section(user) }}
{% endblock %}
{% macro render_user_contacts(user) %}
<div class="c_dbbe4 b_otttb">
{% if user %}
@ -594,264 +140,10 @@ async function btnSend(btn, itemId, sender, too){
</div>
</div>
{% endmacro %}
{% macro render_links(doc) %}
{% if doc.get('exhibition')%}
{% if doc.get('exhibition').get('tizer_file_id') or doc.get('exhibition').get('tizer_link') %}
<a class="btn" href="{% if doc.get('exhibition') and doc.get('exhibition').get('tizer_link') %}{{doc.get('exhibition').tizer_link}}{% else %}/b/exhibition/{{ doc.get('exhibition').tizer_file_id }}{% endif %}" style="width: 100%;">Релиз выставки</a>
{%endif%}
<a class="btn" href="{% if doc.get('exhibition') and doc.get('exhibition').get('rules_link') %}{{doc.get('exhibition').rules_link}}{% else %}/b/exhibition/{{ doc.get('exhibition').rules_file_id }}{% endif %}" style="width: 100%;">Условия выставки</a>
{%endif%}
{% endmacro %}
{% block head %}
{{ header.style() }}
{{ profile_nav.style() }}
<style>/* Base (common) styles */
.c_aihm6{width: 100%;}
.mw{margin: 0 20px;
width: 100%;}
.main__top{ display:flex; justify-content:space-between; gap:16px; align-items:flex-start; margin-bottom:16px;}
.main__title{ flex: 1; min-width: 260px;}
.main__title > p{ margin: 0 0 10px 0; font-size: 20px; font-weight: 900; color: #0f172a;}
.main__address-label{ font-size: 13px; color: #64748b; margin-bottom: 6px;}
.main__address-value{ font-size: 14px; font-weight: 800; color: #0f172a;}
.b_ya0t5 {
align-self: stretch;
border-radius: 5px;
height: 222px;
}
picture > img {
width: 100%;
object-fit: scale-down;
}
.b_kbsvf {
padding: 10px 20px;
align-items: flex-start;
align-self: stretch;
display: flex;
flex-direction: column;
gap: 10px;
justify-content: center;
}
.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_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_5dr47_2 { color: #5F1798; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_h57rq {
align-items: flex-start;
background: #FDFAFF;
border-radius: 5px;
border: 1px solid #5F1798;
display: flex;
flex-direction: column;
width: 335px;
}
.b_wy3rt { align-items: center; background: #5F1798; border-radius: 5px; display: flex; justify-content: center; padding: 15px 0; width: 100%; }
.b_bvl0d { color: #FFF; font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; }
/* remove default button outline but keep focus visible */
.password-toggle:focus { outline: 2px solid rgba(95,23,152,0.25); outline-offset: 2px; }
/* ensure svg size fits */
.password-toggle .eye { width: 24px; height: 24px; display: block; }
.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;}
/* Hover эффект */
.c_aihm6:hover select, .c_nb6gy:hover textarea, .poster-group:hover .file-name-display {border-color: #5F1798;}
/* Когда файл выбран — делаем текст чуть темнее */
.poster-group.has-file .file-name-display {color: #222;font-weight: 500;}
/* Скрытый input file */
.hidden-file {position: absolute;inset: 0;opacity: 0;cursor: pointer;z-index: 2;}
/* Плавающий лейбл */
.poster-group .floating-label {position: absolute;left: 16px;top: -8px;font-size: 13px;color: #5F1798;}background: #fff;padding: 0 6px;z-index: 3;transition: all 0.3s ease;pointer-events: none;}
/* Когда лейбл должен подняться */
.poster-group select:focus + .floating-label,
.poster-group.has-file .floating-label {top: -8px;font-size: 13px;color: #5F1798;}
/* Иконка справа */
.file-icon {position: absolute;right: 16px;top: 50%;transform: translateY(-50%);z-index: 3;pointer-events: none;}
/* Красная ошибка */
.poster-group.error .file-name-display {border-color: #e74c3c !important;box-shadow: 0 0 0 3px rgba(231, 76, 60, 0.15);}
.poster-group.error .floating-label {color: #e74c3c !important;}
/* ====================== FLOATING LABELS ====================== */
.floating-group {position: relative;margin-bottom: 1.5rem;}
.floating-label {position: absolute;left: 16px;top: -8px;font-size: 13px;color: #5F1798;transition: all 0.3s ease;pointer-events: none;background: #fff;padding: 0 6px;z-index: 2;}
/* Плавающий лейбл */
.floating-group select:focus + .floating-label, .floating-group textarea:focus + .floating-label,
.floating-group select:not(:placeholder-shown) + .floating-label, .floating-group textarea:not(:placeholder-shown) + .floating-label {top: -8px;font-size: 15px;color: #5F1798;}
/* Красная подсветка — ТОЛЬКО после попытки отправки */
.floating-group.error select, .floating-group.error textarea,
.floating-group.error select[type="file"] {border-color: #e74c3c !important;box-shadow: 0 0 0 3px rgba(231, 76, 60, 0.38);}
.floating-group.error .floating-label {color: #e74c3c !important;}
/* Основные стили полей */
.c_xhd5k, .c_4ykpn {box-sizing: border-box; width: 100%;padding: 16px 16px;border: 2px solid #ddd;border-radius: 8px;font-size: 16px;transition: all 0.3s ease;background: #fff;}
.c_xhd5k:focus, .c_4ykpn:focus {border-color: #5F1798;outline: none;box-shadow: 0 0 0 3px rgba(95, 23, 152, 0.15);}
.c_aq9et {
cursor: pointer;
transition: transform 0.1s linear, box‑shadow 0.1s linear;
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
}
.c_aq9et:active {
transform: scale(0.9);
box-shadow: 5px 5px 5px rgba(95, 23, 152, 0.32);
}
article>a{width: 100%;}
picture>img{ width: 100%; object-fit: scale-down; }
.fl{display: flex;justify-content: space-between;width: 100%;}
.c_som9s { color: #FFF; font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; }
.c_aq9et { display: flex; padding: 19px 33px; justify-content: center; align-items: center; align-self: stretch; border-radius: 5px; background: #5F1798; }
.b_ornr9 { align-self: stretch; height: 400px; overflow: hidden;}
.b_0jst1 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_vabj9 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }
.b_0jst1_2 { color: #5F1798; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_vabj9_2 { align-items: center; background: #5e17982f; border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }
.b_8f3uh { -webkit-box-orient: vertical; -webkit-line-clamp: 1; color: #000; display: -webkit-box; flex: 1 0 0; font-family: Inter; font-size: 24px; font-style: normal; font-weight: 700; line-height: normal; overflow: hidden; text-overflow: ellipsis; }
.b_9xtz9 { align-items: center; align-self: stretch; display: flex; justify-content: center; }
.b_tfnz2 { align-self: stretch; color: #5F1798; font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; cursor: pointer; }
.b_a68fq { aspect-ratio: 1/1; border-radius: 50px; height: 50px; width: 50px; object-fit: cover; }
.b_ghosy { align-self: stretch; color: #000; font-family: Inter; font-size: 16px; font-style: normal; font-weight: 400; line-height: normal; }
.b_ntazj { align-items: flex-start; display: flex; flex-direction: column; flex: 1 0 0; justify-content: center; }
.b_otttb { align-items: center; align-self: stretch; display: flex; gap: 13px; height: 50px; }
.b_s6ool { white-space: pre-line; -webkit-box-orient: vertical; -webkit-line-clamp: 3; align-self: stretch; color: rgba(58, 58, 58, 0.75); display: -webkit-box; font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; overflow: hidden; text-overflow: ellipsis; }
.b_mp1wb { align-self: stretch; color: rgba(58, 58, 58, 0.75); font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; text-align: right; }
.b_73p10 { align-items: flex-start; align-self: stretch; background: #FFF; display: flex; flex-direction: column; gap: 10px; justify-content: center; }
.c_z3dpx{ padding: 10px 20px;}
.b_ax8vz { align-items: flex-start; border-radius: 10px; border: 1px solid #5F1798; box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.30), 0 4px 20px 0 rgba(0, 0, 0, 0.20); display: flex; flex-direction: column; width: 335px; overflow: hidden;}
@media (min-width: 901px) {
.c_9fgdh { align-items: flex-start; border-radius: 10px; border: 1px solid #5F1798; box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.30), 0 4px 20px 0 rgba(0, 0, 0, 0.20); display: flex; flex-direction: column; flex-shrink: 0; width: 335px; }
}
@media (max-width: 699px) {
.b_ornr9{height: auto;}
}
</style>
<style>
.menua{width: 100%;
display: flex;
flex-direction: column;
gap: 7px;
margin: 10px 20px;}
.icona{display: flex;
gap: 10px;
align-items: center;}
.icona>span{color: #5F1798;}
.c_hysjg {
color: #0ED646;
font-family: Inter;
/* font-size: 12px; */
font-style: normal;
font-weight: 700;
line-height: normal;
}
/* .c_m876y{align-items: center;
background: rgba(14, 214, 70, 0.20);
border-radius: 30px;
display: flex;
justify-content: center;
padding: 1.5px 10px;} */
/* Mobile */
@media (max-width: 699px) {
.c_mrl59 { align-items: center; background: #5F1798; border-radius: 25px; bottom: -15px; display: flex; height: 50px; justify-content: center; position: absolute; right: -15px; width: 50px; }
}
/* Tablet */
@media (min-width: 700px) and (max-width: 900px) {
.c_iiehk { align-items: center; border-radius: 5px; display: flex; flex-direction: column; gap: 20px; width: 335px; }
.c_nnsh6>a>img{display: none;}
}
/* Desktop */
@media (min-width: 901px) {
.c_iiehk { align-items: flex-end; border-radius: 5px; display: flex; flex-direction: column; gap: 20px; width: 310px; }
.c_nnsh6>a>img{display: none;}
}</style>
<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_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_5dr47_2 { color: #5F1798; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_6spaf {
color: #5F1798;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.b_qla2q {
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
color: #000;
display: -webkit-box;
font-family: Inter;
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: normal;
overflow: hidden;
text-overflow: ellipsis;
}
.b_uu2ub { align-items: flex-start; background: #FDFAFF; border-radius: 5px; border: 1px solid #5F1798; box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.25), 0 4px 20px 0 rgba(0, 0, 0, 0.20); display: flex; flex-direction: column; width: 335px; }
.b_1fvkt { align-self: stretch; border-radius: 5px; flex-shrink: 0; }
.b_pvw5a { gap: 7px;align-items: flex-start; align-self: stretch; display: flex; flex-direction: column; flex: 1 0 0; justify-content: space-between; padding: 10px 20px 37px 20px; }
.b_xz6em { align-items: center; align-self: stretch; display: flex; gap: 13px; }
.b_ob59y { border-radius: 1em; height: 70px; min-width: 70px; max-width: 150px; vertical-align: top; object-fit: cover;}
.b_ffruz { align-items: flex-start; display: flex; flex-direction: column; flex: 1 0 0; justify-content: center; }
.b_09est { color: #000; font-family: Inter; font-size: 15px; font-style: normal; font-weight: 700; line-height: normal; }
.b_wy3rt {
align-items: center;
background: #5F1798;
border-radius: 5px;
display: flex;
justify-content: center;
padding: 15px 0;
width: 100%;
}
.b_bvl0d {
color: #FFF;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
/* Mobile */
@media (max-width: 699px) {
.c_3g8gh { align-items: flex-start; align-self: stretch; display: flex; flex-direction: column; flex-shrink: 0; justify-content: space-between; padding: 10px 20px 37px 20px; }
.c_4b1hy { align-items: flex-start; background: #FDFAFF; border-radius: 5px; border: 1px solid #5F1798; box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.25), 0 4px 20px 0 rgba(0, 0, 0, 0.20); display: flex; flex-direction: column; width: 335px; }
.c_mrl59 { align-items: center; background: #5F1798; border-radius: 25px; bottom: -15px; display: flex; height: 50px; justify-content: center; position: absolute; right: -15px; width: 50px; }
}
/* Tablet */
@media (min-width: 700px) and (max-width: 900px) {
.c_iiehk { align-items: center; border-radius: 5px; display: flex; flex-direction: column; gap: 20px; width: 335px; }
.c_nnsh6>a>img{display: none;}
}
/* Desktop */
@media (min-width: 901px) {
.c_iiehk { align-items: flex-end; border-radius: 5px; display: flex; flex-direction: column; gap: 20px; width: 310px; }
.c_nnsh6>a>img{display: none;}
}</style>
<style>/* Base (common) styles */
.c_aq9et {
cursor: pointer;
transition: transform 0.1s linear, box‑shadow 0.1s linear;
@ -936,73 +228,6 @@ picture>img{ width: 100%; object-fit: scale-down; }
.c_iiehk { align-items: flex-end; border-radius: 5px; display: flex; flex-direction: column; gap: 20px; width: 310px; }
.c_nnsh6>a>img{display: none;}
}</style>
<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_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_5dr47_2 { color: #5F1798; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_6spaf {
color: #5F1798;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.b_qla2q {
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
color: #000;
display: -webkit-box;
font-family: Inter;
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: normal;
overflow: hidden;
text-overflow: ellipsis;
}
.b_uu2ub { align-items: flex-start; background: #FDFAFF; border-radius: 5px; border: 1px solid #5F1798; box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.25), 0 4px 20px 0 rgba(0, 0, 0, 0.20); display: flex; flex-direction: column; width: 335px; }
.b_1fvkt { align-self: stretch; border-radius: 5px; flex-shrink: 0; }
.b_pvw5a { gap: 7px;align-items: flex-start; align-self: stretch; display: flex; flex-direction: column; flex: 1 0 0; justify-content: space-between; padding: 10px 20px 37px 20px; }
.b_xz6em { align-items: center; align-self: stretch; display: flex; gap: 13px; }
.b_ob59y { border-radius: 1em; height: 70px; min-width: 70px; max-width: 150px; vertical-align: top; object-fit: cover;}
.b_ffruz { align-items: flex-start; display: flex; flex-direction: column; flex: 1 0 0; justify-content: center; }
.b_09est { color: #000; font-family: Inter; font-size: 15px; font-style: normal; font-weight: 700; line-height: normal; }
.b_wy3rt {
align-items: center;
background: #5F1798;
border-radius: 5px;
display: flex;
justify-content: center;
padding: 15px 0;
width: 100%;
}
.b_bvl0d {
color: #FFF;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
/* Mobile */
@media (max-width: 699px) {
.c_3g8gh { align-items: flex-start; align-self: stretch; display: flex; flex-direction: column; flex-shrink: 0; justify-content: space-between; padding: 10px 20px 37px 20px; }
.c_4b1hy { align-items: flex-start; background: #FDFAFF; border-radius: 5px; border: 1px solid #5F1798; box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.25), 0 4px 20px 0 rgba(0, 0, 0, 0.20); display: flex; flex-direction: column; width: 335px; }
.c_mrl59 { align-items: center; background: #5F1798; border-radius: 25px; bottom: -15px; display: flex; height: 50px; justify-content: center; position: absolute; right: -15px; width: 50px; }
}
/* Tablet */
@media (min-width: 700px) and (max-width: 900px) {
.c_iiehk { align-items: center; border-radius: 5px; display: flex; flex-direction: column; gap: 20px; width: 335px; }
.c_nnsh6>a>img{display: none;}
}
/* Desktop */
@media (min-width: 901px) {
.c_iiehk { align-items: flex-end; border-radius: 5px; display: flex; flex-direction: column; gap: 20px; width: 310px; }
.c_nnsh6>a>img{display: none;}
}</style>
{{ footer.style() }}
{% endblock %}