Дата: Четверг, 08.01.2026, 14:04 | Сообщение # 1 |
|
Написал: Узнаваемый
Автор темы
Мурчанн
не в сети
Сообщений: 162
По мере возможностей решил добавлять новые функции во всплывающее окно модуля «Друзья». Добавлена новая фишка: индикатор онлайн-статуса. Если пользователь онлайн, во внешнем блоке отображается зелёная точка; если офлайн — индикатор не показывается. Во всплывающем окне статус отображается под аватаркой красным или зелёным цветом, в зависимости от состояния. Изначально я планировал использовать только точки, но решил добавить цветовое выделение; возможно, в других версиях вернусь к более простому варианту. Также рассматриваю возможность реализации функции разделения друзей на онлайн и офлайн, аналогично тому, как это сделано во «ВКонтакте». Там нет всплывающего окна, и создание отдельной страницы для модуля друзей изначально показалось неправильным. Однако возможно, что в будущем я создам полноценную страницу для модуля, чтобы при клике в левом меню открывалась именно она, а не всплывающее окно. Скрипт был переписан, внесены изменения, а подход и логика работы немного изменены. Исходный код я представляю в развернутом виде — вы сами сможете упаковать его в CSS и JS. В нашем случае это рабочее решение, и когда вы увидите исходный код, станет понятно, как применяются функции.В скрипте используется скрытый контейнер для хранения списка онлайн-пользователей: Код
<!-- Скрытый контейнер со списком онлайн-пользователей --> <div id="uc-online-list" style="display:none;"> $ONLINE_USERS_LIST$ </div>
Этот контейнер изначально скрыт и служит как «хранилище» данных. Скрипт обращается к нему, чтобы получить актуальный список онлайн-пользователей и отобразить их в нужных блоках всплывающего окна или внешнего интерфейса. Такой подход позволяет отделить данные от визуальной части и облегчает работу с динамическим обновлением статусов.Исходник Код
<!-- MINI BLOCK ДРУЗЬЯ --> <div class="vk-old-block"> <div class="vk-old-header">Друзья</div> <div class="vk-old-footer" id="friends-count">0 друзей</div> <div class="vk-old-content"> <div class="vk-friends"></div> </div> </div> <style> /* ================= MINI BLOCK ================= */ .vk-old-block { max-width:420px; background:#fff; border:1px solid #d1d5da; font-family:Tahoma,Arial,sans-serif; font-size:12px; border-radius:16px; overflow:hidden; } .vk-old-header { background:linear-gradient(180deg,#5d82b9,#4a6fa8); color:#fff; font-weight:bold; font-size:16px; padding:10px 12px; text-align:left; border-bottom:1px solid #d1d5da; border-top-left-radius:16px; border-top-right-radius:16px; } .vk-old-content { padding:10px; } .vk-old-footer { background:#f7f7f7; border-top:1px solid #d1d5da; padding:6px 12px; color:#666; font-size:12px; cursor:pointer; text-align:left; border-bottom-left-radius:0px; border-bottom-right-radius:0px; } /* ================= MINI FRIENDS GRID ================= */ .vk-friends { display:grid; grid-template-columns:repeat(3, 1fr); gap:10px 10px; justify-items:center; } .vk-friend { text-align:center; cursor:pointer; } .vk-friend img { width:82px; height:82px; border-radius:50%; object-fit:cover; } .vk-friend span { display:block; margin-top:4px; font-size:12px; color:#1d2c45; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } /* При наведении — слегка больше яркости */ .vk-friend:hover { filter: brightness(1.45); /* затемнение */ transform: scale(1.00); /* лёгкое увеличение */ } /* ================= OVERLAY ================= */ .vk-ui-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.15); z-index: 9999; display: flex; align-items: center; justify-content: center; } .vk-ui-popup { width: 400px; /* чуть уже */ max-height: 190vh; /* максимум по высоте экрана */ height: auto; /* автоматическая высота */ background: #f5f6f8; border-radius: 24px; display: flex; flex-direction: column; overflow: visible; /* важно */ box-shadow: 0 30px 70px rgba(0,0,0,0.25); font-family: system-ui, -apple-system, Arial, sans-serif; border: 1px solid #d1d5da; padding: 0; } /* ================= HEADER ================= */ .vk-ui-header { padding: 18px 24px; display: flex; align-items: center; justify-content: space-between; /* "Друзья" слева, поиск справа */ background: rgba(93,130,185,0.9); /* стеклянный синий */ backdrop-filter: blur(14px) saturate(180%) brightness(1.05); -webkit-backdrop-filter: blur(14px) saturate(180%) brightness(1.05); border-bottom: 1px solid rgba(0,0,0,0.12); border-top-left-radius: 24px; border-top-right-radius: 24px; } /* Заголовок "Друзья" */ .vk-ui-title { font-size: 20px; font-weight: 600; margin: 0; color: #ffffff; } /* Поиск справа */ .vk-ui-search { flex-shrink: 0; } .vk-ui-search input { width: 160px; padding: 8px 14px 8px 36px; /* минимальные отступы */ border-radius: 14px; border: none; outline: none; font-size: 15px; background: rgba(255,255,255,0.95); color: #1d2c45; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' fill='%2390a4c0'%3E%3Cpath d='M17.5 16l-4.3-4.3a7 7 0 1 0-1.4 1.4L16 17.5zM7 12a5 5 0 1 1 0-10 5 5 0 0 1 0 10z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: 10px center; background-size: 18px 18px; } /* ================= INNER BLOCK ================= */ .vk-ui-inner { background: #f9f9f9; /* светло-серый фон для внутреннего блока */ padding: 14px; /* отступы внутри popup */ } /* Внутренний белый блок */ .vk-ui-inner .vk-ui-subblock { background: #ffffff; /* сам белый блок внутри */ padding: 12px; /* отступы внутри блока */ border-radius: 12px; border: 1px solid #e4e8f0; margin-top: 12px; /* небольшой отступ от верхнего серого фона */ } /* ================= FRIEND LIST ================= */ .vk-ui-list { max-height: 62vh; /* больше места */ overflow-y: auto; /* прокрутка внутри */ padding: 12px; margin: 12px; background: #fff; border-radius: 12px; border: 1px solid #e4e8f0; box-sizing: border-box; flex: 1; /* тянется на всю доступную высоту */ } /* Скрываем скролл для Webkit-браузеров (Chrome, Edge, Safari) */ .vk-ui-list::-webkit-scrollbar { width: 0; height: 0; } /* ROW */ .vk-ui-row { display: flex; align-items: center; padding: 12px 16px; border-bottom: 1px solid #e4e8f0; cursor: pointer; transition: background 0.2s ease; } .vk-ui-row:last-child { border-bottom: none; } .vk-ui-row:hover { background: #f5f7fb; } /* ========================= AVATAR + ONLINE STATUS ========================= */ .vk-ui-ava { position: relative; width: 88px; height: 88px; margin-bottom: 12px; /* расстояние под аватаркой до текста/статуса */ } /* Аватарка */ .vk-ui-ava img { width: 88px; height: 88px; border-radius: 14px; object-fit: cover; box-shadow: 0 4px 14px rgba(0,0,0,0.25); } .vk-ui-ava { margin-bottom: 16px; /* больше пространства под аватаркой */ } .vk-ui-ava .statusOnline { margin: 12px auto 0 auto; /* поднимаем статус ниже аватарки */ } .vk-ui-ava .statusOffline { margin: 12px auto 0 auto; /* поднимаем статус ниже аватарки */ } /* STATUS LETTER (кружок с буквой) */ .vk-ui-letter.friend { background: #6b93c7; } /* Друг */ .vk-ui-letter.idol { background: #c84b4b; } /* Кумир */ .vk-ui-letter.family { background: #4a9e4a; } /* Семья */ .vk-ui-letter.acquaintance { background: #8a8a8a; }/* Знакомый */ .vk-ui-letter.buddy { background: #d18c3b; } /* Приятель */ .vk-ui-letter.nostatus { background: #bbbbbb; } /* Без статуса */ /* BADGE (текстовый статус) */ .vk-ui-badge.friend { background: #6b93c7; } .vk-ui-badge.idol { background: #c84b4b; } .vk-ui-badge.family { background: #4a9e4a; } .vk-ui-badge.acquaintance { background: #8a8a8a; } .vk-ui-badge.buddy { background: #d18c3b; } .vk-ui-badge.nostatus { background: #bbbbbb; } /* STATUS LETTER-2 */ .vk-ui-letter { position: absolute; bottom: -4px; left: -4px; width: 26px; height: 26px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 14px; color: #fff; } .vk-ui-letter.friend { background: #6b93c7; } .vk-ui-letter.idol { background: #c84b4b; } /* INFO */ .vk-ui-info { flex: 1; margin-left: 14px; } .vk-ui-name { font-size: 20px; font-weight: 600; color: #1d2c45; } .vk-ui-badge { margin-top: 6px; padding: 6px 14px; border-radius: 999px; font-size: 14px; display: inline-block; color: #fff; } .vk-ui-badge.friend { background: #6b93c7; } .vk-ui-badge.idol { background: #c84b4b; } .vk-ui-badge.nostatus { background: #8a8a8a; } /* ARROW */ .vk-ui-arrow { font-size: 28px; color: #b6bfd6; } /* Кружок с числом друзей */ .vk-ui-title-count { display: inline-flex; align-items: center; justify-content: center; width: 27px; /* размер кружка */ height: 27px; margin-left: 0px; /* отступ от слова "Друзья" */ border-radius: 50%; background: #fff; /* белый фон */ border: 1px solid #7e7f80; /* белая обводка */ color: #7e7f80; /* серый цвет текста */ font-size: 14px; font-weight: 700; } /* ============================== Мини-грид — точки на аватарке (только онлайн) ============================== */ .vk-friend { position: relative; /* точка позиционируется относительно аватарки */ display: inline-block; } .vk-friend img { border-radius: 50%; /* аватарка круглая */ display: block; } /* ==== Точка онлайн ==== */ .vk-friend .statusDot { position: absolute; width: 16px; /* размер точки */ height: 16px; border-radius: 50%; /* круглая точка */ border: 2px solid #fff; /* белая обводка */ box-shadow: 0 0 2px rgba(0,0,0,0.5); bottom: 18px; /* вертикальное положение */ right: 12px; /* смещение вправо */ } /* Цвет точки только для онлайн */ .vk-friend .onlineDot { background-color: #3eb749; /* зелёная */ } /* Убираем точку, если офлайн */ .vk-friend .offlineDot { display: none; } /* ============================== Всплывающее окно — аватар и статус ============================== */ .vk-ui-ava { text-align: center; position: relative; margin-bottom: 22px; /* отступ под аватаркой */ } /* ==== Точка статуса во всплывающем окне ==== */ .vk-ui-ava .statusDot { position: absolute; width: 14px; /* размер точки */ height: 14px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 2px rgba(0,0,0,0.5); bottom: 6px; /* регулируем вертикальное положение */ right: 6px; /* смещение вправо */ } /* Цвет точки по статусу */ .vk-ui-ava .onlineDot { background-color: #3eb749; } /* онлайн зелёная */ .vk-ui-ava .offlineDot { background-color: #ff3b3b; } /* офлайн красная */ /* ==== Статус текстом под аватаркой ==== */ .vk-ui-ava .statusOnline { display: block; margin: 6px auto 0 auto; /* вертикальное положение, можно регулировать */ padding: 2px 8px; font-size: 10px; font-weight: bold; text-transform: uppercase; color: #fff; background-color: #3eb749; border-radius: 4px; text-align: center; width: fit-content; } .vk-ui-ava .statusOffline { display: block; margin: 6px auto 0 auto; /* вертикальное положение, можно регулировать */ padding: 2px 8px; font-size: 10px; font-weight: bold; text-transform: uppercase; color: #fff; background-color: #ff3b3b; border-radius: 4px; text-align: center; width: fit-content; } /* ============================== Настройки ============================== */ /* Размер точки */ .vk-ui-ava .statusDot { width: 16px; height: 16px; } /* Смещение точки */ .vk-ui-ava .statusDot { bottom: 10px; right: 10px; } </style> <!-- Скрытый контейнер со списком онлайн-пользователей --> <div id="uc-online-list" style="display:none;"> $ONLINE_USERS_LIST$ </div> <script> $(function(){ <?if(!$USER_LOGGED_IN$)?> $('#friends-count').text('Друзей нет'); $('.vk-friends').empty(); return; <?else?> const USER_ID = '$_USER_ID$'; const CACHE_KEY = 'friends_' + USER_ID; const CACHE_TIME_KEY = CACHE_KEY + '_time'; const CACHE_TTL = 24*60*60*1000; const $container = $('.vk-friends'); const $counter = $('#friends-count'); const DEFAULT_AVA = '/.s/src/profile/img/profile_photo_thumbnail.png'; let allFriends = []; /* ==== MINI GRID ==== */ function renderMini(friends){ const onlineHtml = $('#uc-online-list').text().toLowerCase(); // список онлайн $container.empty(); friends.slice(0,6).forEach(f=>{ const isOnline = onlineHtml.includes(f.nick.toLowerCase()); const statusDot = `<span class="statusDot ${isOnline ? 'onlineDot' : 'offlineDot'}"></span>`; const card = $(` <div class="vk-friend" title="${f.nick}"> <img src="${f.ava}"> <span>${f.nick}</span> ${statusDot} </div> `); card.on('click',()=>location.href=f.profile); $container.append(card); }); $counter.html(`${friends.length} друзей`); } /* ==== POPUP ==== */ function showPopup(){ if(!allFriends.length) return; const overlay = $('<div class="vk-ui-overlay"></div>'); const popup = $(` <div class="vk-ui-popup"> <div class="vk-ui-header"> <div class="vk-ui-title"> Друзья <span class="vk-ui-title-count">${allFriends.length}</span> </div> <div class="vk-ui-search"> <input type="text" placeholder="Поиск друзей" id="friend-search"> </div> </div> <div class="vk-ui-list"></div> </div> `); const list = popup.find('.vk-ui-list'); const onlineHtml = $('#uc-online-list').text().toLowerCase(); function renderList(filter=''){ list.empty(); allFriends .filter(f => f.nick.toLowerCase().includes(filter.toLowerCase())) .forEach(f=>{ let badge = 'Без статуса', cls = 'nostatus', letter = ''; const g = f.group.toLowerCase(); if(g.includes('кумир')) { badge='Кумир'; cls='idol'; letter='К'; } else if(g.includes('друг')) { badge='Друг'; cls='friend'; letter='Д'; } else if(g.includes('сем')) { badge='Семья'; cls='family'; letter='С'; } else if(g.includes('знаком')) { badge='Знакомый'; cls='acquaintance'; letter='З'; } else if(g.includes('прият')) { badge='Приятель'; cls='buddy'; letter='П'; } else { badge = f.group || 'Без статуса'; cls='nostatus'; letter=''; } const isOnline = onlineHtml.includes(f.nick.toLowerCase()); const statusBadge = isOnline ? '<div class="statusOnline">ONLINE</div>' : '<div class="statusOffline">OFFLINE</div>'; const row = $(` <div class="vk-ui-row ${cls}"> <div class="vk-ui-ava"> <img src="${f.ava}"> ${letter ? `<span class="vk-ui-letter ${cls}">${letter}</span>` : ''} ${statusBadge} </div> <div class="vk-ui-info"> <div class="vk-ui-name">${f.nick}</div> <div class="vk-ui-badge ${cls}">${badge}</div> </div> <div class="vk-ui-arrow">›</div> </div> `); row.on('click',()=>location.href=f.profile); list.append(row); }); } renderList(); popup.find('#friend-search').on('input', function(){ renderList($(this).val()); }); overlay.on('click', e => { if(e.target===overlay[0]) overlay.remove(); }); overlay.append(popup); $('body').append(overlay); } /* ==== LOAD FRIENDS ==== */ function fetchFriends(){ const cached = localStorage.getItem(CACHE_KEY); const time = localStorage.getItem(CACHE_TIME_KEY); if(cached && time && Date.now()-time < CACHE_TTL){ allFriends = JSON.parse(cached); renderMini(allFriends); } $.get('/blog/0-0-1-0-17-'+USER_ID, html=>{ const $tmp = $('<div>').html(html); allFriends = []; $tmp.find('.friend').each(function(){ const $el=$(this); let group = $el.find('.gr').text().trim() || 'Без статуса'; const g = group.toLowerCase(); if(!['друг','кумир','семья','знакомый','приятель'].some(k => g.includes(k))) { group = 'Приятель'; } allFriends.push({ nick: $el.find('.nick').text().trim(), ava: $el.find('.ava').text().trim() || DEFAULT_AVA, profile: $el.find('.url').text().trim(), group: group }); }); localStorage.setItem(CACHE_KEY, JSON.stringify(allFriends)); localStorage.setItem(CACHE_TIME_KEY, Date.now()); renderMini(allFriends); }); } $counter.css('cursor','pointer').attr('title','Показать всех друзей').on('click', showPopup); fetchFriends(); <?endif?> }); </script>
Код
Небольшие детали , такие как статусы, поиск друзей или разделение на активных и неактивных пользователей — на самом деле делают модуль информативным, а значит, продвинутым. На платформе Юкоз с этим всегда были сложности, поэтому приходится использовать различные хаки, или, проще говоря, «костыли».
Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
Дата: Четверг, 08.01.2026, 23:45 | Сообщение # 2 |
|
Написал: Узнаваемый
Автор темы
Мурчанн
не в сети
Сообщений: 162
Этот вариант отображения онлайн-статуса использует стандартную точку. Решение максимально простое, широко применяется на многих сайтах, а в скрипт были внесены лишь минимальные изменения.Код
<!-- MINI BLOCK ДРУЗЬЯ --> <div class="vk-old-block"> <div class="vk-old-header">Друзья</div> <div class="vk-old-footer" id="friends-count">0 друзей</div> <div class="vk-old-content"> <div class="vk-friends"></div> </div> </div> <style> /* ================= MINI BLOCK ================= */ .vk-old-block { max-width:420px; background:#fff; border:1px solid #d1d5da; font-family:Tahoma,Arial,sans-serif; font-size:12px; border-radius:16px; overflow:hidden; } .vk-old-header { background:linear-gradient(180deg,#5d82b9,#4a6fa8); color:#fff; font-weight:bold; font-size:16px; padding:10px 12px; text-align:left; border-bottom:1px solid #d1d5da; border-top-left-radius:16px; border-top-right-radius:16px; } .vk-old-content { padding:10px; } .vk-old-footer { background:#f7f7f7; border-top:1px solid #d1d5da; padding:6px 12px; color:#666; font-size:12px; cursor:pointer; text-align:left; border-bottom-left-radius:0px; border-bottom-right-radius:0px; } /* ================= MINI FRIENDS GRID ================= */ .vk-friends { display:grid; grid-template-columns:repeat(3, 1fr); gap:10px 10px; justify-items:center; } .vk-friend { text-align:center; cursor:pointer; } .vk-friend img { width:82px; height:82px; border-radius:50%; object-fit:cover; } .vk-friend span { display:block; margin-top:4px; font-size:12px; color:#1d2c45; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } /* При наведении — слегка больше яркости */ .vk-friend:hover { filter: brightness(1.45); /* затемнение */ transform: scale(1.00); /* лёгкое увеличение */ } /* ================= OVERLAY ================= */ .vk-ui-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.15); z-index: 9999; display: flex; align-items: center; justify-content: center; } .vk-ui-popup { width: 400px; /* чуть уже */ max-height: 190vh; /* максимум по высоте экрана */ height: auto; /* автоматическая высота */ background: #f5f6f8; border-radius: 24px; display: flex; flex-direction: column; overflow: visible; /* важно */ box-shadow: 0 30px 70px rgba(0,0,0,0.25); font-family: system-ui, -apple-system, Arial, sans-serif; border: 1px solid #d1d5da; padding: 0; } /* ================= HEADER ================= */ .vk-ui-header { padding: 18px 24px; display: flex; align-items: center; justify-content: space-between; /* "Друзья" слева, поиск справа */ background: rgba(93,130,185,0.9); /* стеклянный синий */ backdrop-filter: blur(14px) saturate(180%) brightness(1.05); -webkit-backdrop-filter: blur(14px) saturate(180%) brightness(1.05); border-bottom: 1px solid rgba(0,0,0,0.12); border-top-left-radius: 24px; border-top-right-radius: 24px; } /* Заголовок "Друзья" */ .vk-ui-title { font-size: 20px; font-weight: 600; margin: 0; color: #ffffff; } /* Поиск справа */ .vk-ui-search { flex-shrink: 0; } .vk-ui-search input { width: 160px; padding: 8px 14px 8px 36px; /* минимальные отступы */ border-radius: 14px; border: none; outline: none; font-size: 15px; background: rgba(255,255,255,0.95); color: #1d2c45; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' fill='%2390a4c0'%3E%3Cpath d='M17.5 16l-4.3-4.3a7 7 0 1 0-1.4 1.4L16 17.5zM7 12a5 5 0 1 1 0-10 5 5 0 0 1 0 10z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: 10px center; background-size: 18px 18px; } /* ================= INNER BLOCK ================= */ .vk-ui-inner { background: #f9f9f9; /* светло-серый фон для внутреннего блока */ padding: 14px; /* отступы внутри popup */ } /* Внутренний белый блок */ .vk-ui-inner .vk-ui-subblock { background: #ffffff; /* сам белый блок внутри */ padding: 12px; /* отступы внутри блока */ border-radius: 12px; border: 1px solid #e4e8f0; margin-top: 12px; /* небольшой отступ от верхнего серого фона */ } /* ================= FRIEND LIST ================= */ .vk-ui-list { max-height: 62vh; /* больше места */ overflow-y: auto; /* прокрутка внутри */ padding: 12px; margin: 12px; background: #fff; border-radius: 12px; border: 1px solid #e4e8f0; box-sizing: border-box; flex: 1; /* тянется на всю доступную высоту */ } /* Скрываем скролл для Webkit-браузеров (Chrome, Edge, Safari) */ .vk-ui-list::-webkit-scrollbar { width: 0; height: 0; } /* ROW */ .vk-ui-row { display: flex; align-items: center; padding: 12px 16px; border-bottom: 1px solid #e4e8f0; cursor: pointer; transition: background 0.2s ease; } .vk-ui-row:last-child { border-bottom: none; } .vk-ui-row:hover { background: #f5f7fb; } /* ========================= AVATAR + ONLINE STATUS ========================= */ .vk-ui-ava { position: relative; width: 88px; height: 88px; margin-bottom: 8px; /* расстояние под аватаркой до текста/статуса */ } /* Аватарка */ .vk-ui-ava img { width: 88px; height: 88px; border-radius: 14px; object-fit: cover; box-shadow: 0 4px 14px rgba(0,0,0,0.25); } /* STATUS LETTER (кружок с буквой) */ .vk-ui-letter.friend { background: #6b93c7; } /* Друг */ .vk-ui-letter.idol { background: #c84b4b; } /* Кумир */ .vk-ui-letter.family { background: #4a9e4a; } /* Семья */ .vk-ui-letter.acquaintance { background: #8a8a8a; }/* Знакомый */ .vk-ui-letter.buddy { background: #d18c3b; } /* Приятель */ .vk-ui-letter.nostatus { background: #bbbbbb; } /* Без статуса */ /* BADGE (текстовый статус) */ .vk-ui-badge.friend { background: #6b93c7; } .vk-ui-badge.idol { background: #c84b4b; } .vk-ui-badge.family { background: #4a9e4a; } .vk-ui-badge.acquaintance { background: #8a8a8a; } .vk-ui-badge.buddy { background: #d18c3b; } .vk-ui-badge.nostatus { background: #bbbbbb; } /* STATUS LETTER-2 */ .vk-ui-letter { position: absolute; bottom: -4px; left: -4px; width: 26px; height: 26px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 14px; color: #fff; } .vk-ui-letter.friend { background: #6b93c7; } .vk-ui-letter.idol { background: #c84b4b; } /* INFO */ .vk-ui-info { flex: 1; margin-left: 14px; } .vk-ui-name { font-size: 20px; font-weight: 600; color: #1d2c45; } .vk-ui-badge { margin-top: 6px; padding: 6px 14px; border-radius: 999px; font-size: 14px; display: inline-block; color: #fff; } .vk-ui-badge.friend { background: #6b93c7; } .vk-ui-badge.idol { background: #c84b4b; } .vk-ui-badge.nostatus { background: #8a8a8a; } /* ARROW */ .vk-ui-arrow { font-size: 28px; color: #b6bfd6; } /* Кружок с числом друзей */ .vk-ui-title-count { display: inline-flex; align-items: center; justify-content: center; width: 27px; /* размер кружка */ height: 27px; margin-left: 0px; /* отступ от слова "Друзья" */ border-radius: 50%; background: #fff; /* белый фон */ border: 1px solid #7e7f80; /* белая обводка */ color: #7e7f80; /* серый цвет текста */ font-size: 14px; font-weight: 700; } /* ============================== Мини-грид — точки на аватарке (только онлайн) ============================== */ .vk-friend { position: relative; /* точка позиционируется относительно аватарки */ display: inline-block; } .vk-friend img { border-radius: 50%; /* аватарка круглая */ display: block; } /* ==== Точка онлайн ==== */ .vk-friend .statusDot { position: absolute; width: 16px; /* размер точки */ height: 16px; border-radius: 50%; /* круглая точка */ border: 2px solid #fff; /* белая обводка */ box-shadow: 0 0 2px rgba(0,0,0,0.5); bottom: 18px; /* вертикальное положение */ right: 12px; /* смещение вправо */ } /* Цвет точки только для онлайн */ .vk-friend .onlineDot { background-color: #3eb749; /* зелёная */ } /* Убираем точку, если офлайн */ .vk-friend .offlineDot { display: none; } /* ============================== Всплывающее окно — аватар и статус ============================== */ .vk-ui-ava { text-align: center; position: relative; margin-bottom: 12px; /* отступ под аватаркой важно */ } /* ==== Точка статуса во всплывающем окне ==== */ .vk-ui-ava .statusDot { position: absolute; width: 14px; /* размер точки */ height: 14px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 2px rgba(0,0,0,0.5); bottom: 6px; /* регулируем вертикальное положение */ right: 6px; /* смещение вправо */ } /* Цвет точки по статусу */ .vk-ui-ava .onlineDot { background-color: #3eb749; } /* онлайн зелёная */ .vk-ui-ava .offlineDot { background-color: #ff3b3b; } /* офлайн красная */ /* ==== Статус текстом под аватаркой ==== */ .vk-ui-ava .statusOnline { display: block; margin: 6px auto 0 auto; /* вертикальное положение, можно регулировать */ padding: 2px 8px; font-size: 10px; font-weight: bold; text-transform: uppercase; color: #fff; background-color: #3eb749; border-radius: 4px; text-align: center; width: fit-content; } .vk-ui-ava .statusOffline { display: block; margin: 6px auto 0 auto; /* вертикальное положение, можно регулировать */ padding: 2px 8px; font-size: 10px; font-weight: bold; text-transform: uppercase; color: #fff; background-color: #ff3b3b; border-radius: 4px; text-align: center; width: fit-content; } /* ============================== Настройки ============================== */ /* Размер точки */ .vk-ui-ava .statusDot { width: 18px; height: 18px; } /* Смещение точки */ .vk-ui-ava .statusDot { bottom: -1px; right: -3px; } /* смещаем статус вверх или вниз */ </style> <!-- Скрытый контейнер со списком онлайн-пользователей --> <div id="uc-online-list" style="display:none;"> $ONLINE_USERS_LIST$ </div> <script> $(function(){ <?if(!$USER_LOGGED_IN$)?> $('#friends-count').text('Друзей нет'); $('.vk-friends').empty(); return; <?else?> const USER_ID = '$_USER_ID$'; const CACHE_KEY = 'friends_' + USER_ID; const CACHE_TIME_KEY = CACHE_KEY + '_time'; const CACHE_TTL = 24*60*60*1000; const $container = $('.vk-friends'); const $counter = $('#friends-count'); const DEFAULT_AVA = '/.s/src/profile/img/profile_photo_thumbnail.png'; let allFriends = []; /* ==== MINI GRID ==== */ function renderMini(friends){ const onlineHtml = $('#uc-online-list').text().toLowerCase(); // список онлайн $container.empty(); friends.slice(0,6).forEach(f=>{ const isOnline = onlineHtml.includes(f.nick.toLowerCase()); const statusDot = `<span class="statusDot ${isOnline ? 'onlineDot' : 'offlineDot'}"></span>`; const card = $(` <div class="vk-friend" title="${f.nick}"> <img src="${f.ava}"> <span>${f.nick}</span> ${statusDot} </div> `); card.on('click',()=>location.href=f.profile); $container.append(card); }); $counter.html(`${friends.length} друзей`); } /* ==== POPUP ==== */ function showPopup(){ if(!allFriends.length) return; const overlay = $('<div class="vk-ui-overlay"></div>'); const popup = $(` <div class="vk-ui-popup"> <div class="vk-ui-header"> <div class="vk-ui-title"> Друзья <span class="vk-ui-title-count">${allFriends.length}</span> </div> <div class="vk-ui-search"> <input type="text" placeholder="Поиск друзей" id="friend-search"> </div> </div> <div class="vk-ui-list"></div> </div> `); const list = popup.find('.vk-ui-list'); const onlineHtml = $('#uc-online-list').text().toLowerCase(); function renderList(filter=''){ list.empty(); allFriends .filter(f => f.nick.toLowerCase().includes(filter.toLowerCase())) .forEach(f=>{ let badge = 'Без статуса', cls = 'nostatus', letter = ''; const g = f.group.toLowerCase(); if(g.includes('кумир')) { badge='Кумир'; cls='idol'; letter='К'; } else if(g.includes('друг')) { badge='Друг'; cls='friend'; letter='Д'; } else if(g.includes('сем')) { badge='Семья'; cls='family'; letter='С'; } else if(g.includes('знаком')) { badge='Знакомый'; cls='acquaintance'; letter='З'; } else if(g.includes('прият')) { badge='Приятель'; cls='buddy'; letter='П'; } else { badge = f.group || 'Без статуса'; cls='nostatus'; letter=''; } const isOnline = onlineHtml.includes(f.nick.toLowerCase()); const statusBadge = `<span class="statusDot ${isOnline ? 'onlineDot' : 'offlineDot'}"></span>`; const row = $(` <div class="vk-ui-row ${cls}"> <div class="vk-ui-ava"> <img src="${f.ava}"> ${letter ? `<span class="vk-ui-letter ${cls}">${letter}</span>` : ''} ${statusBadge} </div> <div class="vk-ui-info"> <div class="vk-ui-name">${f.nick}</div> <div class="vk-ui-badge ${cls}">${badge}</div> </div> <div class="vk-ui-arrow">›</div> </div> `); row.on('click',()=>location.href=f.profile); list.append(row); }); } renderList(); popup.find('#friend-search').on('input', function(){ renderList($(this).val()); }); overlay.on('click', e => { if(e.target===overlay[0]) overlay.remove(); }); overlay.append(popup); $('body').append(overlay); } /* ==== LOAD FRIENDS ==== */ function fetchFriends(){ const cached = localStorage.getItem(CACHE_KEY); const time = localStorage.getItem(CACHE_TIME_KEY); if(cached && time && Date.now()-time < CACHE_TTL){ allFriends = JSON.parse(cached); renderMini(allFriends); } $.get('/blog/0-0-1-0-17-'+USER_ID, html=>{ const $tmp = $('<div>').html(html); allFriends = []; $tmp.find('.friend').each(function(){ const $el=$(this); let group = $el.find('.gr').text().trim() || 'Без статуса'; const g = group.toLowerCase(); if(!['друг','кумир','семья','знакомый','приятель'].some(k => g.includes(k))) { group = 'Приятель'; } allFriends.push({ nick: $el.find('.nick').text().trim(), ava: $el.find('.ava').text().trim() || DEFAULT_AVA, profile: $el.find('.url').text().trim(), group: group }); }); localStorage.setItem(CACHE_KEY, JSON.stringify(allFriends)); localStorage.setItem(CACHE_TIME_KEY, Date.now()); renderMini(allFriends); }); } $counter.css('cursor','pointer').attr('title','Показать всех друзей').on('click', showPopup); fetchFriends(); <?endif?> }); </script>
Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.