15 мая 2023
274

от web-студии KONSULTANTE

Вы хотите повысить эффективность своего сайта? Разработка семантического ядра - важный этап в достижении этой цели. Но часто, даже опытным веб-мастерам, удается избежать нескольких распространенных ошибок, которые могут повлиять на позиции сайта в поисковых системах.

Веб-студия KONSULTANTE предлагает вам свои услуги для избежания этих ошибок и достижения оптимальных результатов. Наши специалисты имеют огромный опыт в создании и оптимизации сайтов с помощью правильной работы с семантическим ядром.

Повысьте видимость и посещаемость вашего сайта с помощью профессиональной разработки семантического ядра от KONSULTANTE. Свяжитесь с нами прямо сейчас и получите бесплатную консультацию.

План статьи

1. Что такое семантическое ядро и почему оно важно для сайта.

2. Основные ошибки при составлении семантического ядра.

3. Неправильное использование ключевых слов.

4. Отсутствие связи между страницами сайта.

5. Ошибки при выборе семантической группы ключевых слов.

6. Как правильно составить семантическое ядро для своего сайта.

7. Инструменты для составления семантического ядра.

8. Важность постоянного обновления и совершенствования семантического ядра.

9. Заключение.

Частые ошибки семантического ядра

Однако, при работе с семантическим ядром, многие веб-мастера допускают ряд ошибок, которые могут негативно сказаться на видимости и позициях сайта в поисковых системах.

  • Неполное или некачественное исследование ключевых слов. Чтобы правильно построить семантическое ядро, необходимо провести полное исследование ключевых слов, учитывая их релевантность, объем запросов, конкурентность и потенциал для вашего бизнеса. Используйте специализированные инструменты для ключевого слова, такие как Google Keyword Planner или Яндекс.Вордстат.
  • Отсутствие иерархической структуры. Семантическое ядро должно иметь четкую иерархическую структуру, состоящую из основных тем, подтем и конкретных запросов. Это поможет организовать информацию на сайте и оптимизировать его для поисковых систем.
  • Использование неподходящих ключевых слов. Используйте только те ключевые слова, которые действительно отражают тематику вашего сайта и бизнеса. Иначе, поисковые системы могут считать ваш контент неправильным или спамом.
  • Неактуализирование семантического ядра. Тематическая направленность вашего сайта может меняться со временем, поэтому важно периодически обновлять и актуализировать семантическое ядро, внося новые и удаляя устаревшие ключевые слова.
  • Неправильная внутренняя перелинковка. Важным аспектом семантического ядра является внутренняя перелинковка – создание ссылок между страницами вашего сайта. Это помогает поисковым системам понять структуру сайта и делает его более доступным для индексации.

Исправление этих частых ошибок поможет вам построить эффективное семантическое ядро, которое будет способствовать улучшению видимости и ранжирования вашего сайта в поисковых системах. Обратитесь к профессиональной web-студии KONSULTANTE, чтобы получить комплексный анализ и оптимизацию вашего семантического ядра.

Дефекты ядра семантики

1. Несоответствие ключевых слов: Одной из главных ошибок является неправильное использование ключевых слов. Если они не соответствуют контексту и содержанию страницы, то это может негативно сказаться на поисковой выдаче.

2. Недостаточность или избыток ключевых слов: Необходимо найти баланс в использовании ключевых слов. Их недостаточность может привести к низкому рейтингу страницы, а избыток может привести к обвинению в спаме.

3. Отсутствие семантической связи: Внутренние ссылки должны быть логичными и иметь семантическую связь с контентом страницы. Иначе, поисковые роботы не смогут правильно понять тематику страницы.

