Список пользователей

1
Админ
Постов: 162
2
Элита
Постов: 45
3
VIP
Постов: 35
4
Проверенные
Постов: 31
5
Проверенные
Постов: 30
6
Пользователи
Постов: 27
7
VIP
Постов: 26
8
Пользователи
Постов: 24

  • Страница 1 из 1
  • 1
Макет шаблона социальная сеть - уведомления V 5.2 uCoz
Дата: Воскресенье, 28.12.2025, 19:22 | Сообщение # 1 | | Написал: Узнаваемый
Автор темы
Мурчанн не в сети
        Сообщений:162
         Регистрация:20.10.2016

Двигаемся вперёд, совершенствуя систему уведомлений и расширяя возможности детальной шаблонизации. На данном этапе мы внедряем скрипты, которые позволяют отображать различные типы уведомлений по всему сайту. Это лишь первые наброски, требующие постоянной доработки, уточнения и визуальной проработки. Шаблон становится всё более продуманным, а мы максимально используем потенциал платформы 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
         Регистрация:20.10.2016

Исходник шаблона (скрипты внутри)

Код
<!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>


Мурчанн

Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
  • Страница 1 из 1
  • 1
Поиск: