903 lines
42 KiB
HTML
903 lines
42 KiB
HTML
<!doctype html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<link rel="preconnect" href="https://avtopost.kultmost.ru">
|
||
<link rel="dns-prefetch" href="https://avtopost.kultmost.ru">
|
||
<link rel="manifest" href="/manifest.json">
|
||
<link rel="icon" type="image/ico" href="/static/favicon.ico">
|
||
<link rel="icon" type="image/ico" sizes="256x256" href="/static/favicon.ico">
|
||
<link rel="apple-touch-icon" type="image/ico" href="/static/favicon.ico">
|
||
<meta name="theme-color" content="#5F1798"/>
|
||
<title>{% block title %}{% endblock %}</title>
|
||
{% block head %}{% endblock %}
|
||
<style>
|
||
{% raw %}
|
||
h1,h2,h3,p {
|
||
max-width: 100%;
|
||
overflow-wrap: break-word;
|
||
word-break: break-word;
|
||
}
|
||
html{display: flex;justify-content: center;}
|
||
input, button {touch-action: manipulation;}
|
||
picture{cursor: zoom-in;}
|
||
.ba { cursor: pointer; box-shadow: 0 4px 14px -4px rgba(95,23,152,.5); transition: transform .18s, box-shadow .18s, background .18s; }
|
||
.ba:hover { background: #6d1ba9; transform: translateY(-2px); box-shadow: 0 10px 24px -6px rgba(95,23,152,.6); }
|
||
.ba:active { transform: translateY(0); box-shadow: 0 4px 12px -4px rgba(95,23,152,.5); }
|
||
@media (prefers-reduced-motion: reduce) { .ba:hover, .ba:active { transform: none; } }
|
||
*{margin:unset;box-sizing:border-box;position:relative;transition:300ms}button,input{all:unset}button,a{cursor:pointer}input{cursor:auto}button>span{text-wrap:nowrap}@font-face{font-family:"Inter";font-style:normal;font-weight:100;font-display:swap;src:url(/static/Inter-Thin.woff2) format("woff2")}@font-face{font-family:"Inter";font-style:normal;font-weight:200;font-display:swap;src:url(/static/Inter-ExtraLight.woff2) format("woff2")}@font-face{font-family:"Inter";font-style:normal;font-weight:300;font-display:swap;src:url(/static/Inter-Light.woff2) format("woff2")}@font-face{font-family:"Inter";font-style:normal;font-weight:400;font-display:swap;src:url(/static/Inter-Regular.woff2) format("woff2")}@font-face{font-family:"Inter";font-style:normal;font-weight:500;font-display:swap;src:url(/static/Inter-Medium.woff2) format("woff2")}@font-face{font-family:"Inter";font-style:normal;font-weight:600;font-display:swap;src:url(/static/Inter-SemiBold.woff2) format("woff2")}@font-face{font-family:"Inter";font-style:normal;font-weight:700;font-display:swap;src:url(/static/Inter-Bold.woff2) format("woff2")}@font-face{font-family:"Inter";font-style:normal;font-weight:800;font-display:swap;src:url(/static/Inter-ExtraBold.woff2) format("woff2")}@font-face{font-family:"Inter";font-style:normal;font-weight:900;font-display:swap;src:url(/static/Inter-Black.woff2) format("woff2")}@font-face{font-family:'Roboto';font-style:normal;font-weight:400;font-display:swap;src:url(/static/fonts/KFOmCnqEu92Fr1Mu72xKOzY.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Roboto';font-style:normal;font-weight:400;font-display:swap;src:url(/static/fonts/KFOmCnqEu92Fr1Mu5mxKOzY.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Roboto';font-style:normal;font-weight:400;font-display:swap;src:url(/static/fonts/KFOmCnqEu92Fr1Mu7GxKOzY.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Roboto';font-style:normal;font-weight:400;font-display:swap;src:url(/static/fonts/KFOmCnqEu92Fr1Mu4mxK.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Roboto';font-style:normal;font-weight:500;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmEU9fCRc4EsA.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Roboto';font-style:normal;font-weight:500;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmEU9fABc4EsA.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Roboto';font-style:normal;font-weight:500;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmEU9fChc4EsA.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Roboto';font-style:normal;font-weight:500;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmEU9fBBc4.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmWUlfCRc4EsA.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmWUlfABc4EsA.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmWUlfChc4EsA.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmWUlfBBc4.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Roboto';font-style:normal;font-weight:900;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmYUtfCRc4EsA.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Roboto';font-style:normal;font-weight:900;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmYUtfABc4EsA.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Roboto';font-style:normal;font-weight:900;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmYUtfChc4EsA.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Roboto';font-style:normal;font-weight:900;font-display:swap;src:url(/static/fonts/KFOlCnqEu92Fr1MmYUtfBBc4.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}::-webkit-scrollbar{height:5px}::-webkit-scrollbar-track{background:#0000}::-webkit-scrollbar-thumb{background:#BDBDBD}::-webkit-scrollbar-thumb:hover{background:#323d5b;border-radius:10px}body{scrollbar-width:none;max-width:1600px;display:grid;justify-content:center}body::-webkit-scrollbar{display:none}:root{font-family:Inter,sans-serif;font-feature-settings:"liga" 1,"calt" 1}a{text-decoration:none}*,::before,::after{box-sizing:border-box}.modal{transition:none;position:fixed;z-index:1000;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:#000c;display:flex;justify-content:center;align-items:center}.close{position:fixed;top:20px;right:30px;color:#fff;font-size:40px;cursor:pointer}#modalLink{transition:none;display:flex;position:fixed;bottom:13px;justify-content:center;width:335px;border-radius:1em;height:38px;gap:5px;align-items:center;background:#5F1798}
|
||
{% endraw %}
|
||
</style>
|
||
<style>
|
||
{% raw %}
|
||
.toast-container {position: fixed;top: 16px;right: 16px;display: flex;flex-direction: column;gap: 10px;z-index: 9999;pointer-events: none;}.toast {pointer-events: auto;width: 365px;max-width: calc(100vw - 40px);background: linear-gradient(90deg,#5f1798,#652895);color: #fff;border-radius: 10px;box-shadow: 0 8px 24px rgba(0,0,0,0.18);overflow: hidden;transform: translateX(110%);opacity: 0;transition: transform 360ms cubic-bezier(.2,.9,.3,1), opacity 260ms;display: flex;flex-direction: column;}.toast.show {transform: translateX(0);opacity: 1;}.toast .accent {height: 6px;width: 100%;}.toast.info .accent { background: linear-gradient(90deg,#0066ff,#3aa0ff); }.toast.error .accent { background: linear-gradient(90deg,#ff3b30,#ff7b7b); }.toast-header {padding: 10px 12px;font-size:18px;font-weight: 500;display:flex;align-items:center;justify-content:space-between;gap:8px;}.toast-body {padding: 10px 12px 14px;font-size:14px;}.toast-actions {display:flex;gap:8px;}.toast-close {background: transparent;border: none;font-weight:700;cursor:pointer;color: #666;padding:4px;border-radius:6px;}.toast-close:active { transform: scale(.98); }
|
||
{% endraw %}
|
||
</style>
|
||
<style>
|
||
{%raw%}
|
||
.offer-label{display:flex;align-items:center;gap:12px;cursor:pointer;user-select:none;font-size:15px;color:#333}.offer-checkbox{appearance:none;-webkit-appearance:none;width:22px;height:22px;border:2px solid #ccc;border-radius:6px;background-color:#fff;margin:0;flex-shrink:0;transition:all .25s ease;cursor:pointer;box-shadow:0 1px 3px #00000014}.offer-checkbox:hover{background-color:#5e17988f;box-shadow:0 0 0 3px #5F179826}.offer-checkbox:checked{background-color:#5F1798;box-shadow:0 0 0 3px #5F179826}.offer-checkbox:checked::after{content:'';display:block;width:100%;height:100%;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M20.285 6.293a1 1 0 00-1.414 0L9 16.172 5.129 12.3a1 1 0 00-1.414 1.414l4.95 4.95a1 1 0 001.414 0l10.586-10.586a1 1 0 000-1.414z'/%3E%3C/svg%3E");background-size:14px;background-position:center;background-repeat:no-repeat}.offer-label:hover .offer-checkbox{border-color:#5F1798}.offer-checkbox:disabled{background-color:#f5f5f5;border-color:#ddd;cursor:not-allowed}.dd-modal-backdrop{position:fixed;inset:0;background:#0006;display:flex;align-items:center;justify-content:center;z-index:1999}.dd-modal{background:#fff;padding:16px;border-radius:8px;min-width:320px;max-width:90%;box-shadow:0 6px 24px #0003}.dd-modal .dd-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}.dd-modal .dd-body{max-height:50vh;overflow:auto;margin-bottom:12px}.dd-modal .dd-actions{text-align:right}.dd-actions > button{border-radius:5px;background:#5F1798;color:#fff;padding:8px 15px}.dd-modal button{cursor:pointer}.dd-close{background:transparent;border:0;font-size:18px}.dd-option{display:flex;align-items:center;gap:8px;padding:6px 4px;border-radius:4px}.dd-option:hover{background:#f4f4f4}
|
||
{% endraw %}
|
||
</style>
|
||
<style>
|
||
.b {
|
||
cursor: pointer;
|
||
transition: transform 0.1s linear, box‑shadow 0.1s linear;
|
||
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
|
||
}
|
||
|
||
.b:active {
|
||
transform: scale(0.9);
|
||
box-shadow: 5px 5px 5px rgba(95, 23, 152, 0.32);
|
||
}
|
||
.some-class{ display: flex;
|
||
height: 24px;
|
||
gap: 7px;
|
||
align-items: center;}
|
||
#modalToolTip {
|
||
display: none;z-index: 2000;
|
||
position: fixed;
|
||
left: 0;
|
||
top: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
overflow: auto;
|
||
align-items: center;
|
||
background-color: rgba(0,0,0,0.4);
|
||
}
|
||
|
||
.modal-content {
|
||
background-color: #fefefe;
|
||
margin: 15% auto;
|
||
padding: 20px;
|
||
border: 1px solid #888;
|
||
width: 80%;
|
||
border-radius: 10px;
|
||
overflow: auto;
|
||
max-height: 98%;
|
||
}
|
||
|
||
.close {
|
||
color: white;
|
||
float: right;
|
||
font-size: 28px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.close:hover,
|
||
.close:focus {
|
||
color: rgb(211, 211, 211);
|
||
text-decoration: none;
|
||
cursor: pointer;
|
||
}
|
||
.dd-option>span{display: flex;
|
||
justify-content: space-between;align-items: center;
|
||
width: 100%;}
|
||
</style>
|
||
</head>
|
||
<body {% if request.cookies.zoom%}style="zoom: {{ request.cookies.zoom }}%"{%endif%} onload="adjustBodyStyle()">
|
||
<script>
|
||
function setSecureCookie(name, value, days = 7) {const date = new Date();date.setTime(date.getTime() + (days * 86400000));document.cookie = `${name}=${encodeURIComponent(value)}; expires=${date.toUTCString()}; path=/; SameSite=Strict; Secure`;}
|
||
var userAgent = navigator.userAgent;var isFirefox = userAgent.indexOf("Firefox") !== -1;function isOldFirefox() {var match = userAgent.match(/Firefox\/(\d+)/);return match && parseInt(match[1], 10) < 126;};function iOS() {return ['iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'].includes(navigator.platform) || (userAgent.includes("Mac") && "ontouchend" in document);};function adjustBodyStyle() {var body = document.body;var width;if (iOS()) {width = screen.width;} else {width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || 360;};const zoom = (width < 700) ? width * 100 / 400 : (width < 900) ? width * 100 / 800 : width * 100 / 1600;if (isFirefox && isOldFirefox()) {body.style.MozTransform = `scale(${zoom}%)`;body.style.MozTransformOrigin = '0% 0%';body.style.width = (width < 901) ? '360px' : '1600px';body.style.position = 'absolute';};setSecureCookie("zoom", zoom, 7);body.style.zoom = `${zoom}%`;};adjustBodyStyle();window.addEventListener('resize', adjustBodyStyle, { passive: true });
|
||
</script>
|
||
{% block content %}{% endblock %}
|
||
<div id="modal" class="modal" style="display: none;cursor: zoom-out;">
|
||
<span class="close" onclick="closeModalPic()">×</span>
|
||
<div style="width: 100%;height: 100%; position: absolute;" onclick="closeModalPic()"></div>
|
||
<img id="modalImg" alt="" src="/static/painter.svg" onclick="closeModalPic()" style="height: 100%;position: absolute;left: 0;transition: none;min-width: 100%;object-fit: scale-down;" />
|
||
<a id="modalLink" style="color: white;text-align: center;display: none;">
|
||
<span id="modalLinkText"></span>
|
||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFFFFF"><path d="m256-240-56-56 384-384H240v-80h480v480h-80v-344L256-240Z"/></svg>
|
||
</a>
|
||
</div>
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
document.querySelectorAll('picture').forEach(picture => {
|
||
picture.addEventListener('click', function() {
|
||
const img = picture.querySelector('img');
|
||
const modalImg = document.getElementById('modalImg');
|
||
const modal = document.getElementById('modal');
|
||
const modalLink = document.getElementById('modalLink');
|
||
const modalLinkText = document.getElementById('modalLinkText');
|
||
let node = picture.parentElement;
|
||
while (node) {
|
||
if (node.tagName && node.tagName.toLowerCase() === 'a') {
|
||
return;
|
||
}
|
||
node = node.parentElement;
|
||
}
|
||
if (img.dataset.src){
|
||
modalImg.src = img.dataset.src;
|
||
} else {
|
||
modalImg.src = img.src;
|
||
}
|
||
if (picture.dataset.link && picture.dataset.label) {
|
||
modalLink.href = picture.dataset.link;
|
||
modalLinkText.textContent = picture.dataset.label;
|
||
modalLink.style.display = 'flex';
|
||
} else {
|
||
modalLink.style.display = 'none';
|
||
}
|
||
modal.style.display = 'flex';
|
||
|
||
});
|
||
});
|
||
});
|
||
function closeModalPic() {const modalImg = document.getElementById('modalImg');modalImg.src = '';document.getElementById('modal').style.display = 'none';}
|
||
</script>
|
||
<script>
|
||
{% raw %}
|
||
function showToast(e={}){let{message:t="",status:a="info",duration:l=4e3}=e,n=document.querySelector(".toast-container");n||((n=document.createElement("div")).className="toast-container",document.body.appendChild(n));let s=document.createElement("div");s.className=`toast ${a}`;let d=document.createElement("div");d.className="accent";let c=document.createElement("div");c.className="toast-header";let i=document.createElement("div");i.textContent=t;let o=document.createElement("button");o.className="toast-close",o.innerHTML="✕",c.appendChild(i),c.appendChild(o),s.appendChild(d),s.appendChild(c),n.appendChild(s),requestAnimationFrame(()=>s.classList.add("show"));let r=!1,p=()=>{r||(r=!0,s.classList.remove("show"),setTimeout(()=>{s.parentNode&&s.parentNode.removeChild(s)},380))};return o.addEventListener("click",p),s.addEventListener("click",e=>{e.target===s&&p()}),l>0&&setTimeout(p,l),{close:p}}
|
||
{% endraw %}
|
||
{% if notify %}
|
||
showToast({message: '{{notify.text}}', status: '{{notify.status}}', duration:{{notify.duration}}});
|
||
{% endif %}
|
||
</script>
|
||
<div id="shareModal"
|
||
style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.4);align-items:center;justify-content:center;z-index: 3000;">
|
||
<div style="background:#fff;padding:16px;border-radius:8px;min-width:260px;"><button id="closeModal"
|
||
style="float:right">×</button>
|
||
<h3>Поделиться</h3>
|
||
<div id="socialLinks" style="display:flex;gap:8px;margin-top:8px;flex-wrap: wrap;">
|
||
<!-- <a id="tw" href="#" target="_blank" rel="noopener"><img /></a> --><a id="fb" href="#" target="_blank"
|
||
rel="noopener"><img loading="lazy" alt="share link facebook" src="/static/icons/facebook.svg"></a><a id="vk" href="#" target="_blank"
|
||
rel="noopener"><img loading="lazy" alt="share link vk" src="/static/icons/vk.svg"></a><a id="tg" href="#" target="_blank"
|
||
rel="noopener"><img loading="lazy" alt="share link telegram" src="/static/icons/telegram.svg"></a><a id="ok" href="#" target="_blank"
|
||
rel="noopener"><img loading="lazy" alt="share link ok" src="/static/icons/ok.svg"></a><a id="wa" href="#" target="_blank"
|
||
rel="noopener"><img loading="lazy" alt="share link wapp" src="/static/icons/wapp.svg"></a><a id="inst" href="#" target="_blank"
|
||
rel="noopener"><img loading="lazy" alt="share link instagram" src="/static/icons/instagram.svg"></a><button id="copyBtn"
|
||
style="display: flex;align-items: center;gap: 5px;">Скопировать ссылку<img loading="lazy" alt="copy link for share"
|
||
src="/static/icons/copy.svg"> <span id="copyMsg"
|
||
style="margin-left:8px;color:green;display:none;">Скопировано!</span></button></div>
|
||
</div>
|
||
</div>
|
||
<script>
|
||
async function imageWithTextBlob(imageUrl, text, options = {}) {
|
||
const response = await fetch(imageUrl, {
|
||
mode: 'cors',
|
||
credentials: 'same-origin',
|
||
});
|
||
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
|
||
const imageBlob = await response.blob();
|
||
const imageBitmap = await createImageBitmap(imageBlob);
|
||
|
||
// OffscreenCanvas (works in modern browsers; fallback to normal canvas if needed)
|
||
const canvas = typeof OffscreenCanvas !== 'undefined'
|
||
? new OffscreenCanvas(
|
||
options.width ?? imageBitmap.width,
|
||
options.height ?? imageBitmap.height
|
||
)
|
||
: document.createElement('canvas');
|
||
|
||
const width = canvas.width ?? (options.width ?? imageBitmap.width);
|
||
const height = canvas.height ?? (options.height ?? imageBitmap.height);
|
||
|
||
if (!(canvas instanceof OffscreenCanvas)) {
|
||
canvas.width = width;
|
||
canvas.height = height;
|
||
}
|
||
|
||
const ctx = canvas.getContext('2d', { alpha: true });
|
||
|
||
// Draw image
|
||
ctx.drawImage(imageBitmap, 0, 0, width, height);
|
||
|
||
// No text: return original bitmap as blob (rendered)
|
||
if (!text?.trim()) {
|
||
if (canvas.convertToBlob) {
|
||
return canvas.convertToBlob({
|
||
type: options.type ?? 'image/png',
|
||
quality: options.quality ?? 0.92,
|
||
});
|
||
}
|
||
return await new Promise((resolve) =>
|
||
canvas.toBlob((b) => resolve(b), options.type ?? 'image/png', options.quality ?? 0.92)
|
||
);
|
||
}
|
||
|
||
// === Text rendering ===
|
||
const basePadding = 6;
|
||
const fontSize = options.fontSize ?? Math.round(width * 0.05);
|
||
ctx.font = `${fontSize}px ${options.fontFamily ?? 'sans-serif'}`;
|
||
ctx.textBaseline = 'bottom';
|
||
ctx.textAlign = 'center';
|
||
|
||
const textColor = options.textColor ?? '#5F1798';
|
||
const bgColor = options.bgColor ?? '#fff';
|
||
const strokeColor = options.strokeColor ?? 'rgba(0,0,0,0.35)';
|
||
const shadowBlur = options.shadowBlur ?? 6;
|
||
const maxWidth = width - basePadding * 2 - (options.extraPadding ?? 0);
|
||
|
||
// Wrap text into lines
|
||
const words = text.split(' ');
|
||
let line = '';
|
||
const lines = [];
|
||
for (const word of words) {
|
||
const testLine = line ? line + ' ' + word : word;
|
||
if (ctx.measureText(testLine).width > maxWidth && line) {
|
||
lines.push(line);
|
||
line = word;
|
||
} else {
|
||
line = testLine;
|
||
}
|
||
}
|
||
if (line) lines.push(line);
|
||
|
||
const lineHeight = fontSize + (options.lineGap ?? 6);
|
||
const boxHeight = lines.length * lineHeight + basePadding * 2;
|
||
const maxLineWidth = Math.max(...lines.map((l) => ctx.measureText(l).width));
|
||
const boxWidth = maxLineWidth + basePadding * 2;
|
||
|
||
const bottomPadding = options.bottomPadding ?? Math.round(fontSize * 0.6);
|
||
const boxX = (width - boxWidth) / 2;
|
||
const boxY = height - bottomPadding - boxHeight;
|
||
const radius = options.radius ?? 8;
|
||
|
||
// Background rounded rect
|
||
ctx.save();
|
||
ctx.fillStyle = bgColor;
|
||
ctx.shadowColor = strokeColor;
|
||
ctx.shadowBlur = shadowBlur;
|
||
roundRect(ctx, boxX, boxY, boxWidth, boxHeight, radius, true, false);
|
||
ctx.restore();
|
||
|
||
// Text
|
||
ctx.fillStyle = textColor;
|
||
ctx.shadowColor = 'transparent';
|
||
for (let i = 0; i < lines.length; i++) {
|
||
const lineY = boxY + basePadding + (i + 1) * lineHeight - (lineHeight - fontSize) / 2;
|
||
const lineX = width / 2;
|
||
|
||
if (options.outline) {
|
||
ctx.lineWidth = options.outlineWidth ?? 2;
|
||
ctx.strokeStyle = options.outlineColor ?? 'rgba(255,255,255,0.8)';
|
||
ctx.strokeText(lines[i], lineX, lineY);
|
||
}
|
||
ctx.fillText(lines[i], lineX, lineY);
|
||
}
|
||
|
||
// Convert to blob
|
||
if (canvas.convertToBlob) {
|
||
return await canvas.convertToBlob({
|
||
type: options.type ?? 'image/png',
|
||
quality: options.quality ?? 0.92,
|
||
});
|
||
}
|
||
return await new Promise((resolve) =>
|
||
canvas.toBlob((b) => resolve(b), options.type ?? 'image/png', options.quality ?? 0.92)
|
||
);
|
||
}
|
||
|
||
function roundRect(ctx, x, y, w, h, r, fill, stroke) {
|
||
if (typeof r === 'number') r = { tl: r, tr: r, br: r, bl: r };
|
||
ctx.beginPath();
|
||
ctx.moveTo(x + r.tl, y);
|
||
ctx.lineTo(x + w - r.tr, y);
|
||
ctx.quadraticCurveTo(x + w, y, x + w, y + r.tr);
|
||
ctx.lineTo(x + w, y + h - r.br);
|
||
ctx.quadraticCurveTo(x + w, y + h, x + w - r.br, y + h);
|
||
ctx.lineTo(x + r.bl, y + h);
|
||
ctx.quadraticCurveTo(x, y + h, x, y + h - r.bl);
|
||
ctx.lineTo(x, y + r.tl);
|
||
ctx.quadraticCurveTo(x, y, x + r.tl, y);
|
||
ctx.closePath();
|
||
if (fill) ctx.fill();
|
||
if (stroke) ctx.stroke();
|
||
}
|
||
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const shareModal = document.getElementById('shareModal');
|
||
const closeshareModal = document.getElementById('closeModal');
|
||
const fb = document.getElementById('fb');
|
||
const vk = document.getElementById('vk');
|
||
const tg = document.getElementById('tg');
|
||
const ok = document.getElementById('ok');
|
||
const wa = document.getElementById('wa');
|
||
const inst = document.getElementById('inst');
|
||
const copyBtn = document.getElementById('copyBtn');
|
||
const copyMsg = document.getElementById('copyMsg');
|
||
function buildShareUrl(platform, url, text = '') {
|
||
const u = encodeURIComponent(url);
|
||
const t = encodeURIComponent(text);
|
||
switch (platform) {
|
||
case 'tw': return `https://twitter.com/intent/tweet?url=${u}&text=${t}`;
|
||
case 'vk': return `https://vk.com/share.php?url=${u}&title=${t}`;
|
||
case 'tg': return `https://telegram.me/share/url?url=${u}&text=${t}`;
|
||
case 'fb': return `https://www.facebook.com/sharer/sharer.php?u=${u}"e=${t}`;
|
||
case 'ok': return `https://connect.ok.ru/offer?url=${u}&title=${t}`;
|
||
case 'wa': return `https://api.whatsapp.com/send?text=${t} ${u}`;
|
||
case 'inst': return `https://www.instagram.com/?url=${u}`;
|
||
default: return '#';
|
||
}
|
||
}
|
||
const shareBtns = document.querySelectorAll('#shareBtn');
|
||
shareBtns.forEach(btn => {
|
||
btn.addEventListener('click', async () => {
|
||
const link = window.location.origin + btn.dataset.link || window.location.href;
|
||
const text = btn.dataset.text || document.title;
|
||
const image = btn.dataset.image;
|
||
const imagetext = btn.dataset.imagetext || '';
|
||
|
||
let blob = null;
|
||
let file = null;
|
||
let objectUrl = null;
|
||
|
||
try {
|
||
if (image) {
|
||
const imageUrl = window.location.origin + image;
|
||
// В обработчике кнопки
|
||
const blob = await imageWithTextBlob(imageUrl, imagetext, {
|
||
textColor: '#5F1798',
|
||
});
|
||
console.log(`Generated blob for "${imagetext}" | size: ${blob.size} | url: ${imageUrl}`);
|
||
const filename = `shared-image-${Date.now()}.png`;
|
||
file = new File([blob], filename, { type: blob.type });
|
||
|
||
// Создаём object URL только если хотим (необязательно для navigator.share)
|
||
objectUrl = URL.createObjectURL(file);
|
||
|
||
if (navigator.canShare && navigator.canShare({ files: [file] })) {
|
||
await navigator.share({
|
||
title: text,
|
||
text: text,
|
||
url: link,
|
||
files: [file],
|
||
});
|
||
} else {
|
||
await navigator.share({
|
||
title: text,
|
||
text: '',
|
||
url: link,
|
||
});
|
||
}
|
||
} else {
|
||
await navigator.share({ title: text, text: '', url: link });
|
||
}
|
||
} catch (err) {
|
||
openshareModal(link, text);
|
||
} finally {
|
||
// Очистка
|
||
try {
|
||
if (objectUrl) {
|
||
URL.revokeObjectURL(objectUrl);
|
||
objectUrl = null;
|
||
}
|
||
} catch (e) { /* ignore */ }
|
||
|
||
// Удаляем ссылки, чтобы GC мог собрать объекты
|
||
blob = null;
|
||
file = null;
|
||
}
|
||
});
|
||
});
|
||
|
||
|
||
function openshareModal(link, text) {
|
||
vk.href = buildShareUrl('vk', link, text);
|
||
tg.href = buildShareUrl('tg', link, text);
|
||
fb.href = buildShareUrl('fb', link, text);
|
||
ok.href = buildShareUrl('ok', link, text);
|
||
wa.href = buildShareUrl('wa', link, text);
|
||
inst.href = buildShareUrl('inst', link, text);
|
||
shareModal.style.display = 'flex';
|
||
copyBtn.dataset.link = link;
|
||
shareModal.style.display = 'flex'
|
||
}
|
||
|
||
closeshareModal.addEventListener('click', () => {
|
||
shareModal.style.display = 'none';
|
||
});
|
||
|
||
shareModal.addEventListener('click', (e) => {
|
||
if (e.target === shareModal) shareModal.style.display = 'none';
|
||
});
|
||
|
||
async function copyToClipboard(text) {
|
||
try {
|
||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||
await navigator.clipboard.writeText(text);
|
||
} else {
|
||
const ta = document.createElement('textarea');
|
||
ta.value = text;
|
||
ta.style.position = 'fixed';
|
||
ta.style.left = '-9999px';
|
||
document.body.appendChild(ta);
|
||
ta.select();
|
||
document.execCommand('copy');
|
||
document.body.removeChild(ta);
|
||
}
|
||
showCopyMessage();
|
||
} catch (e) {
|
||
showToast({message: 'Не удалось скопировать ссылку', status: 'error'});
|
||
}
|
||
}
|
||
|
||
function showCopyMessage() {
|
||
copyMsg.style.display = 'inline';
|
||
setTimeout(() => copyMsg.style.display = 'none', 2000);
|
||
}
|
||
|
||
copyBtn.addEventListener('click', () => {
|
||
const link = copyBtn.dataset.link || window.location.href;
|
||
copyToClipboard(link);
|
||
});
|
||
});
|
||
|
||
</script>
|
||
<script>
|
||
(async () => {
|
||
const parts_cookie = `; ${document.cookie}`.split(`; token=`);
|
||
const token_old = parts_cookie.pop().split(';')[0];
|
||
|
||
if (!token_old) return;
|
||
|
||
try {
|
||
let response = await fetch('/api/me');
|
||
|
||
if (!response.ok) {
|
||
response = await fetch('/api/old/login', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
}
|
||
|
||
if (!response.ok) {
|
||
throw new Error('Network response was not ok');
|
||
}
|
||
|
||
const data = await response.json();
|
||
|
||
if (!data.username) {
|
||
response = await fetch('/api/old/login', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
if (!response.ok) {
|
||
throw new Error('Network response was still not ok');
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('There was a problem with the fetch operation:', error);
|
||
}
|
||
})();
|
||
</script>
|
||
<script>
|
||
let deferredPrompt = null;
|
||
let hasUserInteracted = false;
|
||
let promptTimeout = null;
|
||
|
||
if ('serviceWorker' in navigator) {
|
||
navigator.serviceWorker.register('/sw.js').then(reg => {
|
||
reg.addEventListener('updatefound', () => {
|
||
const newWorker = reg.installing;
|
||
newWorker.addEventListener('statechange', () => {
|
||
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
|
||
console.log('New Service Worker ready');
|
||
}
|
||
});
|
||
});
|
||
});
|
||
|
||
navigator.serviceWorker.addEventListener('message', event => {
|
||
if (event.data.type === 'SW_UPDATED') {
|
||
console.log('New Service Worker activated – reloading...');
|
||
window.location.reload();
|
||
}
|
||
});
|
||
}
|
||
|
||
window.addEventListener('beforeinstallprompt', (e) => {
|
||
e.preventDefault();
|
||
deferredPrompt = e;
|
||
console.log('beforeinstallprompt: готово к установке');
|
||
});
|
||
|
||
|
||
const markInteractionAndTryShow = () => {
|
||
if (hasUserInteracted) return;
|
||
hasUserInteracted = true;
|
||
|
||
document.removeEventListener('click', markInteractionAndTryShow);
|
||
document.removeEventListener('keydown', markInteractionAndTryShow);
|
||
document.removeEventListener('touchstart', safeTouchHandler);
|
||
|
||
tryShowInstallPrompt();
|
||
};
|
||
|
||
let touchMoved = false;
|
||
const safeTouchHandler = (e) => {
|
||
if (e.touches.length > 1) return;
|
||
document.addEventListener('touchmove', () => touchMoved = true, { once: true });
|
||
document.addEventListener('touchend', () => {
|
||
if (!touchMoved) markInteractionAndTryShow();
|
||
touchMoved = false;
|
||
}, { once: true });
|
||
};
|
||
|
||
document.addEventListener('click', markInteractionAndTryShow, { passive: true });
|
||
document.addEventListener('keydown', markInteractionAndTryShow, { passive: true });
|
||
document.addEventListener('touchstart', safeTouchHandler, { passive: true });
|
||
|
||
let scrollAllowed = false;
|
||
const scrollHandler = () => {
|
||
if (scrollAllowed && !hasUserInteracted) markInteractionAndTryShow();
|
||
};
|
||
document.addEventListener('scroll', scrollHandler, { passive: true });
|
||
|
||
document.addEventListener('click', () => scrollAllowed = true, { once: true });
|
||
document.addEventListener('touchstart', () => scrollAllowed = true, { once: true });
|
||
|
||
function tryShowInstallPrompt() {
|
||
if (!deferredPrompt) {
|
||
console.log('Промпт ещё не готов');
|
||
return;
|
||
}
|
||
if (promptTimeout) return;
|
||
|
||
promptTimeout = setTimeout(() => {
|
||
console.log('Показываем install-промпт');
|
||
deferredPrompt.prompt();
|
||
|
||
deferredPrompt.userChoice.then((choiceResult) => {
|
||
if (choiceResult.outcome === 'accepted') {
|
||
console.log('PWA установлен!');
|
||
} else {
|
||
console.log('Установка отклонена');
|
||
}
|
||
deferredPrompt = null;
|
||
promptTimeout = null;
|
||
});
|
||
}, 1500);
|
||
}
|
||
</script>
|
||
<script>
|
||
(function() {
|
||
'use strict';
|
||
|
||
const root = document.getElementById('dd-modal-root') || document.body;
|
||
|
||
// Утилиты парсинга
|
||
function parseList(attr) {
|
||
if (!attr) return [];
|
||
try {
|
||
const fixed = attr.replace(/'/g, '"');
|
||
return JSON.parse(fixed);
|
||
} catch (e) {
|
||
return String(attr).split(',').map(s => s.trim()).filter(Boolean);
|
||
}
|
||
}
|
||
|
||
function parseSavedValue(el) {
|
||
const v = el.getAttribute('data-value');
|
||
if (!v) return [];
|
||
try {
|
||
return JSON.parse(v);
|
||
} catch (e) {
|
||
return v.includes(',')
|
||
? v.split(',').map(s => s.trim()).filter(Boolean)
|
||
: [v];
|
||
}
|
||
}
|
||
|
||
async function openModalForInput(triggerEl) {
|
||
const listRaw = triggerEl.getAttribute('data-list') || '[]';
|
||
const options = parseList(listRaw);
|
||
const isRadio = triggerEl.getAttribute('data-radio') === '1' ||
|
||
triggerEl.getAttribute('data-radio') === 'true';
|
||
const takeAll = triggerEl.getAttribute('data-takeall') === '1' ||
|
||
triggerEl.getAttribute('data-takeall') === 'true';
|
||
const allowCustom = triggerEl.getAttribute('data-custom') === '1' ||
|
||
triggerEl.getAttribute('data-custom') === 'true';
|
||
|
||
const title = triggerEl.getAttribute('data-title') || 'Выберите';
|
||
const saveLabel = triggerEl.getAttribute('data-label') || 'Сохранить';
|
||
const customPlaceholder = triggerEl.getAttribute('data-custom_placeholder') || 'Добавить...';
|
||
|
||
const initial = parseSavedValue(triggerEl);
|
||
|
||
return new Promise(resolve => {
|
||
const backdrop = document.createElement('div');
|
||
backdrop.className = 'dd-modal-backdrop';
|
||
|
||
const modal = document.createElement('div');
|
||
modal.className = 'dd-modal';
|
||
modal.innerHTML = `
|
||
<div class="dd-header">
|
||
<strong>${title}</strong>
|
||
<button class="dd-close" aria-label="Закрыть">×</button>
|
||
</div>
|
||
<div class="dd-custom-container" style="margin-top:8px;"></div>
|
||
<div class="dd-body"></div>
|
||
<div class="dd-actions" style="margin-top:12px;">
|
||
<button class="dd-save">${saveLabel}</button>
|
||
</div>
|
||
`;
|
||
|
||
backdrop.appendChild(modal);
|
||
root.appendChild(backdrop);
|
||
|
||
const body = modal.querySelector('.dd-body');
|
||
const customContainer = modal.querySelector('.dd-custom-container');
|
||
const saveBtn = modal.querySelector('.dd-save');
|
||
const closeBtn = modal.querySelector('.dd-close');
|
||
|
||
const groupName = 'dd-group-' + Date.now() + Math.random().toString(36).slice(2);
|
||
|
||
let currentOptions = options.slice();
|
||
let selected = initial.slice();
|
||
|
||
function renderOptions() {
|
||
body.innerHTML = '';
|
||
|
||
// "Выбрать все"
|
||
if (takeAll) {
|
||
const wrapper = createOption('Выбрать все', true, groupName, isRadio);
|
||
const takeAllInput = wrapper.querySelector('input');
|
||
takeAllInput.id = 'takeAll';
|
||
takeAllInput.checked = selected.length === 0 || selected.includes('Выбрать все');
|
||
body.appendChild(wrapper);
|
||
}
|
||
|
||
currentOptions.forEach(opt => {
|
||
const label = typeof opt === 'object' ? opt.name : String(opt);
|
||
const tooltip = typeof opt === 'object' ? opt.tooltip : null;
|
||
const count = typeof opt === 'object' ? opt.count : null;
|
||
|
||
|
||
const wrapper = createOption(label, false, groupName, isRadio, tooltip, count);
|
||
const input = wrapper.querySelector('input');
|
||
input.checked = selected.includes(label);
|
||
body.appendChild(wrapper);
|
||
});
|
||
}
|
||
|
||
function createOption(label, isTakeAll, name, isRadio, tooltip = null, count = null) {
|
||
const wrapper = document.createElement('label');
|
||
wrapper.className = 'dd-option';
|
||
|
||
const input = document.createElement('input');
|
||
input.type = isRadio ? 'radio' : 'checkbox';
|
||
input.name = name;
|
||
input.value = label;
|
||
input.className = 'offer-checkbox';
|
||
|
||
const span = document.createElement('span');
|
||
span.textContent = label;
|
||
|
||
const div = document.createElement('div');
|
||
div.className = 'some-class';
|
||
|
||
// count
|
||
if (count !== undefined && count !== null) {
|
||
const countNode = document.createElement('span');
|
||
countNode.textContent = count;
|
||
div.appendChild(countNode);
|
||
}
|
||
// tooltip
|
||
if (tooltip) {
|
||
const img = document.createElement('img');
|
||
img.className = 'tooltip-trigger';
|
||
img.dataset.tooltip = tooltip;
|
||
img.src = '/static/help.svg';
|
||
img.alt = 'Подсказка';
|
||
div.appendChild(img);
|
||
}
|
||
|
||
|
||
|
||
// куда-то добавляем div
|
||
span.appendChild(div);
|
||
|
||
|
||
|
||
|
||
|
||
wrapper.append(input, span);
|
||
return wrapper;
|
||
}
|
||
|
||
// Кастомные значения
|
||
if (allowCustom) {
|
||
customContainer.innerHTML = `
|
||
<div style="display:flex;gap:8px;align-items:center;flex-direction:column;">
|
||
<input type="text" class="dd-custom-input" placeholder="${customPlaceholder}"
|
||
style="flex:1;padding:6px;border:1px solid #ccc;width:100%;border-radius:4px;">
|
||
<button class="dd-custom-add" style="padding:6px 8px;width:100%;border-radius:5px;background:#5F1798;color:white;">
|
||
Добавить
|
||
</button>
|
||
</div>
|
||
`;
|
||
|
||
const customInput = customContainer.querySelector('.dd-custom-input');
|
||
const customAdd = customContainer.querySelector('.dd-custom-add');
|
||
|
||
const addCustom = (val) => {
|
||
val = String(val).trim();
|
||
if (!val) return;
|
||
|
||
if (!currentOptions.some(o => String(o) === val && typeof o !== 'object')) {
|
||
currentOptions.push(val);
|
||
triggerEl.setAttribute('data-list', JSON.stringify(currentOptions));
|
||
}
|
||
|
||
if (isRadio) {
|
||
selected = [val];
|
||
} else if (!selected.includes(val)) {
|
||
selected.push(val);
|
||
}
|
||
|
||
renderOptions();
|
||
updateCheckedState();
|
||
};
|
||
|
||
customAdd.addEventListener('click', () => {
|
||
addCustom(customInput.value);
|
||
customInput.value = '';
|
||
customInput.focus();
|
||
});
|
||
|
||
customInput.addEventListener('keydown', e => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
addCustom(customInput.value);
|
||
customInput.value = '';
|
||
}
|
||
});
|
||
}
|
||
|
||
function updateCheckedState() {
|
||
Array.from(body.querySelectorAll('input')).forEach(inp => {
|
||
inp.checked = selected.includes(inp.value);
|
||
});
|
||
}
|
||
|
||
function collectChecked() {
|
||
return Array.from(body.querySelectorAll('input:checked')).map(i => i.value);
|
||
}
|
||
|
||
function closeModal() {
|
||
const result = collectChecked();
|
||
triggerEl.setAttribute('data-value', isRadio ? (result[0] ?? '') : JSON.stringify(result));
|
||
|
||
if (triggerEl.type && triggerEl.type !== 'hidden') {
|
||
triggerEl.value = isRadio ? (result[0] ?? '') : result.join(', ');
|
||
}
|
||
|
||
backdrop.remove();
|
||
resolve(result);
|
||
}
|
||
|
||
// Обработчики закрытия
|
||
closeBtn.addEventListener('click', closeModal);
|
||
saveBtn.addEventListener('click', closeModal);
|
||
backdrop.addEventListener('click', e => {
|
||
if (e.target === backdrop) closeModal();
|
||
});
|
||
|
||
// Логика "Выбрать все"
|
||
function setupTakeAll() {
|
||
const takeAllInput = document.getElementById('takeAll');
|
||
if (!takeAllInput) return;
|
||
|
||
const allInputs = () => Array.from(body.querySelectorAll('input.offer-checkbox'));
|
||
|
||
takeAllInput.addEventListener('change', () => {
|
||
if (takeAllInput.checked) {
|
||
selected = [];
|
||
allInputs().forEach(inp => {
|
||
if (inp !== takeAllInput) inp.checked = false;
|
||
});
|
||
closeModal();
|
||
}
|
||
});
|
||
|
||
// Сброс "Выбрать все" при выборе других
|
||
body.addEventListener('change', e => {
|
||
const inp = e.target;
|
||
if (inp.type !== 'checkbox' || inp === takeAllInput) return;
|
||
|
||
if (inp.checked) {
|
||
takeAllInput.checked = false;
|
||
} else {
|
||
const anyChecked = allInputs().some(x => x !== takeAllInput && x.checked);
|
||
takeAllInput.checked = !anyChecked;
|
||
}
|
||
});
|
||
}
|
||
|
||
renderOptions();
|
||
setupTakeAll();
|
||
updateCheckedState();
|
||
});
|
||
}
|
||
|
||
// Главный обработчик
|
||
document.addEventListener('click', async function(e) {
|
||
const trigger = e.target.closest('[data-select="1"]');
|
||
if (!trigger) return;
|
||
|
||
const input = trigger.querySelector('input');
|
||
if (input) {
|
||
input.readOnly = true;
|
||
input.blur();
|
||
}
|
||
|
||
e.preventDefault();
|
||
|
||
const result = await openModalForInput(trigger);
|
||
if (result && result.length) {
|
||
trigger.dispatchEvent(new Event('change', { bubbles: true }));
|
||
}
|
||
}, true);
|
||
|
||
})();
|
||
</script>
|
||
<div id="dd-modal-root"></div>
|
||
|
||
<div id="modalToolTip">
|
||
<div class="modal-content">
|
||
<span id="closeToolTipBtn" class="close">×</span>
|
||
<p id="modalToolTipText" style="overflow: hidden;white-space: pre-line;"></p>
|
||
</div>
|
||
</div>
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const modal = document.getElementById('modalToolTip');
|
||
const modalText = document.getElementById('modalToolTipText');
|
||
const closeBtn = document.getElementById('closeToolTipBtn');
|
||
|
||
document.addEventListener('click', (event) => {
|
||
if (event.target.classList.contains('tooltip-trigger')) {
|
||
modalText.textContent = event.target.getAttribute('data-tooltip');
|
||
modal.style.display = 'flex';
|
||
}
|
||
});
|
||
|
||
closeBtn.onclick = () => {
|
||
modal.style.display = 'none';
|
||
};
|
||
|
||
window.onclick = (event) => {
|
||
if (event.target === modal) {
|
||
modal.style.display = 'none';
|
||
}
|
||
};
|
||
|
||
document.addEventListener('mouseenter', (event) => {
|
||
if (event.target.classList.contains('tooltip-trigger')) {
|
||
modalText.textContent = event.target.getAttribute('data-tooltip');
|
||
modal.style.display = 'block';
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|