Дата: Воскресенье, 28.12.2025, 19:22 | Сообщение # 1 |
|
Написал: Узнаваемый
Автор темы
Мурчанн
не в сети
Сообщений: 162
Двигаемся вперёд, совершенствуя систему уведомлений и расширяя возможности детальной шаблонизации. На данном этапе мы внедряем скрипты, которые позволяют отображать различные типы уведомлений по всему сайту. Это лишь первые наброски, требующие постоянной доработки, уточнения и визуальной проработки. Шаблон становится всё более продуманным, а мы максимально используем потенциал платформы uCoz для реализации всех необходимых функций.1. Проверка авторизации Код
const isGuest = "<?=$USER_LOGGED_IN$?>" !== "1";
Скрипт проверяет, залогинен ли пользователь. Если гость колокольчик слегка дергается, чтобы привлечь внимание, и открыть ЛС нельзя.2. Колокольчик уведомлений Код
const bell = $("#vk_notifyIcon"); bell.on("click", ()=> { window.location.href = "/index/14"; });
На элементе колокольчика (#vk_notifyIcon) добавлен клик. При нажатии , переходит на страницу ЛС. Если пользователь гость, колокольчик слегка "тряcется" каждые 10 секунд, чтобы привлечь внимание.3. Получение аватарок Код
async function fetchAvatar(profileUrl){ ... }
Принимает ссылку на профиль отправителя. Через $.get() подгружает HTML профиля. Ищет аватар в классе .profile-photo. Если аватар не найден , возвращает дефолт /files/person.png.4. Отображение уведомления Код
function showNotification({avatar, sender, title, description, date, href}) { ... }
Создаёт всплывающее окно уведомления.Содержит: Аватар отправителя; Имя отправителя; Заголовок сообщения; Описание (например, “Новое сообщение пришло”); Дату; Ссылку для перехода к сообщению. При появлении используется плавная анимация: Аватар масштабируется;Текст и дата появляются постепенно. Через 9 секунд уведомление скрывается и удаляется.5. Проверка новых сообщений Код
friends.checkPm = async function(){ ... }
Скрипт каждые 20 секунд (CHECK_DELAY) проверяет наличие новых ЛС: Загружает страницу ЛС через AJAX ($.get("/index/14?" + Math.random())); Находит все непрочитанные сообщения (b.unread); Берёт первое сообщение и его отправителя; Подгружает аватар отправителя; Вызывает showNotification() для показа уведомления.6. Сохранение состояния окна Скрипт учитывает, что окно может быть свернуто пользователем. Состояние хранится в localStorage (vkNotifyCollapsed):Если пользователь свернул окно , оно останется свернутым после перезагрузки. 7. Стили и дизайн Окно уведомлений в стиле ВКонтакте: Белый фон, скруглённые углы; Тень и аккуратная прокрутка; Красивый аватар с рамкой; Подсветка текста и элементов при наведении;Красная “пузырьковая” индикация количества сообщений теперь в стиле ВКонтакте. 8. Вспомогательные детали Плавное появление и исчезновение уведомления через CSS transition. Прокрутка сообщений настроена в стиле ВК. При наведении на сообщение цвет меняется, но без выпирания.В итоге: Скрипт следит за новыми личными сообщениями, показывает их в красивом всплывающем окне с аватарками и названием, хранит состояние окна и обновляет данные каждые 20 секунд.
Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
Дата: Воскресенье, 28.12.2025, 19:23 | Сообщение # 2 |
|
Написал: Узнаваемый
Автор темы
Мурчанн
не в сети
Сообщений: 162
Исходник шаблона (скрипты внутри) Код
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>BRO Account</title> <style> .top-user-menu, .user-box { display: inline-flex !important; align-items: center !important; height: 39px !important; /* подогнано под иконку */ position: relative; /* чтобы можно было двигать */ } /* Сдвиг настроек */ .top-user-menu { display: flex; align-items: center; gap: 10px; /* Поменяй эти значения для сдвига */ top: 0px; /* вверх/вниз: положительное вниз, отрицательное вверх */ left: -100px; /* влево/вправо: положительное вправо, отрицательное влево */ } /* ================================ НАСТРОЙКА ПОЛОЖЕНИЯ СТРЕЛКИ ================================ */ :root { --arrow-offset-y: 4px; /* ↓ ниже (+) / выше (-) */ } /* ================================ АВАТАР + СТРЕЛКА КАК В ВК ================================ */ .user-box { position: relative; display: flex; align-items: center; gap: 6px; cursor: pointer; /* Индивидуальный сдвиг */ top: 0px; left: -10px; } /* Стрелка справа от аватарки */ .user-box::after { content: ""; position: relative; top: calc(var(--arrow-offset-y) + 10px); width: 0; height: 0; margin-left: 26px; border-left: 8px solid transparent; border-right: 4px solid transparent; border-top: 6px solid #FFF; transition: transform 0.15s ease, opacity 0.15s ease; opacity: 0.9; } /* При открытом меню — стрелка вверх */ .user-box.active::after { transform: rotate(180deg); } .user-avatar { width: var(--user-avatar-size, 40px); height: var(--user-avatar-size, 40px); border-radius: 50%; object-fit: cover; position: relative; top: var(--user-avatar-top, -10px); left: var(--user-avatar-left, 33px); border: 3px solid #fff; outline: 1px solid #3f7cff; outline-offset: -1px; box-shadow: 0 4px 8px rgba(0,0,0,0.25); /* затемнение + анимации */ transition: transform .25s, box-shadow .45s, outline-color .25s, top .25s, left .25s, filter .25s; } /* затемнение при наведении */ .user-avatar:hover { box-shadow: 0 16px 14px rgba(0,0,0,0.45); filter: brightness(0.97); outline-color: #1f5cff; transform: translateY(0px); } /* ================================ НАСТРОЙКИ ПОЗИЦИИ МЕНЮ ================================ */ :root { --menu-offset-x: -157px; /* ← влево (-) / вправо (+) */ --menu-offset-y: -5px; /* ↑ вверх (-) / вниз (+) */ --menu-arrow-x: 182px; /* отступ стрелки от правого края */ } /* ================================ КОНТЕЙНЕР АВАТАРА ================================ */ .user-box { position: relative; cursor: pointer; } /* ================================ ВЫПАДАЮЩЕЕ МЕНЮ ================================ */ .user-dropdown { position: absolute; top: calc(50px + var(--menu-offset-y)); right: calc(0px + var(--menu-offset-x)); width: 220px; background: #ffffff; border-radius: 8px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); display: none; z-index: 9999; overflow: hidden; font-family: Arial, sans-serif; opacity: 0; transform: translateY(-6px); transition: opacity .15s ease, transform .15s ease; } /* Стрелка (как у цитаты / ВК) */ .user-dropdown::before { content: ""; position: absolute; top: -8px; right: var(--menu-arrow-x); width: 16px; height: 16px; background: #ffffff; transform: rotate(45deg); box-shadow: -3px -3px 6px rgba(0, 0, 0, 0.05); } /* Активное состояние */ .user-box.active .user-dropdown { display: block; opacity: 1; transform: translateY(0); } /* ================================ ЗАГОЛОВОК ================================ */ .user-dropdown-header { padding: 12px; background: #f5f6f8; font-weight: bold; font-size: 14px; color: #222; } /* ================================ ПУНКТЫ МЕНЮ ================================ */ .dropdown-item { display: flex; align-items: center; gap: 10px; padding: 10px 14px; font-size: 14px; color: #222; text-decoration: none; transition: background 0.15s ease; } /* Иконка (через CSS) */ .dropdown-item::before { content: ""; width: 20px; height: 20px; display: inline-flex; align-items: center; justify-content: center; font-size: 18px; } /* Наведение */ .dropdown-item:hover { background: #f0f2f5; } /* ================================ РАЗДЕЛИТЕЛЬ ================================ */ .dropdown-separator { height: 1px; background: #e5e7eb; margin: 6px 0; } /* ================================ ВЫХОД ================================ */ .dropdown-item.logout { color: #d93025; } .dropdown-item::before { content: ""; width: 22px; height: 26px; display: inline-block; /* ОБЯЗАТЕЛЬНО */ flex-shrink: 0; /* чтобы не схлопывалось */ background-color: #6d7885; /* цвет иконки */ mask-size: contain; mask-repeat: no-repeat; mask-position: center; -webkit-mask-size: contain; -webkit-mask-repeat: no-repeat; -webkit-mask-position: center; } /* Шестерёнка */ .icon-settings::before { content: "⚙️"; } .icon-messages::before { mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 4h16v11H7l-3 3V4z'/%3E%3C/svg%3E"); -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 4h16v11H7l-3 3V4z'/%3E%3C/svg%3E"); } .icon-profile::before { background-color: #1877f2; mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 12a5 5 0 100-10 5 5 0 000 10zm0 2c-4 0-8 2-8 5v1h16v-1c0-3-4-5-8-5z'/%3E%3C/svg%3E"); -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 12a5 5 0 100-10 5 5 0 000 10zm0 2c-4 0-8 2-8 5v1h16v-1c0-3-4-5-8-5z'/%3E%3C/svg%3E"); } .icon-logout::before { background-color: #d93025; mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M16 13v-2H7V8l-5 4 5 4v-3zM20 3H9v4h2V5h7v14h-7v-2H9v4h11z'/%3E%3C/svg%3E"); -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M16 13v-2H7V8l-5 4 5 4v-3zM20 3H9v4h2V5h7v14h-7v-2H9v4h11z'/%3E%3C/svg%3E"); } .icon-users::before { background-color: #d93025; /* синий, как у ВК для активных элементов */ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5s-3 1.34-3 3 1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V20h14v-3.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 2.01 1.97 3.45V20h6v-3.5c0-2.33-4.67-3.5-7-3.5z'/%3E%3C/svg%3E"); -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5s-3 1.34-3 3 1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V20h14v-3.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 2.01 1.97 3.45V20h6v-3.5c0-2.33-4.67-3.5-7-3.5z'/%3E%3C/svg%3E"); } /* ---------- Общие настройки ---------- */ * { box-sizing: border-box; } body { margin: 0; background: #e9ebee; font-family: Arial, sans-serif; -webkit-font-smoothing: antialiased; padding-top: 65px; /* отступ под фиксированную шапку */ } /* ---------- Верхнее меню ---------- */ .header { width: 100%; background: #3b5998; height: 65px; position: fixed; top: 0; left: 0; z-index: 1000; } .header-inner { width: 1035px; max-width: calc(100% - 40px); margin: 0 auto; height: 100%; position: relative; top: 10px; } .logo { position: absolute; top: 12px; left: 0; color: #fff; font-weight: bold; font-size: 22px; text-decoration: none; margin-left: -49px; } .logo-box { background: #fff; color: #0a66c2; padding: 2px 6px; margin-left: 2px; border-radius: 2px; } .nav-cta-wrapper { position: absolute; bottom: 25px; right: 0; display: flex; gap: 15px; } .nav-cta { width: 25px; height: 25px; border-radius: 50%; } /* ---------- Сетка (контент) ---------- */ .container { width: 1135px; max-width: calc(100% - 40px); margin: 15px auto; display: grid; grid-template-columns: 210px 1fr; /* левая колонка фикс, центральная гибкая */ grid-gap: 10px; /* уменьшено на 5px */ align-items: start; } .block { background: #fff; /* все блоки белые */ padding: 15px; border-radius: 5px; box-shadow: 0 1px 3px rgba(0,0,0,0.12); } /* Левая колонка */ .left-col { grid-row: span 2; padding-top: 10px; } .left-col h3 { margin: 0 0 6px 0; padding-left: 8px; letter-spacing: 3px;} .left-col p { margin: 0 0 12px 0; color:#666; } .left-col ul { padding-left: 18px; margin: 0; color:#333; } .left-col ul li { margin: 6px 0; } /* Центральная колонка */ .center-col { display: flex; flex-direction: column; } /* Большая обложка */ .cover { width: 100%; height: 300px; margin-bottom: 10px; /* уменьшено на 5px */ overflow: hidden; border-radius: 2px; } .cover img { width: 100%; height: 100%; object-fit: cover; display:block; } /* Маленький центральный блок — уже */ .center-small { margin-top: -10px; height: 40px; display: flex; align-items: center; justify-content: left; padding-left: 10px; text-align: center; background: #fff; /* белый фон */ border-top: 0px solid #bfc1c4; border-left: none; border-right: none; border-bottom: none; border-radius: 0 !important; margin-bottom: 10px; /* уменьшено на 5px */ } /* ---------- Нижний ряд ---------- */ .bottom-row { grid-column: 2; display: grid; grid-template-columns: 1fr 320px; grid-template-rows: auto auto; gap: 10px; margin-top: 2px; align-items: start; /* ВАЖНО */ } /* Левый блок внизу */ .bottom-left { grid-column: 1 / 2; grid-row: 1 / span 2; min-height: 150px; background: #fff; border-radius: 0; border: 1px solid #d8dce1; padding: 10px; } .right-small { display: flex; align-items: center; justify-content: left; min-height: 60px; width: 100%; background: #fff; border-radius: 0; border: 1px solid #d8dce1; padding: 10px; position: sticky; /* главное */ top: 75px; /* ниже фиксированной шапки */ margin-bottom: 1px; /* добавляем отступ снизу */ } /* Размещение правых блоков */ .bottom-row > .right-small:nth-of-type(1) { grid-column: 2; grid-row: 1; } .bottom-row > .right-small:nth-of-type(2) { grid-column: 2; grid-row: 2; } .bottom-row > .right-small:nth-of-type(3) { grid-column: 2; grid-row: 3; } /* Визуальные улучшения одинаковых блоков */ .bottom-left, .right-small, .center-small { box-shadow: 0 1px 3px rgba(0,0,0,0.08); } /* Мелкая адаптивность */ @media (max-width: 900px) { .container { grid-template-columns: 1fr; width: calc(100% - 30px); } .left-col { grid-row: auto; padding-top: 12px; } .bottom-row { grid-column: 1; grid-template-columns: 1fr; grid-template-rows: auto; } .bottom-left, .right-small { grid-column: 1; grid-row: auto; } .cover { height: 220px; } } /* ---------- Поиск в шапке ---------- */ .vk-search { position: absolute; left: 170px; top: 10px; } .vk-search input { width: 220px; height: 32px; padding: 0 10px 0 34px; /* место под лупу */ border-radius: 8px; border: 1px solid #2f477a; background: #fff; font-size: 14px; outline: none; } .vk-search input::placeholder { color: #888; } .vk-search input:focus { border-color: #1d3f72; } /* ---------- Лупа ---------- */ .vk-search-icon { position: absolute; left: 10px; top: 50%; width: 17px; height: 17px; transform: translateY(-50%); pointer-events: none; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23888'%3E%3Cpath d='M10 2a8 8 0 105.293 14.293l4.707 4.707 1.414-1.414-4.707-4.707A8 8 0 0010 2zm0 2a6 6 0 110 12 6 6 0 010-12z'/%3E%3C/svg%3E") no-repeat center; background-size: contain; } .blockbro { display: flex; flex-direction: column; gap: 10px; /* расстояние между блоками */ position: sticky; /* липкая колонка */ top: 85px; /* ниже фиксированной шапки */ align-self: start; /* обязательно для sticky */ } /* нижняя плашка */ #bottomInfoBar, #unverifiedEmail, .bottom-info-wrapper, .bottom-info-block { display: none !important; visibility: hidden !important; height: 0 !important; max-height: 0 !important; overflow: hidden !important; } div[id^="wp"], div[id^="wprdv"] { display: none !important; visibility: hidden !important; height: 0 !important; max-height: 0 !important; overflow: hidden !important; } iframe[style*="position:fixed"][style*="top:0"] { display: none !important; } :root { --btn-y: -41px; } /* ───── КОНТЕЙНЕР ───── */ .buttons-row { position: relative; display: flex; justify-content: flex-end; align-items: center; gap: 12px; /* расстояние между кнопками */ width: 100%; padding-top: 10px; padding-right: 35px; } /* ───── ОБЩАЯ КНОПКА ───── */ .btn { display: inline-flex; align-items: center; justify-content: center; padding: 8px 16px; min-height: 36px; font-family: 'Inter', sans-serif; font-size: 13px; font-weight: 700; border-radius: 999px; text-decoration: none; cursor: pointer; opacity: 0; transform: translate(var(--btn-x), calc(var(--btn-y) + 6px)) scale(0.96); animation: btnAppear 0.3s ease forwards; transition: background-color 0.2s ease, color 0.2s ease, transform 0.15s ease, box-shadow 0.15s ease; } /* ───── КНОПКА "ВОЙТИ" ───── */ .btn-login { --btn-x: 90px; color: #555; background-color: #f3f4f6; border: 1px solid #e5e7eb; } .btn-login:hover { background-color: #e5e7eb; color: #111; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.12); } /* ───── КНОПКА "ПРИСОЕДИНИТЬСЯ" ───── */ .btn-join { --btn-x: 85px; color: #fff; background-color: #3b5998; border: 1px solid #FFF; } .btn-join:hover { background-color: #4466ad; box-shadow: 0 4px 12px rgba(10, 102, 194, 0.3); } /* active */ .btn:active { transform: translate(var(--btn-x), var(--btn-y)) scale(0.97); } /* ───── АНИМАЦИЯ ───── */ @keyframes btnAppear { to { opacity: 1; transform: translate(var(--btn-x), var(--btn-y)) scale(1); } } :root { --user-shift-x: -40px; /* сдвиг всего блока */ --user-name-shift-x: 15px; /* сдвиг ника внутри блока */ --user-name-shift-y: -8px; /* поднять ник */ } /* контейнер пользователя */ .user-box { display: inline-flex; align-items: center; gap: 0px; cursor: pointer; position: relative; transform: translateX(var(--user-shift-x)); /* сдвиг всего блока */ } /* ник слева */ .user-name { max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 15px; font-weight: 600; color: #fff; display: inline-block; /* тут применяем переменные */ transform: translate(var(--user-name-shift-x), var(--user-name-shift-y)); transition: transform 0.2s ease; } </style> </head> <body> <!-- МЕНЮ --> <div class="header"> <div class="header-inner"> <a href="/" class="logo"> Bro <span class="logo-box">usite.pro</span></a> <form class="vk-search" action="/search" method="get"> <span class="vk-search-icon"></span> <input type="text" name="q" placeholder="Поиск" /> </form> <!-- Колокольчик --> <style> :root{ --bell-shift-x: 410px; --bell-shift-y: 8px; } /* ОБЛАСТЬ */ .vk-alerts{ position: relative; display: inline-block; transform: translate(var(--bell-shift-x), var(--bell-shift-y)); font-family: "Open Sans", Tahoma, sans-serif; } /* КОЛОКОЛ */ .vk-bell{ position: relative; cursor: pointer; } .vk-bell svg{ width: 35px; height: 35px; fill: #fff; } /* СЧЁТЧИК */ .vk-bell-count{ position: absolute; top: -2px; right: -0px; min-width: 16px; height: 16px; background: #f44336; color: #fff; font-size: 11px; font-weight: 700; line-height: 16px; text-align: center; border-radius: 50%; padding: 0 4px; } /* ВСПЛЫВАЮЩЕЕ ОКНО */ .vk-alerts-wrap{ position: absolute; top: 58px; right: -240px; width: 280px; opacity: 0; pointer-events: none; transform: translateY(-10px); transition: opacity .25s, transform .25s; z-index: 9999; } .vk-alerts-wrap.active{ opacity: 1; pointer-events: auto; transform: translateY(0); } /* ОКНО */ .vk-alerts-win{ position: relative; /* важно для псевдоэлемента */ background: #fff; box-shadow: 0 10px 15px rgba(0,0,0,.08); border-radius: 6px; overflow: visible; /* чтобы хвостик не обрезался */ transition: box-shadow .25s; } /* ХВОСТИК ВВЕРХУ СЛЕВА */ .vk-alerts-win::before{ content: ""; position: absolute; top: -8px; /* смещаем вверх за пределы окна */ left: 12px; /* отступ слева */ width: 0; height: 0; border-left: 8px solid transparent; border-right: 8px solid transparent; border-bottom: 8px solid #fff; /* цвет хвостика совпадает с фоном окна */ opacity: 0; transform: translateY(-4px); transition: opacity .25s, transform .25s; z-index: 10; } .vk-alerts-wrap.active .vk-alerts-win::before{ opacity: 1; transform: translateY(0); } /* ЭЛЕМЕНТ */ .vk-alert-item{ padding: 18px 22px; font-size: 13px; line-height: 18px; border-bottom: 1px solid #f0f0f0; } .vk-alert-item:last-child{ border-bottom: none; } /* ЗАГОЛОВОК */ .vk-alert-title{ font-weight: 700; margin-bottom: 6px; } /* ТЕКСТ */ .vk-alert-text{ color: #555; } /* ССЫЛКА */ .vk-alert-link{ display: inline-block; margin-top: 10px; color: #F44336; transition: .3s; text-decoration: none; } .vk-alert-link:hover{ opacity: .7; } /* МАЛЕНЬКАЯ АВАТАРКА И НИК В ЛС */ .vk-alert-pm{ display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-bottom: 1px solid #eee; } .vk-alert-pm img{ width: 30px; height: 30px; border-radius: 50%; object-fit: cover; } .vk-alert-pm .pm-nick{ font-weight: 600; font-size: 13px; color: #0a66c2; } @keyframes shakeBell { 0% { transform: rotate(0deg); } 10% { transform: rotate(-5deg); } 20% { transform: rotate(5deg); } 30% { transform: rotate(-4deg); } 40% { transform: rotate(4deg); } 50% { transform: rotate(-3deg); } 60% { transform: rotate(3deg); } 70% { transform: rotate(-2deg); } 80% { transform: rotate(2deg); } 90% { transform: rotate(-1deg); } 100% { transform: rotate(0deg); } } .vk-bell.shake { animation: shakeBell 1s cubic-bezier(0.4, 0, 0.2, 1); } </style> <!-- HTML --> <div class="vk-alerts" id="vkAlerts"> <div class="vk-bell" id="vkBell"> <svg viewBox="0 0 24 24"> <path d="M12 22a2 2 0 0 0 2-2h-4a2 2 0 0 0 2 2zm6-6V11a6 6 0 1 0-12 0v5l-2 2v1h16v-1l-2-2z"/> </svg> <div class="vk-bell-count" id="vkBellCount">2</div> </div> <div class="vk-alerts-wrap" id="vkPopup"> <div class="vk-alerts-win" id="vkPopupContent"> <!-- Условный блок авторизации --> <?if(!$USER_LOGGED_IN$)?> <div class="vk-alert-item"> <div class="vk-alert-title"> <!-- Иконка замка перед текстом --> <svg style="width:16px;height:16px;vertical-align:middle;margin-right:6px;" viewBox="0 0 24 24" fill="#f44336"> <path d="M12 17a2 2 0 0 0 2-2v-2a2 2 0 1 0-4 0v2a2 2 0 0 0 2 2zm6-6V9a6 6 0 0 0-12 0v2H4v12h16V11h-2zM8 9a4 4 0 0 1 8 0v2H8V9z"/> </svg> Вы не авторизованы </div> <div class="vk-alert-text">Присоединяйтесь к сообществу 🙂</div> <a class="vk-alert-link" href="$LOGIN_LINK$"> <!-- Иконка пальца перед текстом --> <svg style="width:14px;height:14px;vertical-align:middle;margin-right:4px;" viewBox="0 0 24 24" fill="#f44336"> <path d="M9 21h2v-7h3.586l-1.293 1.293 1.414 1.414L18 12l-5.293-5.707-1.414 1.414L14.586 11H12V4H9v17z"/> </svg> Авторизация </a> </div> <?endif?> <div class="vk-alert-item vk-alert-vacancy"> <div class="vk-alert-title"> <span style="margin-right:6px;">👉</span>Вакансии </div> <div class="vk-alert-text">Требуется контент-менеджер</div> <a class="vk-alert-link" href="#"> <!-- Иконка пальца перед текстом --> <svg style="width:14px;height:14px;vertical-align:middle;margin-right:4px;" viewBox="0 0 24 24" fill="#f44336"> <path d="M9 21h2v-7h3.586l-1.293 1.293 1.414 1.414L18 12l-5.293-5.707-1.414 1.414L14.586 11H12V4H9v17z"/> </svg> Смотреть вакансии </a> </div> <!-- Здесь будут личные сообщения (добавляются динамически JS) --> </div> </div> </div> <script> const bell = document.getElementById("vkBell"); const popup = document.getElementById("vkPopup"); const popupContent = document.getElementById("vkPopupContent"); const bellCount = document.getElementById("vkBellCount"); const CHECK_DELAY = 15000; const DEFAULT_AVA = "/files/person.png"; const RECIPIENT = "<?=$USER_NAME$?>"; // текущий пользователь // Открытие / закрытие окна bell.addEventListener("click", e => { e.stopPropagation(); popup.classList.toggle("active"); }); // Закрытие по клику вне блока document.addEventListener("click", e => { if(!document.getElementById("vkAlerts").contains(e.target)){ popup.classList.remove("active"); } }); // Подгружаем аватар async function fetchAvatar(profileUrl){ if(!profileUrl) return DEFAULT_AVA; try{ const html = await fetch(profileUrl + (profileUrl.includes('?')?'&':'?')+'r='+Math.random()) .then(r=>r.text()); const div = document.createElement('div'); div.innerHTML = html; const bg = div.querySelector(".profile-photo")?.style.backgroundImage || ""; const m = bg.match(/url\(['"]?(.*?)['"]?\)/i); return m ? m[1] : DEFAULT_AVA; }catch(e){ return DEFAULT_AVA; } } <?if($USER_LOGGED_IN$)?> // Авторизованные пользователи: уведомления async function checkPM(){ try{ popupContent.querySelectorAll(".vk-alert-pm").forEach(el => el.remove()); const html = await fetch("/index/14?" + Math.random()).then(r=>r.text()); const div = document.createElement('div'); div.innerHTML = html; const unread = div.querySelectorAll("b.unread"); unread.forEach(async b => { const tr = b.closest("tr, div, li"); const nickLink = tr.querySelector("a[href*='/index/8-0-']") || {href:"#", textContent:"Пользователь"}; const sender = nickLink.textContent.trim(); let href = nickLink.getAttribute("href") || "#"; if(href.startsWith('/')) href = location.origin + href; const avatar = await fetchAvatar(href); const title = b.textContent.trim(); let description = "Пришло новое сообщение. Можно прочитать чуть позже."; if(title.includes("Предложение дружбы")){ description = `Пользователь "${sender}" добавил вас в друзья. Вы также можете его добавить, перейдя по этой ссылке. Всего хорошего!`; } else if(title.includes("Новый подарок") || title.includes("Новая награда")){ description = `Пользователь "${sender}" сделал вам подарок. Всего наилучшего!`; } else if(title.includes("Изменен уровень замечаний")){ description = `Здравствуйте, ${RECIPIENT}. Пользователь "${sender}" изменил вам уровень замечаний.`; } const pmDiv = document.createElement("div"); pmDiv.className = "vk-alert-pm vk-alert-item"; pmDiv.innerHTML = ` <img src="${avatar}" alt="${sender}"> <div style="display:flex;flex-direction:column;"> <span class="pm-nick" style="font-weight:600;font-size:13px;">${title}</span> <span style="font-size:12px;color:#555;margin-top:2px;">${description}</span> </div> `; pmDiv.addEventListener("click", ()=>{ window.location.href = href; }); popupContent.appendChild(pmDiv); }); // Обновляем счётчик bellCount.textContent = unread.length; }catch(e){ console.warn("Не удалось загрузить уведомления", e); } setTimeout(checkPM, CHECK_DELAY); } checkPM(); <?endif?> <?if(!$USER_LOGGED_IN$)?> // Неавторизованные пользователи: колокольчик дергается каждые 10 секунд setInterval(()=>{ bell.classList.add("shake"); setTimeout(()=>bell.classList.remove("shake"), 500); }, 5000); <?endif?> </script> <?if($USER_LOGGED_IN$)?> <!-- Блок уведомлений --> <div id="vkNotifyPanel" class="vk-notify"> <div class="vk-notify-header"> <span>Личные сообщения <b id="vkNotifyBadge">0</b></span> <button id="vkNotifyToggle">—</button> </div> <div class="vk-notify-body"> <div class="vk-notify-empty">Загрузка сообщений...</div> </div> </div> <style> /* ===== ВК-стиль уведомлений ===== */ .vk-notify { position: fixed; right: 20px; bottom: 20px; width: 340px; background:#fff; border-radius:12px; box-shadow:0 12px 28px rgba(0,0,0,.18); font-family: "Segoe UI","Roboto",-apple-system,BlinkMacSystemFont; z-index:99999; overflow:hidden; transition:height .3s ease, box-shadow .3s ease; } .vk-notify.expanded { height:400px; } .vk-notify.collapsed { height:48px; } .vk-notify:hover { box-shadow:0 16px 36px rgba(0,0,0,.25); } .vk-notify-header { background:#f5f6f8; padding:10px 14px; font-weight:600; font-size:14px; display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid #e3e4e8; cursor:pointer; } .vk-notify-header button { background:none; border:none; font-size:18px; cursor:pointer; color:#626d7a; } /* Бейдж сообщений */ #vkNotifyBadge { background:#5181b8; /* ВК синий */ color:#fff; border-radius:50%; padding:0 8px; font-size:12px; font-weight:600; min-width:20px; text-align:center; line-height:20px; display:inline-block; box-shadow:0 1px 3px rgba(0,0,0,0.3); transition: transform 0.3s; } #vkNotifyBadge.pulse { animation: pulseBadge 1s ease-in-out 2; } @keyframes pulseBadge { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.2); } } /* Тело уведомлений */ .vk-notify-body { max-height:352px; overflow-y:auto; background:#fff; transition: background .3s; padding:4px 0; } /* Стили скроллбара в стиле ВК */ .vk-notify-body::-webkit-scrollbar { width:6px; } .vk-notify-body::-webkit-scrollbar-track { background:#f0f0f0; border-radius:3px; } .vk-notify-body::-webkit-scrollbar-thumb { background:#c0c0c0; border-radius:3px; } .vk-notify-body::-webkit-scrollbar-thumb:hover { background:#a0a0a0; } /* Элементы списка сообщений */ .vk-item { display:flex; gap:10px; padding:10px 14px; border-bottom:1px solid #f0f1f4; cursor:pointer; border-radius:8px; transition: background .2s; } .vk-item:hover { background:#f5f7fa; } /* лёгкий цвет при hover */ /* Аватар */ .vk-item img { width:40px; height:40px; border-radius:50%; flex-shrink:0; box-shadow:0 1px 3px rgba(0,0,0,0.15); transition: none; } /* Заголовки и текст */ .vk-item-title { font-size:14px; font-weight:600; color:#2a5885; margin-bottom:2px; } .vk-item-text { font-size:13px; color:#555; margin-bottom:2px; } .vk-item-date { font-size:11px; color:#888; } .vk-notify-empty { padding:20px; font-size:13px; color:#777; text-align:center; } </style> <script> window.friends = window.friends || {}; (function($, friends){ const DEFAULT_AVA = "/files/person.png"; const CHECK_DELAY = 20000; // каждые 20 сек const panel = $("#vkNotifyPanel"); const body = panel.find(".vk-notify-body"); const badge = $("#vkNotifyBadge"); const toggleBtn = $("#vkNotifyToggle"); const STORAGE_KEY = "vkNotifyCollapsed"; // Восстанавливаем состояние из localStorage const isCollapsed = localStorage.getItem(STORAGE_KEY) === "1"; if(isCollapsed){ panel.addClass("collapsed"); toggleBtn.text("+"); } else { panel.addClass("expanded"); toggleBtn.text("—"); } // Сворачивание/разворачивание panel.find(".vk-notify-header").on("click", function(){ if(panel.hasClass("expanded")){ panel.removeClass("expanded").addClass("collapsed"); toggleBtn.text("+"); localStorage.setItem(STORAGE_KEY, "1"); } else { panel.removeClass("collapsed").addClass("expanded"); toggleBtn.text("—"); localStorage.setItem(STORAGE_KEY, "0"); } }); async function fetchAvatar(profileUrl){ if(!profileUrl) return DEFAULT_AVA; try{ const html = await $.get(profileUrl + (profileUrl.includes('?')?'&':'?')+'r='+Math.random()); const bg = $(html).find(".profile-photo").first().css("background-image") || ""; const m = bg.match(/url\(['"]?(.*?)['"]?\)/i); return m ? m[1] : DEFAULT_AVA; } catch(e){ return DEFAULT_AVA; } } async function loadMessages(){ try{ const html = await $.get("/index/14?" + Math.random()); const $h = $('<div>').append($.parseHTML(html)); const $unread = $h.find("b.unread"); badge.text($unread.length > 0 ? $unread.length : '0'); body.empty(); if(!$unread.length){ body.html('<div class="vk-notify-empty">Нет новых сообщений</div>'); return; } $unread.each(async function(){ const $msg = $(this); const $parent = $msg.closest("tr, div, li"); let title = $msg.text().trim() || "Новое сообщение"; const $nickLink = $parent.find("a[href*='/index/8-0-']").last(); let sender = $nickLink.text().trim() || "Пользователь"; let profileLink = $nickLink.attr("href"); if(profileLink && profileLink.startsWith('/')) profileLink = location.origin + profileLink; let avatar = await fetchAvatar(profileLink); const dateText = $parent.find("td:nth-child(3), .message-date, time").first().text().trim() || ""; const pmHref = $msg.parent().attr("href") || "#"; body.append(` <div class="vk-item" onclick="window.location.href='${pmHref}'"> <img src="${avatar}"> <div> <div class="vk-item-title">${sender}</div> <div class="vk-item-text">${title}</div> <div class="vk-item-date">${dateText}</div> </div> </div> `); }); } catch(e){ console.warn("Ошибка загрузки ЛС", e); body.html('<div class="vk-notify-empty">Ошибка загрузки сообщений</div>'); } } friends.checkPm = function(){ loadMessages(); setTimeout(friends.checkPm, CHECK_DELAY); }; friends.checkPm(); })(jQuery, window.friends); </script> <?else?> <!-- Гости ничего не видят --> <?endif?> <?if(!$USER_LOGGED_IN$)?> <div class="buttons-row"> <a href="$LOGIN_LINK$" class="btn btn-login">Войти</a> <a href="/index/3" class="btn btn-join">Присоединиться</a> </div> <?endif?> <?if($USER_LOGGED_IN$)?> <div class="nav-cta-wrapper"> <div class="nav-cta"> <!-- Пользователь --> <div class="user-box" id="userMenu"> <!-- НИК (слева, как в старом ВК) --> <span class="user-name">$USERNAME$</span> <!-- Аватар --> <img src="$USER_AVATAR_URL$" alt="$USERNAME$" class="user-avatar real-avatar" onerror="this.style.display='none'; this.nextElementSibling.style.display='block';"> <!-- Дефолтный аватар --> <img src="/.s/src/profile/img/profile_photo_thumbnail.png" alt="default" class="user-avatar default-avatar" style="display:none;"> <!-- Выпадающее меню --> <div class="user-dropdown"> <div class="user-dropdown-header"> <span class="username">$USERNAME$</span> </div> <a href="/index/14" class="dropdown-item icon-messages">Личные сообщения</a> <a href="/index/11" class="dropdown-item icon-settings">Настройки</a> <a href="/index/8" class="dropdown-item icon-profile">Моя страница</a> <a href="/index/15" class="dropdown-item icon-users">Пользователи</a> <div class="dropdown-separator"></div> <a href="$LOGOUT_LINK$" class="dropdown-item icon-logout logout">Выход</a> </div> </div> </div> </div> <script> document.addEventListener("click", function (e) { const userBox = document.getElementById("userMenu"); if (!userBox) return; if (userBox.contains(e.target)) { userBox.classList.toggle("active"); } else { userBox.classList.remove("active"); } }); </script> <?endif?> </div> </div> </div> </div> </div> <!-- КОНТЕНТ --> <div class="container"> <!-- Левая колонка --> <div class="left-col block"> $GLOBAL_MENU$ </div> <!-- Центральная колонка --> <div class="center-col"> <div class="cover block"> <img src="https://bro.usite.pro/img/photo01.jpg" alt="cover"> </div> <div class="center-small block"> Маленький блок под центральным$POWERED_BY$ </div> <div class="bottom-row"> <!-- Лента новостей --> $BODY$ <!-- Правая колонка (липкая) --> <div class="right-col blockbro"> <div class=""> $GLOBAL_BRO$ </div> $GLOBAL_TODAY$ <div class="right-small block"> Нижний блок справа 1 </div> <div class="right-small block"> Нижний блок справа 2 </div> <div class="right-small block"> Нижний блок справа 3 </div> </div> </div> </div> </div> </body> </html>
Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.