4. Ошибки при использовании тегов: Некорректное использование тегов , <meta>, <header>, <footer> и других может привести к неправильному отображению информации в поисковой выдаче.</p> <p>5. Нерелевантный контент: Предоставление контента, который не соответствует ожиданиям пользователей, может снизить позиции вашего сайта в поисковой выдаче.</p> <p>Избегая данных дефектов ядра семантики, вы сможете повысить рейтинг и видимость вашего сайта, привлечь больше посетителей и увеличить продажи.</p> <h3>Проблема неправильной структуры</h3> <p>Часто владельцы веб-ресурсов не задумываются о структурной организации своего контента и просто добавляют отдельные ключевые слова без учета их связи и взаимодействия друг с другом. Такой подход не только затрудняет понимание контента поисковыми системами, но и создает путаницу у посетителей.</p> <p>Проблема неправильной структуры семантического ядра проявляется в недостатке логической организации ключевых слов и фраз. Отсутствие иерархии и связей между ключевыми словами делает сложным понимание целостной концепции сайта и его тематики.</p> <p>Семантическое ядро веб-ресурса должно быть организовано в виде дерева, где на верхнем уровне находятся общие понятия, а на последующих уровнях представлены более узкие тематики и ключевые слова.</p> <p>Правильно организованное семантическое ядро облегчает поисковым системам и пользователям понимание структуры и контента сайта, а также повышает вероятность попадания в поисковые выдачи по целевым запросам.</p> <p>Нажмите кнопку ниже, чтобы узнать, как устранить проблемы с неправильной структурой семантического ядра и повысить эффективность вашей семантической оптимизации.</p> <h3>Ошибки при выборе ключевых слов</h3> <p>При создании семантического ядра, многие веб-мастера допускают ошибки при выборе ключевых слов. Это может серьезно сказаться на ранжировании и видимости сайта в поисковых системах. Важно избегать следующих ошибок:</p> <table> <tbody> <tr> <td> <p>1.</p> </td> <td> <p>Использование слишком общих ключевых слов</p> </td> </tr> <tr> <td> <p>2.</p> </td> <td> <p>Игнорирование семантического контекста</p> </td> </tr> <tr> <td> <p>3.</p> </td> <td> <p>Недостаточное исследование рынка и аудитории</p> </td> </tr> <tr> <td> <p>4.</p> </td> <td> <p>Неиспользование долгих и коротких ключевых фраз</p> </td> </tr> <tr> <td> <p>5.</p> </td> <td> <p>Применение ключевых слов без смысловых связей</p> </td> </tr> <tr> <td> <p>6.</p> </td> <td> <p>Невнимательность к словам-исключениям</p> </td> </tr> </tbody> </table> <p>Избегайте этих ошибок при выборе ключевых слов в вашем семантическом ядре, чтобы повысить эффективность и результативность ваших SEO-усилий. Доверьтесь профессионалам от web-студии KONSULTANTE, и мы поможем вам в создании оптимального семантического ядра для вашего сайта!</p> <h3>Проблемы с описанием тегов</h3> <p>Один из распространенных вопросов, связанных с формированием семантического ядра, связан с неправильным или недостаточным описанием тегов.</p> <p>Когда создается сайт или веб-приложение, каждая страница должна содержать специальные теги, которые помогают поисковым системам понять структуру и смысл содержимого. Однако, при выборе и описании тегов, часто допускаются ошибки, которые могут негативно сказаться на поисковой оптимизации.</p> <p>Проблема №1: Неправильный выбор тегов. Когда выбираются теги, неопытные веб-разработчики или копирайтеры могут использовать неправильные или устаревшие теги, которые не отражают смысловую нагрузку контента.</p> <p>Проблема №2: Недостаточное описание тегов. Для каждого тега необходимо написать уникальное и информативное описание, которое точно передает смысл содержимого. Однако, часто описание тегов оказывается слишком общим или неточным, что делает его бесполезным для поисковых систем.</p> <p>Проблема №3: Отсутствие тегов у изображений и мультимедийного контента. Многие веб-разработчики забывают о необходимости добавления тегов к изображениям и другому мультимедийному контенту. Это может привести к тому, что поисковые системы не смогут определить, что именно изображено на картинке или в видео, и не будут его учитывать при поисковой выдаче.</p> <p>Проблема №4: Неправильное использование атрибутов. Каждый тег имеет определенные атрибуты, которые должны быть использованы правильно. Однако, некорректное или случайное использование атрибутов может привести к некорректному отображению контента или ошибкам в работе веб-страницы.</p> <p>Решение этих проблем – тщательное изучение семантики тегов и их правильное использование при создании веб-страниц. Консультирование с опытными специалистами web-студии KONSULTANTE поможет избежать этих ошибок и сделает ваш сайт максимально оптимизированным для поисковых систем.</p> <p>Объедините услугу по настройке семантического ядра с услугой профессиональной поисковой оптимизации, и ваш сайт сможет занять лидирующие позиции в результатах поиска.</p> <table> <tr> <td>Название </td> <td>Описание </td> </tr> <tr> <td>Оптимизация SEO-тегов</td> <td>Помогаем настроить теги страниц для максимальной поисковой оптимизации</td> </tr> <tr> <td>Аудит семантического ядра</td> <td>Проверяем и оптимизируем семантическое ядро вашего сайта</td> </tr> <tr> <td>Консультации по SEO</td> <td>Оказываем профессиональную помощь и советы по поисковой оптимизации</td> </tr> </table> <h2>Ошибки в ядре смысла</h2> <p><strong>1. Неверный выбор ключевых слов</strong></p> <p>Одним из главных факторов успешного семантического ядра является правильный выбор ключевых слов. Ошибкой является использование общих и популярных слов, которые не могут четко определить смысл и цель страницы. Необходимо провести исследование и выбрать ключевые слова, которые ясно характеризуют контент.</p> <p><strong>2. Перекрытие ключевых слов</strong></p> <p>Еще одна распространенная ошибка – перекрытие ключевых слов между различными страницами сайта. Это может привести к конфликтам и неразберихе для поисковых систем. Для каждой страницы следует определить уникальные ключевые слова и распределить их логически между страницами.</p> <p><strong>3. Некачественный контент</strong></p> <p>Ошибкой является использование некачественного и неинформативного контента на страницах сайта. Пользователи и поисковые системы ожидают видеть полезную и актуальную информацию. Обратите внимание на структуру контента, его читаемость и уникальность. Не забывайте использовать ключевые слова в тексте, но делайте это естественно, без переуступки качеству.</p> <p><strong>4. Использование некорректных метатегов</strong></p> <p>Метатеги являются важными элементами семантического ядра и играют роль в описании контента страницы. Ошибкой является использование некорректных или пустых метатегов. Каждая страница должна иметь уникальные и информативные метатеги, которые четко определяют ее смысл и содержание.</p> <p><strong>5. Неправильная структура сайта</strong></p> <p>Структура сайта также является важным аспектом формирования семантического ядра. Ошибка включает в себя неправильное разделение контента по страницам и отсутствие логической связи между ними. Четко структурируйте сайт и убедитесь, что каждая страница имеет свою уникальность и логическую цель.</p> <p><strong>6. Отсутствие оптимизации для мобильных устройств</strong></p> <p>Сегодня большая часть пользователей обращается к сайтам через мобильные устройства. Ошибка состоит в отсутствии оптимизации для мобильных устройств, что может негативно сказаться на позициях сайта в поисковой выдаче и на удобстве его использования. Убедитесь, что ваш сайт адаптирован под мобильные устройства и имеет удобный интерфейс.</p></div><div class="main-button-wrap"><a class="big button-def elips main-color " href = '/' >Перейти на главную</a></div></div><!--'start_frame_cache_area'--><script> $(document).ready(function($) { $(".section-menu-id-35").addClass('selected'); }); </script><!--'end_frame_cache_area'--></div><div class="wr-block-comments"><div class="wr-content-title"><div class="content-title">Комментарии</div><div class="line"></div></div><div class="block-comments block-comments-js" data-element-id="5390"><div class="comments-set comments-set-js"></div><div class="loading-block"></div></div></div><form class="form-comments"><input type="hidden" name="ELEMENT_ID" value="5390"><input type="hidden" name="IBLOCK_ID" value="19"><div class="main-inuts"><div class="col-12"><div class="input"><div class="bg"></div><span class="desc">Name</span><input class='focus-anim input-name' name="name" type="text"></div></div><div class="col-12"><div class="input"><div class="bg"></div><span class="desc">Email</span><input class='focus-anim input-name' name="email" type="email"></div></div><div class="col-12"><div class="input"><div class="bg"></div><span class="desc">Phone</span><input class='focus-anim input-name' name="phone" type="tel"></div></div></div><div class="question-js"><div class="row row-section"><div class="col-md-6 col-12"><div class="input-simple left-col "><span class="desc">Ваше имя</span><input type="text" class="focus-anim bord-1" name="USER_NAME" value=""></div></div></div><div class="textarea-simple"><div class="bg"></div><span class="desc">Оставить комментарий</span><textarea class="focus-anim text-require bord-1" name="TEXT"></textarea></div><div class="wt-button"><div class="loader-simple d-none"><div class="xLoader form-preload"><div class="audio-wave"><span></span><span></span><span></span><span></span><span></span></div></div></div><button class="elips button-def main-color active btn-submit-comments-js" type="button">Отправить</button></div></div><div class="thank-js d-none"></div></form></div><div class="col-lg-3 hidden-md hidden-sm hidden-xs"><div class="menu-navigation" id="navigation"><div class="menu-navigation-inner"><input type="hidden" id="detail-page" name="detail-page" value="19"><!--'start_frame_cache_ajax'--><ul class="new-detail row no-gutters"><li class="col-12 back"><a href="/blog/raznoe/"><span class="text">К списку раздела статей</span></a></li></ul><!--'end_frame_cache_ajax'--><!--'start_frame_cache_7DqYyc'--><div class="other-news"><div class="item lazyload" data-src="/upload/dev2fun.imagecompress/webp/resize_cache/iblock/81a/q4fi8og3w6p7zdom9eao27uov3v8bj0q/600_600_140cd750bba9870f18aada2478b24840a/ofitsialnyysaytpriemipoluchenierezultatovanalizovvsenaodnomresurse.webp" ><div class="frameshadow"></div><div class="new-dark-shadow"></div><div class="cont"><div class="name bold">Официальный сайт прием и получение результатов анализов - все на одном ресурсе!</div></div><a class="wrap-link" href="/blog/detail/ofitsialnyy-sayt-priem-i-poluchenie-rezultatov-analizov-vse-na-odnom-resurse/"></a></div><div class="item lazyload" data-src="/upload/dev2fun.imagecompress/webp/resize_cache/iblock/278/qzzbuovezj4gw612dlvu04gwoiz9jk7k/600_600_140cd750bba9870f18aada2478b24840a/tekhnikiuluchsheniyaispolzovaniyaotwebstudiikonsultantepovyshenieeffektivnostiiudobstva.webp" ><div class="frameshadow"></div><div class="new-dark-shadow"></div><div class="cont"><div class="name bold">Техники улучшения использования от web-студии KONSULTANTE: повышение эффективности и удобства</div></div><a class="wrap-link" href="/blog/detail/tekhniki-uluchsheniya-ispolzovaniya-ot-web-studii-konsultante-povyshenie-effektivnosti-i-udobstva/"></a></div></div><!--'end_frame_cache_7DqYyc'--></div></div></div></div></div></div></div><div class="catalog-stories-ajax" data-count="4"></div><!-- dlya nastroek saita --><style> body{ background-attachment: scroll; background-repeat: no-repeat; background-position: top center; background-color: transparent; } </style><footer class=" txt-color-default tone-dark lazyload" data-src="/upload/dev2fun.imagecompress/webp/resize_cache/phoenix/af1/0lpxbt22e5wxx527tfnwazcac2ka2a40/1600_1200_1/Overlay-_17_.webp" ><div class="shadow-tone"></div><div class="container"><div class="container-top"><div class="row"><div class="col-lg-3 col-sm-6 col-12 column-1"><img class='logotype lazyload hidden-md hidden-sm hidden-xs' data-src='/upload/phoenix/0ca/l3itxab2mgpxp5dk1zuq4v5t36e035xy/konsultante.svg' /><img class='logotype lazyload visible-md visible-sm visible-xs' data-src='/upload/dev2fun.imagecompress/webp/resize_cache/phoenix/07d/dc88n26finh9srfx6oqu13txy1z895vv/500_200_1/konsultante.webp' /><!--'start_frame_cache_footer-contacts'--><!--'end_frame_cache_footer-contacts'--><div class="button-wrap"><a class="button-def main-color elips call-modal callform" data-header="Подвал сайта" data-call-modal="form171">Заказать звонок</a></div></div><div class="col-lg-3 col-sm-6 col-12 column-2"><div class="footer-description-item">Наша WEB-Студия оказывает полный спектр услуг от создания сайтов до маркетинга в интернете.Консультируем, создаем план, создаем сайт, продвигаем сайт в поисковых системах, рекламируем во всех возможных сетях учитывая Ваши пожелания и отталкиваясь от нашего опыта. <br><p style="text-align: center;"><iframe src="https://yandex.ru/sprav/widget/rating-badge/197486751403?type=rating" width="150" height="50" frameborder="0"></iframe></p></div><div class="political"><div class="agreement-item"><a class="call-modal callagreement" data-call-modal="agreement123"><span class="bord-bot white">Политика в отношении обработки персональных данных</span></a></div><div class="agreement-item"><a class="call-modal callagreement" data-call-modal="agreement124"><span class="bord-bot white">QR-Код для оплаты</span></a></div></div><div class="copytright-item d-none d-md-block"><a class="copyright"><table><tr></tr></table></a></div></div><div class="col-lg-3 col-sm-6 col-12 column-3 parent-tool-settings hidden-xs"><div class="menu-items"><div class="menu-item"><a href='/main/' class='section-menu-id-133 hover ' >На главную</a></div><div class="menu-item"><a href='/catalog/' class='section-menu-id-136 hover ' >Каталог товаров</a></div><div class="menu-item"><a href='/offers/' class='section-menu-id-138 hover ' >Акции и спецпредложения</a></div><div class="menu-item"><a href='/services/' class='section-menu-id-141 hover ' >Наши услуги</a></div><div class="menu-item"><a href='/contact/' class='section-menu-id-142 hover ' >Контакты</a></div></div><!--'start_frame_cache_menu-footer'--><script> $(document).ready(function($) { }); </script><!--'end_frame_cache_menu-footer'--></div><div class="col-lg-3 col-sm-6 col-12 column-4"><div class="banner-items"><div class="banner-item"><a href="https://40-e.ru/partnerskaya-programma/" target="_blank"><img class="img-fluid lazyload" data-src="/upload/dev2fun.imagecompress/webp/resize_cache/phoenix/880/5mm54c2kf18n1nx140norzz3lx4bh1kb/350_130_1/Brown-Organic-Photo-Family-Photo-Collage-_4_.webp" alt="" /></a></div><div class="banner-item"><a href="https://40-e.ru/partnerskaya-programma/" target="_blank"><img class="img-fluid lazyload" data-src="/upload/dev2fun.imagecompress/webp/phoenix/01d/0tqh3pq1kjlbb302pjb8pozk2isfbdhu/Brown_Organic_Photo_Family_Photo_Collage_5_.webp" alt="" /></a></div></div></div></div></div><div class="container-bottom row align-items-center"><div class="text-item col-lg-6 col-12"> ©2006-2023 WEB-Студия KONSULTANTE </div><div class="icon-items col-lg-6 col-12"><img class="lazyload" data-src="/upload/dev2fun.imagecompress/webp/phoenix/5da/ziv2sxzgg6zkpahdeadh6bbh13fpa7ne/pay.webp" alt="" title=""/></div></div></div></footer></div><div class="phx-modal-dialog" data-target = "auth-modal-dialog"><div class="dialog-content"><a class="close-phx-modal-dialog" data-target = "auth-modal-dialog"></a><div class="auth-dialog-form with-pic"><div class="row no-gutters"><div class="col-md-7 hidden-sm hidden-xs picture" style="background-image: url(/upload/dev2fun.imagecompress/webp/phoenix/134/pj1bo5iunyk4r8iia2dj1w5yvalwzajz/personal.webp);"></div><div class="col-md-5 col-12"><form class="form auth" action="#"><div class="title-form main1"> Личный кабинет </div><div class="subtitle-form">Вам будет доступна история заказов, управление рассылками, свои цены и скидки для постоянных клиентов и прочее.</div><div class="inputs-block"><div class="input"><div class="bg"></div><span class="desc">Ваш логин</span><input class='focus-anim require' name="auth-login" type="text" value="" /></div><div class="input"><div class="bg"></div><span class="desc">Ваш пароль</span><input class='focus-anim require' name="auth-password" type="password" /></div><div class="errors"></div><div class="input-btn"><div class="load"><div class="xLoader form-preload"><div class="audio-wave"><span></span><span></span><span></span><span></span><span></span></div></div></div><button class="button-def main-color big active elips auth-submit" name="form-submit" type="button">Войти в личный кабинет</button></div></div><div class="input txt-center"><a class="forgot" href="/personal/forgotpasswd/"><span class="bord-bot">Забыли пароль?</span></a></div></form><div class="register row no-margin"><div class="col-12"><a href="/personal/register/"><span class="bord-bot">Создать личный кабинет</span></a></div></div></div></div></div></div></div><!--'start_frame_cache_set-area'--><!--'end_frame_cache_set-area'--><a href="#body" class="up scroll"></a><div class="popup-slider" id="sliderPopup" data-current-image=''><div class="gallery-container wrapper-picture"><div class="wrapper-big-picture"></div><div class="controls-pictures"></div></div><a class="close-popup-slider-style" onclick = "destroyPopupGallery();"></a><div class="popup-slider-nav"><div class="nav-item action_prev"></div><div class="nav-item action_next"></div></div></div><div class="modalArea shadow-modal-wind-contact"><div class="shadow-modal"></div><div class="phoenix-modal window-modal"><div class="phoenix-modal-dialog"><div class="dialog-content"><a class="close-modal wind-close"></a><div class="content-in"><div class="list-contacts-modal"><!--'start_frame_cache_popup-contacts'--> <table><tr><td><div class="button-wrap"><a class="button-def main-color d-block elips call-modal callform" data-from-open-modal='open-menu' data-header="Подвал сайта" data-call-modal="form171">Заказать звонок</a></div></td></tr><tr><td><div class="desc">Удаленно можем создавать проекты любой сложности. Но предпочитаем встречаться перед началом работ.</div></td></tr><tr><td><div class='soc-group'><a rel='noindex, nofollow' target='_blank' href='https://vk.com/web_seo_kaluga' class='soc_ic soc_vk'><i class='concept-vkontakte'></i></a><a rel='noindex, nofollow' target='_blank' href='https://t.me/Dginyvolshebnik' class='soc_ic soc_telegram'><i class='concept-paper-plane'></i></a><div class='clearfix'></div></div> </td></tr></table><!--'end_frame_cache_popup-contacts'--> </div></div></div></div></div></div><script type="text/javascript"> $(document).ready( function() { $("a.phoenix-sets-list-item.seo span.status-seo").addClass("seo-notbad"); } ); </script><script type="text/javascript"> initPhoneMask(); </script><div class="loading"></div><div class="loading-top-right circleG-area"><div class="circleG circleG_1"></div><div class="circleG circleG_2"></div><div class="circleG circleG_3"></div></div><script> $(window).on("load", function() { $("body").append("<link href=\"https://fonts.googleapis.com/css?family=PT+Sans+Caption&display=swap&subset=latin-ext\" type=\"text/css\" rel=\"stylesheet\">"); }); </script><!--'start_frame_cache_css-js-area'--> <!--'end_frame_cache_css-js-area'--><style type="text/css"> /*ширина контента*/ .container { max-width: 90%; } /* банер сбоку */ div.concept-banner.cb-close-on div.cb-close { width: 50px !important; height: 50px !important; } div.concept-banner.cb-side td.cb-text { padding: 1px !important; } /* анимация тел подвал footer div.phone div.phone-value { background: linear-gradient(90deg, #ffff00 10%, #999900 30%, #e3cced 60%, #ffffff 101%) repeat-x 0 0; -webkit-background-clip: text; -webkit-text-fill-color: transparent; animation: gusenitsa 70s linear infinite; } @keyframes gusenitsa { 0% { background-position: 0 0; } 100% { background-position: 50em 0; /* Измените это значение на своё усмотрение */ }*/ /* блок ссылок */ .achievments-list__container { display: grid; gap: 20px; } .simple-card-v3 { text-decoration: none; color: inherit; text-align: center; } .simple-card-v3 img { max-width: 100%; height: auto; } .h5-mob { margin-top: 10px; font-size: 16px; } </style></body></html> <script> $(document).ready(function() { $("body").append("<input type=\"hidden\" class=\"cbanner_site_id\" name=\"cbanner_site_id\" value=\"s1\"><script async type='text/javascript' src='/bitrix/js/concept.banner/scripts.js'><\/script><link rel='stylesheet' href='/bitrix/css/concept.banner/template_styles.css'>"); }); </script><style> .ai-widget-backdrop { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 9998; opacity: 0; transition: opacity 0.3s; } .ai-widget-backdrop.active { display: block; opacity: 1; } .ai-main-btn { background-color: #6366F1 !important; border-radius: 50% !important; animation: ai-pulse 2s infinite;} @keyframes ai-pulse { 0% { box-shadow: 0 0 0 0 #6366F1aa; } 70% { box-shadow: 0 0 0 15px transparent; } 100% { box-shadow: 0 0 0 0 transparent; } } </style><div id="ai-widget-wrapper-ai-project237179" class="ai-widget-wrapper"><!-- Backdrop --><div class="ai-widget-backdrop" onclick="document.getElementById('ai-widget-modal').classList.remove('active')"></div><div class="ai-contact-widget"><style> /* CRITICAL RULE: CSS UNIQUENESS */ /* Все CSS классы и ID должны быть уникальными для предотвращения конфликтов. Используем префикс "ai-contact-" */ /* ИНТЕГРАЦИЯ FONT AWESOME: Подключаем библиотеку иконок Font Awesome 6 (Free) */ /* Font Awesome CSS должен быть подключен до использования его классов, чтобы стили корректно применялись */ @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css'); /* Общие стили для иконок (Font Awesome) */ .ai-contact-icon { /* Используем flex для центрирования иконки внутри родительского элемента. */ display: flex; justify-content: center; align-items: center; color: currentColor; /* Цвет иконки будет наследоваться от родителя (например, white) */ } /* Анимации */ /* Анимация волны для основной кнопки */ @keyframes ai-contact-pulse-wave { 0% { transform: translate(-50%, -50%) scale(1); opacity: 1; } 100% { transform: translate(-50%, -50%) scale(1.5); /* Расширяется до 150% */ opacity: 0; } } /* Анимация раскрытия элементов меню (для групп кнопок + лейблов) */ @keyframes ai-contact-expandUp { from { opacity: 0; transform: translateY(20px) scale(0.8); } to { opacity: 1; transform: translateY(0) scale(1); } } /* Анимации переворота для иконки основной кнопки */ /* Анимация "скрытия" иконки */ @keyframes ai-contact-icon-flip-out { 0% { transform: translate(-50%, -50%) rotateY(0deg); opacity: 1; } 100% { transform: translate(-50%, -50%) rotateY(90deg); opacity: 0; } } /* Анимация "появления" новой иконки */ @keyframes ai-contact-icon-flip-in { 0% { transform: translate(-50%, -50%) rotateY(-90deg); opacity: 0; } 100% { transform: translate(-50%, -50%) rotateY(0deg); opacity: 1; } } /* Основной контейнер виджета */ .ai-contact-widget { position: fixed; right: 20px; bottom: 20px; z-index: 99999; display: flex; flex-direction: column; /* Элементы располагаются вертикально */ align-items: flex-end; /* Выравнивание по правому краю, чтобы лейблы были слева от кнопок */ gap: 10px; /* Расстояние между меню и основной кнопкой */ width: max-content; /* Ширина по содержимому */ max-width: 90%; /* CRITICAL LAYOUT REQUIREMENT: Виджет не должен превышать 90% ширины viewport */ } /* Меню с группами кнопок связи */ .ai-contact-menu { display: flex; flex-direction: column-reverse; /* Раскрытие снизу вверх */ gap: 8px; /* Расстояние между группами элементов меню */ opacity: 0; visibility: hidden; pointer-events: none; /* Отключаем события мыши, когда меню скрыто */ transform: scale(0); transform-origin: bottom right; /* Анимация масштабирования от правого нижнего угла */ transition: opacity 0.3s ease, transform 0.3s ease, visibility 0.3s ease; } .ai-contact-menu.ai-contact-is-open { opacity: 1; visibility: visible; pointer-events: auto; transform: scale(1); } /* Группа, содержащая лейбл и кнопку/ссылку */ .ai-contact-menu-item-group { display: flex; align-items: center; /* Центрирование лейбла и кнопки по вертикали */ gap: 10px; /* Расстояние между лейблом и кнопкой */ justify-content: flex-end; /* Располагает лейбл слева от кнопки */ } /* Лейблы рядом с кнопками */ .ai-contact-label { color: white; /* background-color будет задан соответствующим классом цвета (ai-contact-item-darkblue и т.д.) */ padding: 8px 12px; border-radius: 12px; /* Закругленные углы, как у кнопок */ font-size: 15px; white-space: nowrap; /* Предотвращаем перенос текста */ overflow: hidden; /* Скрываем содержимое при max-width: 0 */ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15); /* Тень как у кнопок */ cursor: pointer; /* Делаем лейбл кликабельным */ /* Начальное скрытое состояние для анимации появления */ opacity: 0; max-width: 0; /* Для анимации ширины */ transform: translateX(20px); /* Для анимации сдвига */ padding-left: 0; /* Начальный padding для анимации */ padding-right: 0; /* Начальный padding для анимации */ /* Свойства для плавной анимации */ transition: opacity 0.3s ease, max-width 0.3s ease, transform 0.3s ease, padding 0.3s ease, background-color 0.2s ease, box-shadow 0.2s ease; } /* Добавляем стили при наведении на лейбл, чтобы он выглядел интерактивным, как кнопка */ .ai-contact-label:hover { box-shadow: 0 4px 15px rgba(0, 0, 0, 0.25); transform: translateX(0) scale(1.05); /* Легкое увеличение при наведении */ } /* Состояние лейбла при открытом меню */ .ai-contact-menu.ai-contact-is-open .ai-contact-menu-item-group .ai-contact-label { opacity: 1; max-width: 200px; /* Максимальная ширина для лейбла */ transform: translateX(0); /* Перемещаем на оригинальную позицию */ padding-left: 12px; /* Восстанавливаем padding */ padding-right: 12px; /* Восстанавливаем padding */ /* background-color будет применен от классов типа .ai-contact-item-darkblue */ } /* Основная кнопка переключения */ .ai-contact-main-toggle-btn { position: relative; width: 60px; height: 60px; border-radius: 16px; /* Закругленные углы */ border: none; cursor: pointer; display: flex; justify-content: center; align-items: center; color: white; /* Цвет иконки Font Awesome */ font-size: 28px; /* Размер иконки по умолчанию */ transition: background 0.3s ease, box-shadow 0.3s ease, transform 0.2s ease, border-radius 0.3s ease; box-shadow: 0 4px 20px rgba(139, 92, 246, 0.4); /* Тень, как в ТЗ */ background: radial-gradient(circle, #8B5CF6 0%, #3B82F6 100%); /* Градиент по умолчанию */ outline: none; /* Убираем стандартный outline */ /* Добавляем стили для 3D-трансформаций и скрытия контента при перевороте */ transform-style: preserve-3d; overflow: hidden; } /* Единый элемент для отображения меняющихся иконок */ .ai-contact-main-toggle-btn .ai-contact-dynamic-icon { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) rotateY(0deg); /* Начальное состояние */ font-size: 28px; /* Размер Font Awesome иконки */ transition: transform 0.4s ease-out, opacity 0.4s ease-out; /* Плавный переход для вращения и прозрачности */ backface-visibility: hidden; /* Предотвращает обратную сторону элемента */ } /* Иконка закрытия (крестик), появляется при открытом меню */ .ai-contact-main-toggle-btn .ai-contact-close-icon { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0); /* Скрыто по умолчанию */ font-size: 28px; transition: transform 0.3s ease, opacity 0.3s ease; opacity: 0; backface-visibility: hidden; /* Предотвращает обратную сторону элемента */ } /* Анимация "скрытия" текущей динамической иконки перед сменой (начало переворота) */ .ai-contact-main-toggle-btn.ai-contact-is-rotating .ai-contact-dynamic-icon { animation: ai-contact-icon-flip-out 0.2s ease-in forwards; } /* Анимация "появления" новой динамической иконки после смены (конец переворота) */ .ai-contact-main-toggle-btn.ai-contact-flip-in .ai-contact-dynamic-icon { animation: ai-contact-icon-flip-in 0.2s ease-out forwards; } /* Мерцающий эффект для основной кнопки */ .ai-contact-main-toggle-btn::before { content: ''; position: absolute; top: 50%; left: 50%; width: 100%; height: 100%; background: radial-gradient(circle, rgba(139, 92, 246, 0.8) 0%, rgba(59, 130, 246, 0) 100%); border-radius: inherit; /* Используем тот же border-radius */ transform: translate(-50%, -50%) scale(1); opacity: 0; animation: ai-contact-pulse-wave 2s infinite cubic-bezier(0.25, 0.46, 0.45, 0.94); z-index: -1; /* Под кнопкой */ pointer-events: none; /* Не блокируем события мыши */ } /* При открытом меню - меняем фон и иконку основной кнопки, отключаем пульсацию */ .ai-contact-main-toggle-btn.ai-contact-is-open { background: #FF0000; /* Красный фон для кнопки закрытия */ box-shadow: 0 4px 15px rgba(255, 0, 0, 0.5); /* Отключаем все кастомные theme-классы, когда меню открыто */ } /* Скрываем динамическую иконку, когда меню открыто */ .ai-contact-main-toggle-btn.ai-contact-is-open .ai-contact-dynamic-icon { opacity: 0; transform: translate(-50%, -50%) scale(0); /* Скрываем ее, используя масштабирование */ } /* Показываем иконку закрытия, когда меню открыто */ .ai-contact-main-toggle-btn.ai-contact-is-open .ai-contact-close-icon { opacity: 1; transform: translate(-50%, -50%) scale(1); /* Показываем ее */ } /* Отключаем пульсацию, когда кнопка вращается или открыта */ .ai-contact-main-toggle-btn.ai-contact-is-rotating::before, .ai-contact-main-toggle-btn.ai-contact-is-open::before { animation: none !important; opacity: 0 !important; } /* Цвета для основной кнопки, соответствующие элементам меню, применяются к самой кнопке */ .ai-contact-main-toggle-btn.ai-contact-theme-default { background: radial-gradient(circle, #8B5CF6 0%, #3B82F6 100%); box-shadow: 0 4px 20px rgba(139, 92, 246, 0.4); } .ai-contact-main-toggle-btn.ai-contact-theme-darkblue { background-color: #191970; box-shadow: 0 4px 20px rgba(25, 25, 112, 0.4); } .ai-contact-main-toggle-btn.ai-contact-theme-telegram { background-color: #0088CC; box-shadow: 0 4px 20px rgba(0, 136, 204, 0.4); } .ai-contact-main-toggle-btn.ai-contact-theme-whatsapp { background-color: #25D366; box-shadow: 0 4px 20px rgba(37, 211, 102, 0.4); } .ai-contact-main-toggle-btn.ai-contact-theme-vk { background-color: #4C75A3; box-shadow: 0 4px 20px rgba(76, 117, 163, 0.4); } /* Элементы меню (только иконки) */ .ai-contact-item { display: flex; justify-content: center; align-items: center; width: 50px; height: 50px; border-radius: 12px; /* Закругленные углы */ color: white; /* Цвет иконок */ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15); transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease; outline: none; text-decoration: none; /* Убираем подчеркивание для ссылок */ flex-shrink: 0; /* Предотвращаем сжатие кнопки при маленькой ширине */ } .ai-contact-item:hover, .ai-contact-item:focus { transform: scale(1.1); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.25); } /* Цвета кнопок меню и лейблов в соответствии с мессенджерами/назначением */ .ai-contact-item-darkblue { background-color: #191970; } /* Телефон, Email (Напишите нам) */ .ai-contact-item-telegram { background-color: #0088CC; } /* Telegram */ .ai-contact-item-whatsapp { background-color: #25D366; } /* WhatsApp */ .ai-contact-item-vk { background-color: #4C75A3; } /* VK */ /* Иконки в элементах меню (применяется к Font Awesome иконкам) */ .ai-contact-item .ai-contact-icon { font-size: 26px; /* Размер Font Awesome иконки */ } /* Анимация появления для каждой группы элементов меню */ .ai-contact-menu.ai-contact-is-open .ai-contact-menu-item-group { animation: ai-contact-expandUp 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; /* Задержка применяется через JS для staggered эффекта */ } /* Стили для модального окна формы обратной связи (используются как базовые для QR модала) */ .ai-contact-modal-form { display: none; /* Скрыто по умолчанию */ position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.6); /* Полупрозрачный фон */ justify-content: center; align-items: center; z-index: 100000; /* Выше виджета */ opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s ease; } .ai-contact-modal-form.ai-contact-modal-is-open { display: flex; /* Показываем модальное окно */ opacity: 1; visibility: visible; } .ai-contact-modal-content { background-color: #ffffff; padding: 30px; border-radius: 12px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); width: 90%; max-width: 500px; /* Максимальная ширина формы */ position: relative; transform: translateY(20px); opacity: 0; transition: transform 0.3s ease, opacity 0.3s ease; } .ai-contact-modal-form.ai-contact-modal-is-open .ai-contact-modal-content { transform: translateY(0); opacity: 1; } .ai-contact-modal-close { position: absolute; top: 15px; right: 15px; background: none; border: none; font-size: 28px; cursor: pointer; color: #666; line-height: 1; transition: color 0.2s ease; } .ai-contact-modal-close:hover { color: #333; } .ai-contact-modal-content h3 { margin-top: 0; margin-bottom: 20px; color: #333; text-align: center; font-size: 24px; } .ai-contact-form-group { margin-bottom: 18px; } .ai-contact-form-group label { display: block; margin-bottom: 8px; font-weight: 500; color: #555; font-size: 15px; } .ai-contact-form-group input[type="text"], .ai-contact-form-group input[type="email"], .ai-contact-form-group input[type="tel"], /* Добавлен тип tel для стилизации поля телефона */ .ai-contact-form-group textarea, .ai-contact-form-group input[type="file"] { /* Добавлен input[type="file"] для стилизации поля загрузки файла */ width: calc(100% - 24px); /* Учитываем padding */ padding: 12px; border: 1px solid #ccc; border-radius: 8px; font-size: 16px; color: #333; transition: border-color 0.2s ease, box-shadow 0.2s ease; } .ai-contact-form-group input[type="text"]:focus, .ai-contact-form-group input[type="email"]:focus, .ai-contact-form-group input[type="tel"]:focus, /* Добавлен тип tel для фокуса */ .ai-contact-form-group textarea:focus, .ai-contact-form-group input[type="file"]:focus { /* Добавлен input[type="file"] для фокуса */ border-color: #8B5CF6; box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.2); outline: none; } /* Дополнительные стили для поля загрузки файла, чтобы оно выглядело аккуратнее */ .ai-contact-form-group input[type="file"] { border: 1px solid #ccc; background-color: #f9f9f9; cursor: pointer; height: auto; /* Позволяет содержимому файла определять высоту */ } .ai-contact-form-group input[type="file"]::-webkit-file-upload-button { visibility: hidden; /* Скрываем стандартную кнопку Chrome */ } .ai-contact-form-group input[type="file"]::before { content: 'Выбрать файл'; /* Кастомный текст кнопки */ display: inline-block; background: linear-gradient(90deg, #8B5CF6 0%, #3B82F6 100%); color: white; border: none; border-radius: 5px; padding: 8px 15px; outline: none; white-space: nowrap; -webkit-user-select: none; cursor: pointer; font-weight: 500; font-size: 15px; margin-right: 10px; } .ai-contact-form-group input[type="file"]:hover::before { background: linear-gradient(90deg, #6a3eaf 0%, #2a6dbd 100%); } .ai-contact-form-group input[type="file"]:active::before { background: linear-gradient(90deg, #5b3495 0%, #21599e 100%); } /* Текст предупреждения о размере файла */ .ai-contact-file-warning-text { display: block; /* Отображаем как блочный элемент */ font-size: 13px; color: #888; margin-top: 5px; padding-left: 12px; /* Выравнивание с input'ами */ } .ai-contact-form-group textarea { min-height: 100px; resize: vertical; } .ai-contact-privacy-checkbox { display: flex; align-items: flex-start; margin-bottom: 20px; } .ai-contact-privacy-checkbox input[type="checkbox"] { margin-right: 10px; width: 18px; height: 18px; flex-shrink: 0; cursor: pointer; } .ai-contact-privacy-checkbox label { font-size: 14px; color: #666; line-height: 1.4; cursor: pointer; } .ai-contact-privacy-checkbox label a { color: #8B5CF6; text-decoration: none; transition: color 0.2s ease; } .ai-contact-privacy-checkbox label a:hover { text-decoration: underline; color: #6a3eaf; } .ai-contact-submit-btn { display: block; width: 100%; padding: 14px; background: linear-gradient(90deg, #8B5CF6 0%, #3B82F6 100%); color: white; border: none; border-radius: 8px; font-size: 18px; font-weight: 600; cursor: pointer; transition: background 0.3s ease, box-shadow 0.3s ease; } .ai-contact-submit-btn:hover { background: linear-gradient(90deg, #6a3eaf 0%, #2a6dbd 100%); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); } .ai-contact-submit-btn:disabled { background: #cccccc; cursor: not-allowed; box-shadow: none; } .ai-contact-form-message { margin-top: 15px; padding: 10px 15px; border-radius: 8px; text-align: center; font-size: 15px; display: none; /* Скрыто по умолчанию */ } .ai-contact-form-message.ai-contact-success { background-color: #e6ffe6; color: #28a745; border: 1px solid #28a745; } .ai-contact-form-message.ai-contact-error { background-color: #ffe6e6; color: #dc3545; border: 1px solid #dc3545; } /* --- Стили для нового модального окна QR кода --- */ /* Наследуем основные стили от .ai-contact-modal-content */ .ai-contact-qr-content { max-width: 380px; /* Уменьшенная ширина для QR модала */ padding: 20px; text-align: center; /* Центрируем содержимое */ } .ai-contact-qr-content h3 { font-size: 22px; margin-bottom: 15px; color: #333; } .ai-contact-qr-body { display: flex; flex-direction: column; /* Элементы по умолчанию располагаются вертикально */ align-items: center; /* Центрируем по горизонтали */ gap: 20px; margin-bottom: 20px; } .ai-contact-qr-code-wrapper { background-color: #f9f9f9; padding: 10px; border-radius: 8px; border: 1px solid #eee; box-shadow: inset 0 0 5px rgba(0,0,0,0.05); max-width: 200px; /* Ограничиваем размер контейнера QR */ } .ai-contact-qr-code-img { display: block; /* Убираем лишний пробел под изображением */ max-width: 100%; height: auto; } .ai-contact-qr-phone { font-size: 20px; font-weight: 600; color: #191970; /* Темно-синий цвет, как у кнопок телефона/почты */ margin: 0; white-space: nowrap; /* Предотвращаем перенос номера */ } .ai-contact-qr-social-links { display: flex; gap: 10px; /* Расстояние между иконками соцсетей */ justify-content: center; flex-wrap: wrap; /* Позволяет переносить кнопки на новую строку, если не хватает места */ } /* Элементы .ai-contact-item внутри QR модала также используют общие стили кнопок */ .ai-contact-qr-social-links .ai-contact-item { width: 45px; /* Чуть меньше, чем в основном меню, для компактности */ height: 45px; font-size: 24px; /* Размер иконки */ border-radius: 10px; } .ai-contact-qr-footer { margin-top: 10px; padding-top: 15px; border-top: 1px solid #eee; } .ai-contact-qr-footer-logo-img { max-width: 100%; height: auto; display: block; margin: 0 auto; } /* Адаптивность */ @media (max-width: 767px) { .ai-contact-widget { right: 15px; bottom: 15px; } .ai-contact-main-toggle-btn { width: 55px; height: 55px; border-radius: 14px; /* Пропорциональное уменьшение */ } /* Уменьшаем размер иконок Font Awesome для основной кнопки */ .ai-contact-main-toggle-btn .ai-contact-dynamic-icon, .ai-contact-main-toggle-btn .ai-contact-close-icon { font-size: 26px; /* Уменьшаем размер иконки Font Awesome */ } .ai-contact-item { width: 45px; height: 45px; border-radius: 10px; /* Пропорциональное уменьшение */ } /* Уменьшаем размер иконок Font Awesome для элементов меню */ .ai-contact-item .ai-contact-icon { font-size: 24px; /* Уменьшаем размер иконки Font Awesome */ } .ai-contact-label { font-size: 14px; padding: 6px 10px; border-radius: 10px; } .ai-contact-menu.ai-contact-is-open .ai-contact-menu-item-group .ai-contact-label { max-width: 180px; /* Уменьшаем max-width для лейблов на мобильных */ padding-left: 10px; padding-right: 10px; } /* Скрываем пункт "Сканировать QR" на мобильных устройствах */ .ai-contact-qr-menu-item { display: none; } .ai-contact-modal-content { padding: 20px; border-radius: 10px; width: 95%; /* Чуть шире на мобильных */ } /* QR Модал на мобильных */ .ai-contact-qr-content { padding: 15px; max-width: 320px; /* Еще меньше на мобильных */ } .ai-contact-qr-content h3 { font-size: 20px; margin-bottom: 10px; } .ai-contact-qr-phone { font-size: 18px; } .ai-contact-qr-social-links .ai-contact-item { width: 40px; height: 40px; font-size: 22px; } .ai-contact-modal-close { font-size: 24px; top: 10px; right: 10px; } .ai-contact-modal-content h3 { font-size: 20px; margin-bottom: 15px; } .ai-contact-form-group label { font-size: 14px; } .ai-contact-form-group input[type="text"], .ai-contact-form-group input[type="email"], .ai-contact-form-group input[type="tel"], /* Адаптивный стиль для телефона */ .ai-contact-form-group textarea, .ai-contact-form-group input[type="file"] { /* Адаптивный стиль для загрузки файла */ padding: 10px; font-size: 15px; } /* Адаптивный стиль для кнопки выбора файла */ .ai-contact-form-group input[type="file"]::before { padding: 6px 12px; font-size: 14px; } .ai-contact-file-warning-text { font-size: 12px; padding-left: 10px; } .ai-contact-privacy-checkbox label { font-size: 13px; } .ai-contact-submit-btn { padding: 12px; font-size: 16px; } } </style><div class="ai-contact-menu"><!-- Группы элементов меню, расположены в порядке снизу вверх благодаря column-reverse --><!-- 1. Телефон: Цвет изменен на темно-синий, как у Email, согласно инструкции. Иконка Font Awesome. --><div class="ai-contact-menu-item-group"><span class="ai-contact-label ai-contact-item-darkblue" data-target-id="ai-contact-phone-link">Позвонить</span> <!-- Добавлен data-target-id для JS --><a href="tel:+79208880303" class="ai-contact-item ai-contact-item-darkblue" id="ai-contact-phone-link" aria-label="Позвонить по телефону" rel="noopener noreferrer"><i class="fa-solid fa-phone ai-contact-icon"></i></a></div><!-- 2. Email (открывает форму обратной связи): Иконка Font Awesome конверта. Функционал открытия модального окна перенесен сюда. --><div class="ai-contact-menu-item-group"><span class="ai-contact-label ai-contact-item-darkblue" data-target-id="ai-contact-open-modal-btn">Напишите нам</span> <!-- Добавлен data-target-id для JS --><button type="button" class="ai-contact-item ai-contact-item-darkblue" id="ai-contact-open-modal-btn" aria-label="Открыть форму обратной связи"><i class="fa-solid fa-envelope ai-contact-icon"></i></button></div><!-- 3. Telegram: Соответствующий цвет и иконка Font Awesome. --><div class="ai-contact-menu-item-group"><span class="ai-contact-label ai-contact-item-telegram" data-target-id="ai-contact-telegram-link">Написать в Telegram</span> <!-- Добавлен data-target-id для JS --><!-- ВНИМАНИЕ: Для корректной работы ссылки на Telegram необходимо заменить "your_telegram_username" на ваш реальный логин Telegram или ссылку на бота. --><a href="https://t.me/Dginyvolshebnik" class="ai-contact-item ai-contact-item-telegram" id="ai-contact-telegram-link" target="_blank" aria-label="Написать в Telegram" rel="noopener noreferrer"><i class="fa-brands fa-telegram-plane ai-contact-icon"></i></a></div><!-- 4. WhatsApp: Соответствующий цвет и иконка Font Awesome. --><div class="ai-contact-menu-item-group"><span class="ai-contact-label ai-contact-item-whatsapp" data-target-id="ai-contact-whatsapp-link">Написать в WhatsApp</span> <!-- Добавлен data-target-id для JS --><a href="https://wa.me/79208880303" class="ai-contact-item ai-contact-item-whatsapp" id="ai-contact-whatsapp-link" target="_blank" aria-label="Написать в WhatsApp" rel="noopener noreferrer"><i class="fa-brands fa-whatsapp ai-contact-icon"></i></a></div><!-- 5. VK: Соответствующий цвет и иконка Font Awesome. --><div class="ai-contact-menu-item-group"><span class="ai-contact-label ai-contact-item-vk" data-target-id="ai-contact-vk-link">Написать в VK</span> <!-- Добавлен data-target-id для JS --><a href="https://vk.com/dginyvolshebnik" class="ai-contact-item ai-contact-item-vk" id="ai-contact-vk-link" target="_blank" aria-label="Написать в VK" rel="noopener noreferrer"><i class="fa-brands fa-vk ai-contact-icon"></i></a></div><!-- НОВЫЙ ПУНКТ: Сканировать QR код. Виден только на десктопах и планшетах (скрыт на мобильных). --><div class="ai-contact-menu-item-group ai-contact-qr-menu-item"><span class="ai-contact-label ai-contact-item-darkblue" data-target-id="ai-contact-open-qr-modal-btn">Сканировать QR</span><button type="button" class="ai-contact-item ai-contact-item-darkblue" id="ai-contact-open-qr-modal-btn" aria-label="Открыть окно со QR-кодом"><i class="fa-solid fa-qrcode ai-contact-icon"></i></button></div></div><!-- Основная кнопка, которая переключает меню и вращается --><button class="ai-contact-main-toggle-btn ai-contact-theme-default" aria-label="Открыть меню связи"><!-- Единый элемент для отображения меняющихся иконок (чат, телефон, email, мессенджеры) --><i class="ai-contact-dynamic-icon ai-contact-icon fa-solid fa-comment"></i><!-- Иконка закрытия (крестик), появляется при открытом меню --><i class="fa-solid fa-times ai-contact-close-icon ai-contact-icon"></i></button></div><!-- Модальное окно формы обратной связи --><div id="ai-contact-modal-form" class="ai-contact-modal-form" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="ai-contact-modal-title"><div class="ai-contact-modal-content"><button type="button" class="ai-contact-modal-close" aria-label="Закрыть модальное окно">×</button><h3 id="ai-contact-modal-title">Связаться с нами</h3><form id="ai-contact-feedback-form"><!-- ANTI-SPAM FIELDS (ОБЯЗАТЕЛЬНО) --><!-- Honeypot поле: должно быть скрыто от пользователя, но заполнено ботами --><input type="text" name="website_hp" class="ai-contact-honeypot" style="display:none !important" tabindex="-1" autocomplete="off"><!-- Поле для таймера: будет заполнено JS при загрузке страницы --><input type="hidden" name="form_time" id="ai-contact-form-time" value=""><div class="ai-contact-form-group"><label for="ai-contact-name">Ваше имя:<span style="color: red;">*</span></label><input type="text" id="ai-contact-name" name="name" required placeholder="Введите ваше имя"></div><div class="ai-contact-form-group"><label for="ai-contact-phone">Ваш телефон:<span style="color: red;">*</span></label><input type="tel" id="ai-contact-phone" name="phone" required placeholder="Например: +79876543210"></div><div class="ai-contact-form-group"><label for="ai-contact-email">Ваш Email:</label><input type="email" id="ai-contact-email" name="email" placeholder="Введите ваш Email (необязательно)"></div><div class="ai-contact-form-group"><label for="ai-contact-message">Сообщение:</label><textarea id="ai-contact-message" name="message" rows="5" placeholder="Ваше сообщение (необязательно)..."></textarea></div><div class="ai-contact-form-group"><label for="ai-contact-file">Прикрепить файл:</label><input type="file" id="ai-contact-file" name="attachment"><span id="ai-contact-file-size-warning" class="ai-contact-file-warning-text">Максимальный размер файла: 20 МБ.</span></div><div class="ai-contact-privacy-checkbox"><input type="checkbox" id="ai-contact-privacy-policy" name="privacyPolicy" required><label for="ai-contact-privacy-policy"> Я согласен с <a href="https://40-e.ru/privacy-policy" target="_blank" rel="noopener noreferrer">политикой конфиденциальности</a>. </label></div><button type="submit" class="ai-contact-submit-btn">Отправить</button><div id="ai-contact-form-response" class="ai-contact-form-message" aria-live="polite"></div></form></div></div><!-- НОВОЕ МОДАЛЬНОЕ ОКНО: QR код для связи --><div id="ai-contact-qr-modal" class="ai-contact-modal-form" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="ai-contact-qr-modal-title"><div class="ai-contact-modal-content ai-contact-qr-content"><button type="button" class="ai-contact-modal-close" aria-label="Закрыть модальное окно">×</button><h3 id="ai-contact-qr-modal-title">Мы на связи</h3><div class="ai-contact-qr-body"><div class="ai-contact-qr-code-wrapper"><!-- Путь к изображению QR кода. Замените 'qr_code.png' на актуальный путь, если он другой. --><img src="/upload/dev2fun.imagecompress/webp/images/qr.webp" alt="QR-код для связи с нами" class="ai-contact-qr-code-img"></div><div class="ai-contact-qr-info"><p class="ai-contact-qr-phone">+7 920 888 0303</p><div class="ai-contact-qr-social-links"><!-- Кнопки мессенджеров, стилизованные как элементы меню --><a href="https://t.me/Dginyvolshebnik" class="ai-contact-item ai-contact-item-telegram" target="_blank" aria-label="Написать в Telegram" rel="noopener noreferrer"><i class="fa-brands fa-telegram-plane ai-contact-icon"></i></a><a href="https://wa.me/79208880303" class="ai-contact-item ai-contact-item-whatsapp" target="_blank" aria-label="Написать в WhatsApp" rel="noopener noreferrer"><i class="fa-brands fa-whatsapp ai-contact-icon"></i></a><a href="https://vk.com/dginyvolshebnik" class="ai-contact-item ai-contact-item-vk" target="_blank" aria-label="Написать в VK" rel="noopener noreferrer"><i class="fa-brands fa-vk ai-contact-icon"></i></a></div></div></div><div class="ai-contact-qr-footer"><!-- Логотип в подвале модального окна. Замените 'logo_konsultante.png' на актуальный путь. --><img src="/upload/dev2fun.imagecompress/webp/images/logoqr.webp" alt="Логотип WEB-СТУДИЯ KONSULTANTE" class="ai-contact-qr-footer-logo-img"></div></div></div><script> // CRITICAL RULE: CSS UNIQUENESS // Все JS классы и ID должны быть уникальными для предотвращения конфликтов. Используем префикс "ai-contact-" document.addEventListener('DOMContentLoaded', () => { const widget = document.querySelector('.ai-contact-widget'); const toggleButton = widget.querySelector('.ai-contact-main-toggle-btn'); const menu = widget.querySelector('.ai-contact-menu'); // Элементы меню теперь представляют собой группы (лейбл + кнопка) // Они расположены в HTML в визуальном порядке (сверху вниз), но отображаются через flex-direction: column-reverse, // что делает их порядок снизу вверх. Для staggered animation нужно получить их в этом "визуальном" снизу-вверх порядке. const menuGroups = Array.from(menu.querySelectorAll('.ai-contact-menu-item-group')).reverse(); // Кнопка открытия модального окна обратной связи (Email) const openModalButton = document.getElementById('ai-contact-open-modal-btn'); const modal = document.getElementById('ai-contact-modal-form'); const modalCloseButton = modal.querySelector('.ai-contact-modal-close'); const feedbackForm = document.getElementById('ai-contact-feedback-form'); const formResponseDiv = document.getElementById('ai-contact-form-response'); const privacyCheckbox = document.getElementById('ai-contact-privacy-policy'); const submitButton = feedbackForm.querySelector('.ai-contact-submit-btn'); // Добавлены ссылки на новые поля формы: телефон и файл const phoneInput = document.getElementById('ai-contact-phone'); // Поле для телефона const fileInput = document.getElementById('ai-contact-file'); // Поле для загрузки файла const fileSizeWarningText = document.getElementById('ai-contact-file-size-warning'); // Текст предупреждения о размере файла // НОВЫЕ ЭЛЕМЕНТЫ: Модальное окно QR кода const openQrModalButton = document.getElementById('ai-contact-open-qr-modal-btn'); // Кнопка открытия QR модала const qrModal = document.getElementById('ai-contact-qr-modal'); const qrModalCloseButton = qrModal.querySelector('.ai-contact-modal-close'); // Антиспам поля const formTimeInput = document.getElementById('ai-contact-form-time'); // Элементы иконок внутри основной кнопки const dynamicIcon = toggleButton.querySelector('.ai-contact-dynamic-icon'); const closeIcon = toggleButton.querySelector('.ai-contact-close-icon'); let isMenuOpen = false; let isModalOpen = false; let isQrModalOpen = false; // НОВОЕ: Флаг состояния для QR модала let exitIntentTriggered = false; // НОВОЕ: Флаг для срабатывания exit-intent один раз // Определяем темы для циклического вращения кнопки ДИНАМИЧЕСКИ, ИЗ СУЩЕСТВУЮЩИХ КНОПОК МЕНЮ. // Инструкция: "смена значков и цвета должна быть из кнопок которые есть". // Мы собираем данные об иконках, цветах и текстах лейблов из каждого элемента меню. // Исключаем новый пункт "Сканировать QR" из ротации основной кнопки, так как он открывает отдельное модальное окно, // а основная кнопка должна показывать иконки прямого действия (звонок, чат, почта). const rotationThemes = []; menuGroups.forEach(group => { // Пропускаем элемент QR-кода, чтобы он не участвовал в ротации основной кнопки if (group.classList.contains('ai-contact-qr-menu-item')) { return; } const itemElement = group.querySelector('.ai-contact-item'); const iconElement = itemElement.querySelector('.ai-contact-icon'); const labelElement = group.querySelector('.ai-contact-label'); if (itemElement && iconElement && labelElement) { // Извлекаем все классы Font Awesome, которые указывают на тип и саму иконку // (например, 'fa-solid fa-phone' или 'fa-brands fa-telegram-plane'). const faIconFullClasses = Array.from(iconElement.classList) .filter(cls => cls.startsWith('fa-') && !cls.includes('ai-contact-icon')) .join(' '); // Извлекаем класс цвета кнопки меню (например, 'ai-contact-item-darkblue'). const colorClass = Array.from(itemElement.classList).find(cls => cls.startsWith('ai-contact-item-')); // Преобразуем его в класс темы для основной кнопки (например, 'ai-contact-theme-darkblue'). let themeClassForButton = ''; if (colorClass) { themeClassForButton = colorClass.replace('ai-contact-item-', 'ai-contact-theme-'); } rotationThemes.push({ iconClasses: faIconFullClasses, // Полный набор классов иконки Font Awesome для динамической смены themeClass: themeClassForButton, // Класс для изменения цвета фона основной кнопки label: labelElement.textContent.trim() // Текст лейбла для aria-label основной кнопки }); } }); let currentRotationThemeIndex = -1; // Начинаем с -1, чтобы первый вызов применил тему с индексом 0 let rotationInterval; const ROTATION_INTERVAL_TIME = 5000; // Интервал смены иконки: 5 секунд const ICON_FLIP_DURATION = 200; // Длительность анимации переворота иконки (соответствует CSS) const MAX_FILE_SIZE_MB = 20; // Максимальный размер файла в мегабайтах const MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024; // Максимальный размер файла в байтах // Устанавливаем значение поля form_time при загрузке страницы formTimeInput.value = Math.floor(Date.now() / 1000); // Функция для применения конкретной темы к основной кнопке (фон и иконка) function applyRotatingTheme(themeIndex, animate = true) { if (themeIndex < 0 || themeIndex >= rotationThemes.length) { console.error("Invalid theme index for rotation."); return; } const theme = rotationThemes[themeIndex]; // Удаляем класс дефолтной темы и все классы тем вращения, чтобы применить только один новый toggleButton.classList.remove('ai-contact-theme-default'); rotationThemes.forEach(t => toggleButton.classList.remove(t.themeClass)); // Применяем новый класс темы для цвета toggleButton.classList.add(theme.themeClass); // Обновляем aria-label для доступности toggleButton.setAttribute('aria-label', theme.label); // Если включена анимация, выполняем эффект переворота if (animate) { // Шаг 1: Анимация "скрытия" текущей иконки (flip-out) toggleButton.classList.add('ai-contact-is-rotating'); // Этот класс запускает @keyframes ai-contact-icon-flip-out setTimeout(() => { // Шаг 2: Смена классов иконки в середине анимации // Используем полный набор классов иконки, полученных из элемента меню dynamicIcon.className = `ai-contact-dynamic-icon ai-contact-icon ${theme.iconClasses}`; toggleButton.classList.remove('ai-contact-is-rotating'); // Удаляем класс "скрытия" // Шаг 3: Анимация "появления" новой иконки (flip-in) toggleButton.classList.add('ai-contact-flip-in'); // Этот класс запускает @keyframes ai-contact-icon-flip-in setTimeout(() => { toggleButton.classList.remove('ai-contact-flip-in'); // Убираем класс после завершения анимации }, ICON_FLIP_DURATION); }, ICON_FLIP_DURATION); // Ждем завершения анимации "скрытия" } else { // Без анимации: просто устанавливаем иконку и убеждаемся, что dynamicIcon видна // Используем полный набор классов иконки dynamicIcon.className = `ai-contact-dynamic-icon ai-contact-icon ${theme.iconClasses}`; // Также убеждаемся, что нет никаких классов анимации вращения toggleButton.classList.remove('ai-contact-is-rotating', 'ai-contact-flip-in'); } } // Функция для применения дефолтной темы (иконка чата и фиолетовый градиент) function applyDefaultChatTheme(animate = false) { // Удаляем все классы тем вращения, чтобы применить дефолтную rotationThemes.forEach(t => toggleButton.classList.remove(t.themeClass)); // Применяем стандартный градиент toggleButton.classList.add('ai-contact-theme-default'); toggleButton.setAttribute('aria-label', 'Открыть меню связи'); if (animate) { // Анимация возврата к иконке чата (например, после закрытия меню/модала) toggleButton.classList.add('ai-contact-is-rotating'); setTimeout(() => { dynamicIcon.className = `ai-contact-dynamic-icon ai-contact-icon fa-solid fa-comment`; // Устанавливаем иконку чата toggleButton.classList.remove('ai-contact-is-rotating'); toggleButton.classList.add('ai-contact-flip-in'); setTimeout(() => { toggleButton.classList.remove('ai-contact-flip-in'); }, ICON_FLIP_DURATION); }, ICON_FLIP_DURATION); } else { // Без анимации, просто устанавливаем иконку чата dynamicIcon.className = `ai-contact-dynamic-icon ai-contact-icon fa-solid fa-comment`; // Устанавливаем иконку чата toggleButton.classList.remove('ai-contact-is-rotating', 'ai-contact-flip-in'); // Чистим классы анимации } } // Функция для запуска периодического вращения function startPeriodicRotation() { stopRotation(); // Очищаем любой существующий интервал rotationInterval = setInterval(() => { // Вращаем только если меню, форма обратной связи и QR модал закрыты if (!isMenuOpen && !isModalOpen && !isQrModalOpen) { currentRotationThemeIndex = (currentRotationThemeIndex + 1) % rotationThemes.length; applyRotatingTheme(currentRotationThemeIndex, true); // Применяем тему с анимацией } }, ROTATION_INTERVAL_TIME); } // Функция для остановки вращения function stopRotation() { if (rotationInterval) { clearInterval(rotationInterval); rotationInterval = null; } // Удаляем классы анимации вращения сразу toggleButton.classList.remove('ai-contact-is-rotating', 'ai-contact-flip-in'); // Важно: здесь не меняем иконку или фон, они должны остаться такими, какими были на момент остановки. // Дальнейшее состояние (крестик, иконка чата, или продолжение вращения) будет задано в toggleMenu, closeModal или closeQrModal. } // Функция для открытия/закрытия меню function toggleMenu() { // Если открыт QR модал или форма обратной связи, сначала закрываем их if (isQrModalOpen) { closeQrModal(); } if (isModalOpen) { closeModal(); } isMenuOpen = !isMenuOpen; menu.classList.toggle('ai-contact-is-open', isMenuOpen); toggleButton.classList.toggle('ai-contact-is-open', isMenuOpen); // Этот класс отвечает за красный фон и иконку 'X' toggleButton.setAttribute('aria-expanded', isMenuOpen); if (isMenuOpen) { stopRotation(); // Останавливаем вращение при открытии меню // CSS автоматически переключит фон на красный и иконку на крестик благодаря классу 'ai-contact-is-open' toggleButton.setAttribute('aria-label', 'Закрыть меню связи'); // При открытии: staggered effect для каждой группы элементов меню menuGroups.forEach((group, index) => { group.style.animationDelay = `${index * 0.05}s`; // Задержка +0.05s для каждой группы // Применение staggered transition для лейбла const label = group.querySelector('.ai-contact-label'); if (label) { label.style.transitionDelay = `${index * 0.05 + 0.1}s`; // Лейблы появляются чуть позже группы } }); console.log('Yandex.Metrica: messenger_open'); // Placeholder для Яндекс.Метрики } else { // При закрытии меню toggleButton.classList.remove('ai-contact-is-open'); // Удаляем класс 'is-open', CSS переключит на динамическую иконку // Сначала возвращаем кнопку к дефолтной иконке чата с анимацией applyDefaultChatTheme(true); // Затем, после завершения анимации возврата к иконке чата, запускаем вращение setTimeout(() => { currentRotationThemeIndex = -1; // Сбрасываем индекс, чтобы начать вращение с первой темы // Сразу применяем первую вращающуюся тему с анимацией, как первый шаг цикла currentRotationThemeIndex = (currentRotationThemeIndex + 1) % rotationThemes.length; applyRotatingTheme(currentRotationThemeIndex, true); startPeriodicRotation(); // Запускаем периодическое вращение }, ICON_FLIP_DURATION + 50); // Небольшая задержка после анимации появления иконки чата menuGroups.forEach(group => { group.style.animationDelay = ''; const label = group.querySelector('.ai-contact-label'); if (label) { // Сброс задержки для лейблов, чтобы они исчезали синхронно с закрытием меню label.style.transitionDelay = ''; } }); } } // Функция для открытия модального окна обратной связи function openModal() { // Если открыто меню или QR модал, закрываем их if (isMenuOpen) { toggleMenu(); } if (isQrModalOpen) { closeQrModal(); } stopRotation(); // Останавливаем вращение при открытии модального окна modal.classList.add('ai-contact-modal-is-open'); modal.setAttribute('aria-hidden', 'false'); isModalOpen = true; // Переключаем фокус на модальное окно для доступности modal.focus(); console.log('Yandex.Metrica: form_feedback_open'); // Placeholder для Яндекс.Метрики } // Функция для закрытия модального окна обратной связи function closeModal() { modal.classList.remove('ai-contact-modal-is-open'); modal.setAttribute('aria-hidden', 'true'); isModalOpen = false; // Очищаем форму и сообщения feedbackForm.reset(); formResponseDiv.style.display = 'none'; formResponseDiv.textContent = ''; formResponseDiv.classList.remove('ai-contact-success', 'ai-contact-error'); // Сброс классов сообщения submitButton.disabled = !privacyCheckbox.checked; // Сброс состояния кнопки fileInput.value = ''; // Очищаем поле файла toggleButton.focus(); // Возвращаем фокус на кнопку виджета // При закрытии модального окна, возвращаем кнопку к дефолтной иконке чата, затем запускаем вращение applyDefaultChatTheme(true); // Анимация возврата к иконке чата setTimeout(() => { currentRotationThemeIndex = -1; // Сбрасываем индекс для начала вращения с первой темы // Сразу применяем первую вращающуюся тему с анимацией currentRotationThemeIndex = (currentRotationThemeIndex + 1) % rotationThemes.length; applyRotatingTheme(currentRotationThemeIndex, true); startPeriodicRotation(); // Запускаем периодическое вращение }, ICON_FLIP_DURATION + 50); // Небольшая задержка после анимации появления иконки чата } // НОВАЯ ФУНКЦИЯ: для открытия модального окна QR кода function openQrModal() { // Если открыто меню или форма обратной связи, закрываем их if (isMenuOpen) { toggleMenu(); } if (isModalOpen) { closeModal(); } stopRotation(); // Останавливаем вращение при открытии модального окна qrModal.classList.add('ai-contact-modal-is-open'); qrModal.setAttribute('aria-hidden', 'false'); isQrModalOpen = true; // Переключаем фокус на модальное окно для доступности qrModal.focus(); console.log('Yandex.Metrica: qr_modal_open'); // Метрика для открытия QR модала } // НОВАЯ ФУНКЦИЯ: для закрытия модального окна QR кода function closeQrModal() { qrModal.classList.remove('ai-contact-modal-is-open'); qrModal.setAttribute('aria-hidden', 'true'); isQrModalOpen = false; toggleButton.focus(); // Возвращаем фокус на кнопку виджета // При закрытии модального окна, возвращаем кнопку к дефолтной иконке чата, затем запускаем вращение applyDefaultChatTheme(true); // Анимация возврата к иконке чата setTimeout(() => { currentRotationThemeIndex = -1; // Сбрасываем индекс для начала вращения с первой темы // Сразу применяем первую вращающуюся тему с анимацией currentRotationThemeIndex = (currentRotationThemeIndex + 1) % rotationThemes.length; applyRotatingTheme(currentRotationThemeIndex, true); startPeriodicRotation(); // Запускаем периодическое вращение }, ICON_FLIP_DURATION + 50); // Небольшая задержка после анимации появления иконки чата } // Обработчик клика по основной кнопке виджета toggleButton.addEventListener('click', toggleMenu); // Обработчик кликов по элементам меню (кнопкам/ссылкам внутри групп) menuGroups.forEach(group => { const item = group.querySelector('.ai-contact-item'); // Получаем саму кнопку/ссылку внутри группы const label = group.querySelector('.ai-contact-label'); // Получаем лейбл внутри группы if (item) { item.addEventListener('click', (event) => { const ariaLabel = item.getAttribute('aria-label'); // Placeholder для событий Яндекс.Метрики if (ariaLabel.includes('Позвонить')) { console.log('Yandex.Metrica: phone_click'); } else if (ariaLabel.includes('Написать в Telegram')) { console.log('Yandex.Metrica: telegram_click'); } else if (ariaLabel.includes('Написать в WhatsApp')) { console.log('Yandex.Metrica: whatsapp_click'); } else if (ariaLabel.includes('Написать в VK')) { console.log('Yandex.Metrica: vk_click'); } // Если это НЕ кнопка открытия модального окна обратной связи И НЕ QR модала, закрываем меню if (item.id !== 'ai-contact-open-modal-btn' && item.id !== 'ai-contact-open-qr-modal-btn') { toggleMenu(); } }); } // Добавляем обработчик клика для лейбла, чтобы он тоже запускал действие кнопки if (label && item) { label.addEventListener('click', () => { item.click(); // Программно имитируем клик по соответствующей кнопке/ссылке }); } }); // Обработчик для кнопки открытия модального окна обратной связи (Email) openModalButton.addEventListener('click', (event) => { event.preventDefault(); // Предотвращаем стандартное действие кнопки (если это была бы ссылка с #) console.log('Yandex.Metrica: form_feedback_click'); // Метрика для формы обратной связи openModal(); }); // НОВЫЙ ОБРАБОТЧИК: для кнопки открытия QR модала openQrModalButton.addEventListener('click', (event) => { event.preventDefault(); openQrModal(); }); // Обработчик для кнопки закрытия модального окна обратной связи modalCloseButton.addEventListener('click', closeModal); // НОВЫЙ ОБРАБОТЧИК: для кнопки закрытия QR модала qrModalCloseButton.addEventListener('click', closeQrModal); // Закрытие модального окна обратной связи при клике на оверлей modal.addEventListener('click', (event) => { if (event.target === modal) { closeModal(); } }); // НОВЫЙ ОБРАБОТЧИК: Закрытие QR модала при клике на оверлей qrModal.addEventListener('click', (event) => { if (event.target === qrModal) { closeQrModal(); } }); // Закрытие меню/модальных окон по нажатию Esc document.addEventListener('keydown', (event) => { if (event.key === 'Escape') { if (isModalOpen) { closeModal(); } else if (isQrModalOpen) { // НОВОЕ: Проверяем QR модал первым closeQrModal(); } else if (isMenuOpen) { toggleMenu(); } } }); // Закрытие меню при клике вне виджета (если модальные окна не открыты) document.addEventListener('click', (event) => { // Проверяем, что клик был вне виджета и не на модальных окнах if (isMenuOpen && !widget.contains(event.target) && !modal.contains(event.target) && !qrModal.contains(event.target)) { toggleMenu(); } }); // НОВОЕ: Exit-intent (всплытие QR модала при попытке покинуть страницу) // Будет срабатывать только один раз за сессию (пока страница не будет перезагружена). document.addEventListener('mouseleave', (event) => { // Проверяем, что курсор уходит за верхний край окна браузера if (event.clientY < 0 && !exitIntentTriggered && !isMenuOpen && !isModalOpen && !isQrModalOpen) { exitIntentTriggered = true; // Устанавливаем флаг, чтобы не срабатывало повторно openQrModal(); console.log('Yandex.Metrica: exit_intent_qr_open'); // Метрика для exit-intent } }); // Установка aria-expanded для начального состояния toggleButton.setAttribute('aria-expanded', 'false'); // Установка rel="noopener noreferrer" для всех ссылок с target="_blank" document.querySelectorAll('a[target="_blank"]').forEach(link => { if (!link.getAttribute('rel')) { link.setAttribute('rel', 'noopener noreferrer'); } }); // --- Логика формы обратной связи --- // Изначально кнопка отправки должна быть disabled, если чекбокс не отмечен submitButton.disabled = !privacyCheckbox.checked; // Обработчик изменения состояния чекбокса приватности privacyCheckbox.addEventListener('change', () => { submitButton.disabled = !privacyCheckbox.checked; }); /** * Асинхронная функция для отправки данных формы на указанный URL. * Обрабатывает ответы сервера, включая проверку на HTML-ответы вместо JSON. * @param {string} url - URL для отправки запроса. * @param {FormData} formData - Объект FormData, содержащий данные формы. * @returns {Promise<object>} Объект с статусом ('success'/'error') и сообщением. */ async function submitContactForm(url, formData) { try { const response = await fetch(url, { method: 'POST', body: formData, }); const text = await response.text(); // Проверяем, не вернул ли сервер HTML (например, страницу 404 или ошибку) if (text.trim().startsWith('<')) { console.error(`Ошибка от ${url}: Сервер вернул HTML вместо JSON.`, text); return { status: 'error', message: `Ошибка от ${url}: Некорректный ответ сервера.` }; } try { const json = JSON.parse(text); return json; } catch (e) { console.error(`Ошибка парсинга JSON ответа от ${url}:`, e, 'Исходный текст:', text); return { status: 'error', message: `Ошибка обработки ответа от ${url}.` }; } } catch (err) { console.error(`Сетевая ошибка при отправке на ${url}:`, err); return { status: 'error', message: `Сетевая ошибка при отправке на ${url}: ${err.message}` }; } } feedbackForm.addEventListener('submit', async (event) => { event.preventDefault(); // Предотвращаем стандартную отправку формы formResponseDiv.style.display = 'none'; formResponseDiv.textContent = ''; formResponseDiv.classList.remove('ai-contact-success', 'ai-contact-error'); const name = document.getElementById('ai-contact-name').value.trim(); const phone = document.getElementById('ai-contact-phone').value.trim(); const email = document.getElementById('ai-contact-email').value.trim(); const selectedFile = fileInput.files; const privacyAgreed = privacyCheckbox.checked; // --- Валидация обязательных полей --- if (!name) { formResponseDiv.textContent = 'Пожалуйста, введите ваше имя.'; formResponseDiv.classList.add('ai-contact-error'); formResponseDiv.style.display = 'block'; return; } if (!phone) { formResponseDiv.textContent = 'Пожалуйста, введите ваш телефон.'; formResponseDiv.classList.add('ai-contact-error'); formResponseDiv.style.display = 'block'; return; } if (!privacyAgreed) { formResponseDiv.textContent = 'Пожалуйста, согласитесь с политикой конфиденциальности.'; formResponseDiv.classList.add('ai-contact-error'); formResponseDiv.style.display = 'block'; return; } // --- Валидация формата телефона --- const phoneRegex = /^\+7\d{10}$/; // Например: +79876543210 if (!phoneRegex.test(phone)) { formResponseDiv.textContent = 'Пожалуйста, введите корректный номер телефона в формате +79876543210.'; formResponseDiv.classList.add('ai-contact-error'); formResponseDiv.style.display = 'block'; return; } // --- Валидация файла --- if (selectedFile.length > 0) { // Проверяем, что файл действительно выбран if (selectedFile.size > MAX_FILE_SIZE_BYTES) { formResponseDiv.textContent = `Размер файла превышает максимально допустимые ${MAX_FILE_SIZE_MB} МБ.`; formResponseDiv.classList.add('ai-contact-error'); formResponseDiv.style.display = 'block'; submitButton.textContent = 'Отправить'; submitButton.disabled = !privacyCheckbox.checked; return; } } // Простая проверка email (если он заполнен) const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (email && !emailRegex.test(email)) { formResponseDiv.textContent = 'Пожалуйста, введите корректный адрес электронной почты.'; formResponseDiv.classList.add('ai-contact-error'); formResponseDiv.style.display = 'block'; return; } submitButton.textContent = 'Отправка...'; submitButton.disabled = true; const formData = new FormData(feedbackForm); // Создаем FormData из всей формы, включая антиспам-поля // Отправляем данные на оба бэкенда const telegramPromise = submitContactForm('https://40-e.ru/telegram.php', formData); const mailPromise = submitContactForm('https://40-e.ru/mail.php', formData); const [telegramResult, mailResult] = await Promise.allSettled([telegramPromise, mailPromise]); let overallSuccess = true; let combinedMessages = []; // Обработка результата отправки в Telegram if (telegramResult.status === 'fulfilled' && telegramResult.value.status === 'success') { combinedMessages.push(telegramResult.value.message || 'Сообщение успешно отправлено в Telegram.'); } else { overallSuccess = false; // Более подробное сообщение об ошибке combinedMessages.push((telegramResult.status === 'fulfilled' && telegramResult.value.message) || (telegramResult.reason?.message) || 'Ошибка при отправке в Telegram.'); console.error('Telegram submission failed:', telegramResult); } // Обработка результата отправки по Email if (mailResult.status === 'fulfilled' && mailResult.value.status === 'success') { combinedMessages.push(mailResult.value.message || 'Сообщение успешно отправлено по Email.'); } else { overallSuccess = false; // Более подробное сообщение об ошибке combinedMessages.push((mailResult.status === 'fulfilled' && mailResult.value.message) || (mailResult.reason?.message) || 'Ошибка при отправке по Email.'); console.error('Mail submission failed:', mailResult); } if (overallSuccess) { formResponseDiv.textContent = '✅ ' + combinedMessages.join(' '); formResponseDiv.classList.add('ai-contact-success'); feedbackForm.reset(); // Очищаем форму privacyCheckbox.checked = false; // Сбрасываем чекбокс submitButton.disabled = true; // Деактивируем кнопку до нового согласия fileInput.value = ''; // Очищаем поле файла formResponseDiv.style.display = 'block'; console.log('Yandex.Metrica: form_feedback_sent_success'); } else { formResponseDiv.textContent = '❌ ' + combinedMessages.join(' '); formResponseDiv.classList.add('ai-contact-error'); formResponseDiv.style.display = 'block'; console.log('Yandex.Metrica: form_feedback_sent_error'); } submitButton.textContent = 'Отправить'; submitButton.disabled = !privacyCheckbox.checked; // Сброс состояния кнопки }); // --- Инициализация и запуск вращения основной кнопки --- // Изначальное состояние: устанавливаем иконку чата и дефолтный фон без анимации applyDefaultChatTheme(false); // Через 5 секунд запускаем первую анимацию переворота на первую тему, а затем запускаем периодическое вращение setTimeout(() => { // Убедимся, что rotationThemes не пуст, иначе нечего вращать if (rotationThemes.length > 0) { currentRotationThemeIndex = (currentRotationThemeIndex + 1) % rotationThemes.length; // Переходим к первой теме вращения (индекс 0) applyRotatingTheme(currentRotationThemeIndex, true); // Применяем ее с анимацией startPeriodicRotation(); // Запускаем интервал для последующих вращений } }, ROTATION_INTERVAL_TIME); }); </script></div><script> (function() { const root = document.getElementById("ai-widget-wrapper-ai-project237179"); if(!root) return; // Config passed from PHP to JS const config = { phone: "+7 999 000 00 00", title: "Мы на связи", buttons: [{"key":"tg","sort":1,"link":"https:\/\/t.me\/username","icon":"fa-telegram","color":"#0088cc","label":"Telegram"},{"key":"wa","sort":2,"link":"https:\/\/wa.me\/79990000000","icon":"fa-whatsapp","color":"#25D366","label":"WhatsApp"},{"key":"vk","sort":3,"link":"https:\/\/vk.com\/username","icon":"fa-vk","color":"#0077FF","label":"VKontakte"},{"key":"jivo","sort":4,"link":"#jivo","icon":"fa-headset","color":"#ff7e00","label":"JivoChat"},{"key":"custom","sort":10,"link":"#","icon":"fa-comments","color":"#6366F1","label":"Chat"}], useJivo: "#jivo" }; // Apply Content const setText = (sel, val) => { const el = root.querySelector(sel); if(el) el.textContent = val; }; setText('.phone-text', config.phone); setText('.widget-title', config.title); // Toggle Backdrop const modal = root.querySelector('.ai-widget-modal') || root.querySelector('#ai-widget-modal'); const backdrop = root.querySelector('.ai-widget-backdrop'); if(modal && backdrop) { const observer = new MutationObserver(mutations => { mutations.forEach(rec => { if(rec.target.classList.contains('active') || rec.target.style.display === 'block') { backdrop.classList.add('active'); } else { backdrop.classList.remove('active'); } }); }); observer.observe(modal, { attributes: true, attributeFilter: ['class', 'style'] }); } // Jivo Logic if(config.useJivo) { const jivoBtn = document.querySelector('a[href*="#jivo"]'); if(jivoBtn) { jivoBtn.addEventListener('click', (e) => { e.preventDefault(); if(window.jivo_api) window.jivo_api.open(); }); } } })(); </script>