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

1
Админ
Постов: 211
2
VIP
Постов: 72
3
Элита
Постов: 50
4
Проверенные
Постов: 35
5
VIP
Постов: 35
6
Проверенные
Постов: 32
7
Пользователи
Постов: 31
8
Проверенные
Постов: 29

  • Страница 1 из 1
  • 1
Модуль ВК «Подарки» Ucoz v .10.0 финальная версия
Дата: Понедельник, 09.02.2026, 18:13 | Сообщение # 1 | | Написал: Узнаваемый
Автор темы
Мурчанн не в сети
        Сообщений:211
         Регистрация:20.10.2016

Скрипт не является идеальным, однако он работает достаточно стабильно.

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

Есть недостатки.

Когда пользователи дарят одинаковые подарки, платформа объединяет их в одну карточку, при этом под подарком отображаются разные записи от разных отправителей. Скрипт пока не способен корректно разделять такие случаи, поскольку его логика устроена иначе. Для решения этой задачи требуется более сложная и продвинутая обработка данных, а сам скрипт уже достаточно объёмный.

Я постарался максимально оптимизировать его, чтобы снизить нагрузку на системные ресурсы, но любая дополнительная логика неизбежно влияет на производительность. Возможно, в будущем я найду способ улучшить обработку объединённых подарков. А пока, чтобы избежать подобных неточностей, рекомендуется по возможности не отправлять одинаковые подарки.

Этот модуль не просто декоративный блок, а самостоятельная система, которая незаметно для пользователя управляет загрузкой и отображением подарков. Его работа построена по принципу «минимум действий максимум результата, где каждая операция выполняется только тогда, когда это действительно необходимо.



Сразу после открытия страницы скрипт не спешит обращаться к серверу. В первую очередь он проверяет локальный кэш браузера. Если данные уже были сохранены ранее, блок подарков мгновенно заполняется — пользователь сразу видит количество подарков и последние из них, без ожидания загрузки. Это создаёт ощущение моментальной работы страницы.

Далее скрипт запускает тихую фоновую проверку , он обращается к системе и запрашивает только одно значение: общее количество подарков. Никакие изображения, профили или комментарии в этот момент не загружаются. Это ключевой момент оптимизации: если число подарков не изменилось, скрипт полностью прекращает активность и «засыпает», не создавая нагрузки ни на браузер, ни на систему.

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

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

Таким образом, модуль действует как фоновый наблюдатель: большую часть времени он бездействует, не расходуя ресурсы, и активируется только тогда, когда действительно происходят изменения. Именно эта логика делает его быстрым, экономичным и незаметным для пользователя.

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

Внутренний механизм останется современным и обновлённым, а визуальное оформление будет соответствовать вашему привычному восприятию. Таким образом, функциональность сохраняется, а дизайн остаётся знакомым и комфортным.

Исходник

Код
<!-- ======= Блок подарков ======= -->
<div class="gifts-block">
<div class="gifts-header">Подарки</div>
<div class="gifts-footer" id="gifts-count">Загрузка…</div>
<div class="gifts-container" id="gifts-container"></div>
</div>

<link rel="stylesheet" href="/css/vk-gifts.css" media="all">

<script>
(async function() {
'use strict';

const container = document.getElementById('gifts-container');
const counter = document.getElementById('gifts-count');
const userId = <?$JSENCODE$($USER_ID$)?> || 0;

if (!userId) return;

const STORAGE_KEY = `giftsData_${userId}`;

let allGifts = [];
let cachedCount = -1;

// ──────────────────────────────────────────────
// Стили (оригинальные, без изменений)
// ──────────────────────────────────────────────
const style = document.createElement('style');
style.innerHTML = `
.gifts-block {background:#fff;border-radius:2px;box-shadow:0 2px 12px rgba(0,0,0,0.1);overflow:hidden;margin:8px 0;position:relative}
.gifts-header {background:linear-gradient(135deg,#4a76a8,#5a88c0);color:#fff;padding:12px 16px;font-weight:600;font-size:14px}
#gifts-container {display:flex;gap:5px;justify-content:center;padding:16px 16px 28px;flex-wrap:wrap;position:relative}
.gift-card {width:86px;height:100px;border-radius:12px;overflow:hidden;transition:transform .25s ease,box-shadow .25s ease;cursor:pointer}
.gift-card img {width:180%;height:140%;object-fit:contain}
.gift-card:hover {transform:scale(1.06);box-shadow:0 10px 25px rgba(74,118,168,0.3)}
.show-all-btn {position:absolute;bottom:0;right:16px;background:#4a76a8;color:white;font-size:12px;font-weight:600;padding:6px 14px;border-radius:8px 8px 0 0;border:none;box-shadow:0 -3px 8px rgba(0,0,0,0.12);cursor:pointer;transition:all 0.2s ease;z-index:2}
.show-all-btn:hover {background:#3b5e8c;box-shadow:0 -4px 12px rgba(0,0,0,0.18)}
.show-all-btn:active {transform:translateY(1px)}
.gifts-popup-overlay {position:fixed;inset:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:9999;backdrop-filter:blur(5px)}
.gifts-popup {background:#fff;width:520px;max-height:92vh;border-radius:10px;overflow:hidden;box-shadow:0 20px 70px rgba(0,0,0,0.45);border:1px solid #d0d9e4;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}
.gifts-popup-header {background:#4a76a8;color:white;padding:14px 20px;font-size:17px;font-weight:600;display:flex;justify-content:space-between;align-items:center}
.gifts-popup-header span {cursor:pointer;font-size:26px;opacity:0.85}
.gifts-popup-header span:hover {opacity:1}
.gifts-popup-list {padding:16px;overflow-y:auto;max-height:68vh;background:#f9fbff}
.vk-gift {padding:14px 0;border-bottom:1px solid #e8ecf2;transition:background 0.2s}
.vk-gift:hover {background:#f0f4fa}
.vk-user img {width:80px;height:80px;border-radius:50%;border:3px solid #4a76a8;object-fit:cover;transition:all 0.28s ease;box-shadow:0 3px 10px rgba(0,0,0,0.12)}
.vk-user:hover img {transform:scale(1.12);box-shadow:0 8px 24px rgba(74,118,168,0.4);filter:brightness(1.1)}
.vk-user {display:flex;gap:16px;align-items:center;margin-bottom:10px}
.vk-user .name {font-weight:700;color:#2c3e50;font-size:15.5px}
.vk-user .name a {color:#2c3e50;text-decoration:none}
.vk-user .name a:hover {color:#4a76a8;text-decoration:underline}
.vk-user .recipient-label {font-size:12px;color:#7f8c9d;margin-top:3px}
.vk-user .date {font-size:12.5px;color:#95a5a6}
.vk-user .date-loading {color:#aaa;font-style:italic}
.vk-comment {margin-top:8px;font-size:14px;color:#34495e;background:#ecf4ff;padding:10px 14px;border-radius:10px;border-left:4px solid #4a76a8}
.vk-gift-img {text-align:center;margin:14px 0}
.vk-gift-img img {max-width:260px;border-radius:12px;box-shadow:0 5px 18px rgba(0,0,0,0.12)}
.vk-reply {text-align:center;margin-top:14px}
.vk-reply a {background:#4a76a8;color:white;padding:9px 22px;border-radius:8px;font-weight:600;text-decoration:none;transition:background 0.2s}
.vk-reply a:hover {background:#3b5e8c}
.gifts-popup-header span { cursor: pointer;font-size: 26px;opacity: 0.85; transition: transform 0.3s ease, opacity 0.3s ease;}
.gifts-popup-header span:hover {transform: rotate(50deg); opacity: 2; }

.show-all-btn {
position: absolute;
bottom: -20px; /* сильно ниже видимой области */
right: 16px;
background: #4a76a8;
color: white;
font-size: 12px;
font-weight: 600;
padding: 8px 16px;
border-radius: 8px 8px 0 0;
border: none;
box-shadow: 0 -3px 10px rgba(0,0,0,0.25);
cursor: pointer;
transition: all 0.38s cubic-bezier(0.25, 0.8, 0.25, 1);
z-index: 10; /* выше всех элементов внутри контейнера */
opacity: 0.85;
white-space: nowrap;
}

#gifts-container {
position: relative;
overflow: visible !important; /* критично — чтобы hover-зона не обрезалась */
padding-bottom: 20px; /* запас места снизу для вылезания кнопки */
min-height: 140px; /* чтобы даже при 0 подарках была зона наведения */
}

/* hover — поднимаем вверх и делаем полностью видимой */
.show-all-btn:hover,
.show-all-btn:focus-visible {
bottom: 0; /* прижимаем к нижнему краю */
opacity: 1;
box-shadow: 0 -6px 16px rgba(0,0,0,0.35);
transform: none; /* убираем translateY, используем bottom */
}

/* активное нажатие */
.show-all-btn:active {
bottom: 2px;
box-shadow: 0 -2px 8px rgba(0,0,0,0.2);
}

.statusDot {
position: absolute;
bottom: 6px;
left: 56px;
width: 16px;
height: 16px;
border-radius: 50%;
border: 2.5px solid #fff;
box-shadow: 0 1px 4px rgba(0,0,0,0.3);
z-index: 2;
}
.onlineDot { background: #51da51; } /* зелёный — онлайн */
.offlineDot { background: #ff4d4d; } /* красный — оффлайн */

/* РАЗМЕРЫ */
.gifts-popup-header small {
display: inline-block;
min-width: 26px;        /* минимальная ширина (1–2 цифры) */
padding: 0 8px;         /* растягивается под 3-4 цифры */
height: 26px;
line-height: 25px;
text-align: center;

/* СТАРЫЙ СТИЛЬ VK */
color: #6b5d00;
background: linear-gradient(#fff7c2, #ffe58a);
border: 1px solid #e6c700;

border-radius: 6px;
padding: 0 6px;
padding-top: -5px;

/* позиционирование — оставил  */
margin-left: -300px;
margin-right: 2px;
font-weight: 700;
font-size: 0.82em;
vertical-align: middle;
box-sizing: border-box;

/* лёгкий внутренний блеск как в старом ВК */
box-shadow:
  inset 0 1px 0 rgba(255,255,255,0.8),
  inset 0 -1px 0 rgba(0,0,0,0.06),
  0 1px 1px rgba(0,0,0,0.08);
}

`;
document.head.appendChild(style);
// ──────────────────────────────────────────────
// Функции для даты (надёжная логика)
// ──────────────────────────────────────────────

// ====================== ЛЁГКАЯ ПРОВЕРКА КОЛИЧЕСТВА ======================
async function getGiftCount() {
try {
const res = await fetch(`/index/54-${userId}-`);
const text = await res.text();
const xml = new DOMParser().parseFromString(text, 'application/xml');
const cmd = xml.querySelector('cmd[p="content"]');
if (!cmd) return 0;

const temp = document.createElement('div');
temp.innerHTML = cmd.textContent;

let total = 0;
temp.querySelectorAll('td[onclick*="_uWnd.reload"]').forEach(td => {
const b = td.querySelector('b');
total += b ? (+b.textContent.trim() || 1) : 1;
});
return total;
} catch (e) {
console.error(e);
return -1;
}
}

// ====================== ПОЛНОЕ ОБНОВЛЕНИЕ (только при изменении числа) ======================
async function fullRefresh() {
console.log('[Gifts] Количество изменилось → полное обновление кэша');

try {
const res = await fetch(`/index/54-${userId}-`);
const text = await res.text();
const xml = new DOMParser().parseFromString(text, 'application/xml');
const cmd = xml.querySelector('cmd[p="content"]');
if (!cmd) return;

const temp = document.createElement('div');
temp.innerHTML = cmd.textContent;

const tds = [...temp.querySelectorAll('td[onclick*="_uWnd.reload"]')];
let total = 0;
const newGifts = [];

for (const td of tds) {
const img = td.querySelector('img');
if (!img) continue;
const count = td.querySelector('b') ? +td.querySelector('b').textContent.trim() || 1 : 1;
total += count;
const onclick = td.getAttribute('onclick');

for (let i = 0; i < count; i++) {
newGifts.push({
img: img.src,
onclick,
nick: 'Загрузка...',
avatar: '/.s/src/profile/img/profile_photo_thumbnail.png',
date: '',
comment: '',
profile: '#',
recipientId: null
});
}
}

cachedCount = total;
allGifts = newGifts;

// Обогащаем все подарки
for (let i = 0; i < Math.min(allGifts.length, 40); i++) {
allGifts[i] = await enrichGift(allGifts[i]);
// небольшая задержка, чтобы не убивать браузер
if (i % 4 === 3) await new Promise(r => setTimeout(r, 350));
}

allGifts.sort((a, b) => new Date(b.date || 0) - new Date(a.date || 0));

// Сохраняем новый кэш
localStorage.setItem(STORAGE_KEY, JSON.stringify({
count: cachedCount,
gifts: allGifts
}));

renderCounter();
renderPreview();
updateShowAllButton();

} catch (e) {
console.error(e);
counter.textContent = 'Ошибка';
}
}

async function enrichGift(g) {
try {
const m = g.onclick.match(/url:'([^']+)'/);
if (!m) return g;

const page = await fetch(m[1]).then(r => r.text());
const doc = new DOMParser().parseFromString(page, 'text/html');

const legend = doc.querySelector('legend a');
if (legend) {
g.nick = legend.textContent.trim();
g.profile = legend.href;
}

g.comment = doc.querySelector('[id^="mtx"]')?.textContent.trim() || g.comment;
g.date = doc.querySelector('div[style*="text-align:right"], .date')?.textContent.trim() || g.date;

if (g.profile && g.profile !== '#') {
const prof = await fetch(g.profile).then(r => r.text());
const pdoc = new DOMParser().parseFromString(prof, 'text/html');
const photo = pdoc.querySelector('.profile-photo');
if (photo?.style.backgroundImage) {
g.avatar = photo.style.backgroundImage.replace(/^url\(["']?|["']?\)$/g, '');
}
const idMatch = g.profile.match(/-(\d+)$/);
if (idMatch) g.recipientId = idMatch[1];
}
return g;
} catch (e) {
return g;
}
}

function renderCounter() {
const word = cachedCount === 1 ? 'подарок' : cachedCount < 5 ? 'подарка' : 'подарков';
counter.textContent = `${cachedCount} ${word}`;
}

function renderPreview() {
container.innerHTML = '';
allGifts.slice(0, 3).forEach(g => {
const div = document.createElement('div');
div.className = 'gift-card';
div.title = g.nick;
div.innerHTML = `<img src="${g.img}" alt="">`;
container.appendChild(div);
});
}

function updateShowAllButton() {
document.querySelectorAll('.show-all-btn').forEach(b => b.remove());
if (allGifts.length > 3) {
const btn = document.createElement('button');
btn.className = 'show-all-btn';
btn.textContent = 'Все подарки';
btn.onclick = showPopup;
container.appendChild(btn);
}
}

// Глобальная переменная со списком онлайн (должна быть определена раньше в скрипте!)
const onlineListText = (document.getElementById('uc-online-list')?.textContent || '').toLowerCase();

// ====================== ПОПАП С ОНЛАЙН-СТАТУСОМ ======================
function showPopup() {
if (!allGifts?.length) {
alert('Нет подарков или данные ещё не загружены');
return;
}

console.log(`[Gifts] Открываем попап | подарков: ${allGifts.length}`);

const overlay = document.createElement('div');
overlay.className = 'gifts-popup-overlay';

const popup = document.createElement('div');
popup.className = 'gifts-popup';

popup.innerHTML = `
<div class="gifts-popup-header">
Мои подарки <small>${allGifts.length}</small>
<span title="Закрыть">×</span>
</div>
<div class="gifts-popup-list"></div>
`;

overlay.appendChild(popup);
document.body.appendChild(overlay);

// Закрытие по крестику
popup.querySelector('span').onclick = () => overlay.remove();

// Закрытие по клику вне попапа
overlay.addEventListener('click', function closeHandler(e) {
if (e.target === overlay) {
overlay.remove();
overlay.removeEventListener('click', closeHandler);
}
});

const listContainer = popup.querySelector('.gifts-popup-list');

allGifts.forEach(gift => {
const safeNick = gift.nick || 'Пользователь';
const safeAvatar = gift.avatar || '/.s/src/profile/img/profile_photo_thumbnail.png';
const safeProfile = (gift.profile && gift.profile !== '#' && gift.profile !== 'javascript:;')
? gift.profile
: 'javascript:;';
const safeDate = gift.date || 'дата неизвестна';

// Проверка онлайна по нику (самый простой и совместимый способ)
const isOnline = safeNick !== 'Пользователь' &&
safeNick !== 'Загрузка...' &&
onlineListText.includes(safeNick.toLowerCase());

const statusHTML = `
<span class="statusDot ${isOnline ? 'onlineDot' : 'offlineDot'}"
title="${isOnline ? 'Онлайн' : 'Оффлайн'}"></span>
`;

const row = document.createElement('div');
row.className = 'vk-gift';

row.innerHTML = `
<div class="vk-user" style="position:relative;">
<a href="${safeProfile}" target="_blank" rel="noopener noreferrer" style="position:relative; display:inline-block;">
<img src="${safeAvatar}" alt="${safeNick}">
${statusHTML}
</a>
<div class="user-info">
<div class="name">
<a href="${safeProfile}" target="_blank" rel="noopener noreferrer">${safeNick}</a>
</div>
<div class="date">${safeDate}</div>
${gift.comment ? `<div class="vk-comment">${gift.comment}</div>` : ''}
</div>
</div>

<div class="vk-gift-img">
<img src="${gift.img}" alt="Подарок">
</div>

<div class="vk-reply"></div>
`;

const replyBlock = row.querySelector('.vk-reply');

if (gift.recipientId && String(gift.recipientId) !== String(userId)) {
const giftBtn = document.createElement('a');
giftBtn.href = 'javascript:;';
giftBtn.textContent = '🎁 Вручить подарок';
giftBtn.onclick = () => {
new _uWnd('AwD', 'Вручить награду', 380, 200, {
maxh: 300,
minh: 100,
closeonesc: 1
}, {
url: '/index/55-' + gift.recipientId
});
};
replyBlock.appendChild(giftBtn);
} else {
replyBlock.textContent = 'Нельзя вручить себе';
}

listContainer.appendChild(row);
});
}

// ====================== ЗАПУСК — МОМЕНТАЛЬНЫЙ РЕНДЕР ИЗ КЭША ======================
counter.style.cursor = 'pointer';
counter.onclick = showPopup;

// 1. Мгновенно показываем данные из кэша (если есть)
const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
if (saved.gifts && typeof saved.count === 'number') {
allGifts = saved.gifts;
cachedCount = saved.count;
renderCounter();
renderPreview();
updateShowAllButton();
console.log('[Gifts] Моментально показали кэш — страница готова');
} else {
// Если кэша нет — показываем 0
renderCounter(0);
console.log('[Gifts] Кэша нет — ждём загрузку');
}

// 2. Тихо, в фоне, проверяем актуальное количество
(async () => {
const liveCount = await getGiftCount();

//  если не удалось получить число — НИЧЕГО не делаем
if (liveCount < 0) {
  console.warn('[Gifts] Не удалось проверить количество — спим');
  return;
}

//  если кэш есть и число НЕ изменилось — СПИМ
if (cachedCount !== -1 && liveCount === cachedCount) {
  console.log(`[Gifts] Число не изменилось (${liveCount}) — ничего не грузим`);
  return;
}

// если кэша нет (первый запуск)
if (cachedCount === -1) {
  console.log('[Gifts] Кэша нет — первая загрузка');
  await fullRefresh();
  return;
}

//  если число изменилось — обновляем кэш
console.log(`[Gifts] Число изменилось: ${cachedCount} → ${liveCount} — обновляем кэш`);
await fullRefresh();
})();
})();
</script>


На видео проверяем работу модуля подарков в действии.

Тестируем функционал в реальном времени с разных аккаунтов: дарим подарки друг другу и наблюдаем, как происходят обновления и изменения. Смотрим, анализируем процесс и делаем выводы о работе системы прямо на глазах.

Мурчанн

Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
Дата: Понедельник, 09.02.2026, 21:39 | Сообщение # 2 | | Написал: Узнаваемый
Автор темы
Мурчанн не в сети
        Сообщений:211
         Регистрация:20.10.2016

Внутренних настроек через CSS здесь очень много.

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

Например, я закруглил обводку вокруг числа и увеличил интенсивность эффекта стекла вокруг самого всплывающего окна. При этом остаётся ещё огромное количество настроек, которые вы можете адаптировать под себя и свои цели.

Стандартное окно с обнулёнными настройками я подготовил специально для вас.
Для себя же я создаю совершенно другие стили, более индивидуальные и продуманные.

Код
<!-- ======= Блок подарков ======= -->
<div class="gifts-block">
<div class="gifts-header">Подарки</div>
<div class="gifts-footer" id="gifts-count">Загрузка…</div>
<div class="gifts-container" id="gifts-container"></div>
</div>

<link rel="stylesheet" href="/css/vk-gifts.css" media="all">

<script>
(async function() {
'use strict';

const container = document.getElementById('gifts-container');
const counter = document.getElementById('gifts-count');
const userId = <?$JSENCODE$($USER_ID$)?> || 0;

if (!userId) return;

const STORAGE_KEY = `giftsData_${userId}`;

let allGifts = [];
let cachedCount = -1;

// ──────────────────────────────────────────────
// Стили (оригинальные, без изменений)
// ──────────────────────────────────────────────
const style = document.createElement('style');
style.innerHTML = `
.gifts-block {background:#fff;border-radius:2px;box-shadow:0 2px 12px rgba(0,0,0,0.1);overflow:hidden;margin:8px 0;position:relative}
.gifts-header {background:linear-gradient(135deg,#4a76a8,#5a88c0);color:#fff;padding:12px 16px;font-weight:600;font-size:14px}
#gifts-container {display:flex;gap:5px;justify-content:center;padding:16px 16px 28px;flex-wrap:wrap;position:relative}
.gift-card {width:86px;height:100px;border-radius:12px;overflow:hidden;transition:transform .25s ease,box-shadow .25s ease;cursor:pointer}
.gift-card img {width:180%;height:140%;object-fit:contain}
.gift-card:hover {transform:scale(1.06);box-shadow:0 10px 25px rgba(74,118,168,0.3)}
.show-all-btn {position:absolute;bottom:0;right:16px;background:#4a76a8;color:white;font-size:12px;font-weight:600;padding:6px 14px;border-radius:8px 8px 0 0;border:none;box-shadow:0 -3px 8px rgba(0,0,0,0.12);cursor:pointer;transition:all 0.2s ease;z-index:2}
.show-all-btn:hover {background:#3b5e8c;box-shadow:0 -4px 12px rgba(0,0,0,0.18)}
.show-all-btn:active {transform:translateY(1px)}
.gifts-popup-overlay {position:fixed;inset:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:9999;backdrop-filter:blur(5px)}
.gifts-popup {background:#fff;width:520px;max-height:92vh;border-radius:10px;overflow:hidden;box-shadow:0 20px 70px rgba(0,0,0,0.45);border:10px solid #d0d9e4;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}
.gifts-popup-header {background:#4a76a8;color:white;padding:14px 20px;font-size:17px;font-weight:600;display:flex;justify-content:space-between;align-items:center}
.gifts-popup-header span {cursor:pointer;font-size:26px;opacity:0.85}
.gifts-popup-header span:hover {opacity:1}
.gifts-popup-list {padding:16px;overflow-y:auto;max-height:68vh;background:#f9fbff}
.vk-gift {padding:14px 0;border-bottom:1px solid #e8ecf2;transition:background 0.2s}
.vk-gift:hover {background:#f0f4fa}
.vk-user img {width:80px;height:80px;border-radius:50%;border:3px solid #4a76a8;object-fit:cover;transition:all 0.28s ease;box-shadow:0 3px 10px rgba(0,0,0,0.12)}
.vk-user:hover img {transform:scale(1.12);box-shadow:0 8px 24px rgba(74,118,168,0.4);filter:brightness(1.1)}
.vk-user {display:flex;gap:16px;align-items:center;margin-bottom:10px}
.vk-user .name {font-weight:700;color:#2c3e50;font-size:15.5px}
.vk-user .name a {color:#2c3e50;text-decoration:none}
.vk-user .name a:hover {color:#4a76a8;text-decoration:underline}
.vk-user .recipient-label {font-size:12px;color:#7f8c9d;margin-top:3px}
.vk-user .date {font-size:12.5px;color:#95a5a6}
.vk-user .date-loading {color:#aaa;font-style:italic}
.vk-comment {margin-top:8px;font-size:14px;color:#34495e;background:#ecf4ff;padding:10px 14px;border-radius:10px;border-left:4px solid #4a76a8}
.vk-gift-img {text-align:center;margin:14px 0}
.vk-gift-img img {max-width:260px;border-radius:12px;box-shadow:0 5px 18px rgba(0,0,0,0.12)}
.vk-reply {text-align:center;margin-top:14px}
.vk-reply a {background:#4a76a8;color:white;padding:9px 22px;border-radius:8px;font-weight:600;text-decoration:none;transition:background 0.2s}
.vk-reply a:hover {background:#3b5e8c}
.gifts-popup-header span { cursor: pointer;font-size: 26px;opacity: 0.85; transition: transform 0.3s ease, opacity 0.3s ease;}
.gifts-popup-header span:hover {transform: rotate(50deg); opacity: 2; }

.show-all-btn {
position: absolute;
bottom: -20px; /* сильно ниже видимой области */
right: 16px;
background: #4a76a8;
color: white;
font-size: 12px;
font-weight: 600;
padding: 8px 16px;
border-radius: 8px 8px 0 0;
border: none;
box-shadow: 0 -3px 10px rgba(0,0,0,0.25);
cursor: pointer;
transition: all 0.38s cubic-bezier(0.25, 0.8, 0.25, 1);
z-index: 10; /* выше всех элементов внутри контейнера */
opacity: 0.85;
white-space: nowrap;
}

#gifts-container {
position: relative;
overflow: visible !important; /* критично — чтобы hover-зона не обрезалась */
padding-bottom: 20px; /* запас места снизу для вылезания кнопки */
min-height: 140px; /* чтобы даже при 0 подарках была зона наведения */
}

/* hover — поднимаем вверх и делаем полностью видимой */
.show-all-btn:hover,
.show-all-btn:focus-visible {
bottom: 0; /* прижимаем к нижнему краю */
opacity: 1;
box-shadow: 0 -6px 16px rgba(0,0,0,0.35);
transform: none; /* убираем translateY, используем bottom */
}

/* активное нажатие */
.show-all-btn:active {
bottom: 2px;
box-shadow: 0 -2px 8px rgba(0,0,0,0.2);
}

.statusDot {
position: absolute;
bottom: 6px;
left: 56px;
width: 16px;
height: 16px;
border-radius: 50%;
border: 2.5px solid #fff;
box-shadow: 0 1px 4px rgba(0,0,0,0.3);
z-index: 2;
}
.onlineDot { background: #51da51; } /* зелёный — онлайн */
.offlineDot { background: #ff4d4d; } /* красный — оффлайн */

/* РАЗМЕРЫ */
.gifts-popup-header small {
display: inline-block;
min-width: 26px; /* минимальная ширина (1–2 цифры) */
padding: 0 8px; /* растягивается под 3-4 цифры */
height: 26px;
line-height: 25px;
text-align: center;

/* СТАРЫЙ СТИЛЬ VK */
color: #6b5d00;
background: linear-gradient(#fff7c2, #ffe58a);
border: 1px solid #e6c700;

border-radius: 50px;
padding: 0 6px;
padding-top: -5px;

/* позиционирование — оставил */
margin-left: -290px;
margin-right: 0px;
font-weight: 700;
font-size: 0.72em;
vertical-align: middle;
box-sizing: border-box;

/* лёгкий внутренний блеск как в старом ВК */
box-shadow:
inset 0 1px 0 rgba(255,255,255,0.8),
inset 0 -1px 0 rgba(0,0,0,0.06),
0 1px 1px rgba(0,0,0,0.08);
}

`;
document.head.appendChild(style);
// ──────────────────────────────────────────────
// Функции для даты (надёжная логика)
// ──────────────────────────────────────────────

// ====================== ЛЁГКАЯ ПРОВЕРКА КОЛИЧЕСТВА ======================
async function getGiftCount() {
try {
const res = await fetch(`/index/54-${userId}-`);
const text = await res.text();
const xml = new DOMParser().parseFromString(text, 'application/xml');
const cmd = xml.querySelector('cmd[p="content"]');
if (!cmd) return 0;

const temp = document.createElement('div');
temp.innerHTML = cmd.textContent;

let total = 0;
temp.querySelectorAll('td[onclick*="_uWnd.reload"]').forEach(td => {
const b = td.querySelector('b');
total += b ? (+b.textContent.trim() || 1) : 1;
});
return total;
} catch (e) {
console.error(e);
return -1;
}
}

// ====================== ПОЛНОЕ ОБНОВЛЕНИЕ (только при изменении числа) ======================
async function fullRefresh() {
console.log('[Gifts] Количество изменилось → полное обновление кэша');

try {
const res = await fetch(`/index/54-${userId}-`);
const text = await res.text();
const xml = new DOMParser().parseFromString(text, 'application/xml');
const cmd = xml.querySelector('cmd[p="content"]');
if (!cmd) return;

const temp = document.createElement('div');
temp.innerHTML = cmd.textContent;

const tds = [...temp.querySelectorAll('td[onclick*="_uWnd.reload"]')];
let total = 0;
const newGifts = [];

for (const td of tds) {
const img = td.querySelector('img');
if (!img) continue;
const count = td.querySelector('b') ? +td.querySelector('b').textContent.trim() || 1 : 1;
total += count;
const onclick = td.getAttribute('onclick');

for (let i = 0; i < count; i++) {
newGifts.push({
img: img.src,
onclick,
nick: 'Загрузка...',
avatar: '/.s/src/profile/img/profile_photo_thumbnail.png',
date: '',
comment: '',
profile: '#',
recipientId: null
});
}
}

cachedCount = total;
allGifts = newGifts;

// Обогащаем все подарки
for (let i = 0; i < Math.min(allGifts.length, 40); i++) {
allGifts[i] = await enrichGift(allGifts[i]);
// небольшая задержка, чтобы не убивать браузер
if (i % 4 === 3) await new Promise(r => setTimeout(r, 350));
}

allGifts.sort((a, b) => new Date(b.date || 0) - new Date(a.date || 0));

// Сохраняем новый кэш
localStorage.setItem(STORAGE_KEY, JSON.stringify({
count: cachedCount,
gifts: allGifts
}));

renderCounter();
renderPreview();
updateShowAllButton();

} catch (e) {
console.error(e);
counter.textContent = 'Ошибка';
}
}

async function enrichGift(g) {
try {
const m = g.onclick.match(/url:'([^']+)'/);
if (!m) return g;

const page = await fetch(m[1]).then(r => r.text());
const doc = new DOMParser().parseFromString(page, 'text/html');

const legend = doc.querySelector('legend a');
if (legend) {
g.nick = legend.textContent.trim();
g.profile = legend.href;
}

g.comment = doc.querySelector('[id^="mtx"]')?.textContent.trim() || g.comment;
g.date = doc.querySelector('div[style*="text-align:right"], .date')?.textContent.trim() || g.date;

if (g.profile && g.profile !== '#') {
const prof = await fetch(g.profile).then(r => r.text());
const pdoc = new DOMParser().parseFromString(prof, 'text/html');
const photo = pdoc.querySelector('.profile-photo');
if (photo?.style.backgroundImage) {
g.avatar = photo.style.backgroundImage.replace(/^url\(["']?|["']?\)$/g, '');
}
const idMatch = g.profile.match(/-(\d+)$/);
if (idMatch) g.recipientId = idMatch[1];
}
return g;
} catch (e) {
return g;
}
}

function renderCounter() {
const word = cachedCount === 1 ? 'подарок' : cachedCount < 5 ? 'подарка' : 'подарков';
counter.textContent = `${cachedCount} ${word}`;
}

function renderPreview() {
container.innerHTML = '';
allGifts.slice(0, 3).forEach(g => {
const div = document.createElement('div');
div.className = 'gift-card';
div.title = g.nick;
div.innerHTML = `<img src="${g.img}" alt="">`;
container.appendChild(div);
});
}

function updateShowAllButton() {
document.querySelectorAll('.show-all-btn').forEach(b => b.remove());
if (allGifts.length > 3) {
const btn = document.createElement('button');
btn.className = 'show-all-btn';
btn.textContent = 'Все подарки';
btn.onclick = showPopup;
container.appendChild(btn);
}
}

// Глобальная переменная со списком онлайн (должна быть определена раньше в скрипте!)
const onlineListText = (document.getElementById('uc-online-list')?.textContent || '').toLowerCase();

// ====================== ПОПАП С ОНЛАЙН-СТАТУСОМ ======================
function showPopup() {
if (!allGifts?.length) {
alert('Нет подарков или данные ещё не загружены');
return;
}

console.log(`[Gifts] Открываем попап | подарков: ${allGifts.length}`);

const overlay = document.createElement('div');
overlay.className = 'gifts-popup-overlay';

const popup = document.createElement('div');
popup.className = 'gifts-popup';

popup.innerHTML = `
<div class="gifts-popup-header">
Мои подарки <small>${allGifts.length}</small>
<span title="Закрыть">×</span>
</div>
<div class="gifts-popup-list"></div>
`;

overlay.appendChild(popup);
document.body.appendChild(overlay);

// Закрытие по крестику
popup.querySelector('span').onclick = () => overlay.remove();

// Закрытие по клику вне попапа
overlay.addEventListener('click', function closeHandler(e) {
if (e.target === overlay) {
overlay.remove();
overlay.removeEventListener('click', closeHandler);
}
});

const listContainer = popup.querySelector('.gifts-popup-list');

allGifts.forEach(gift => {
const safeNick = gift.nick || 'Пользователь';
const safeAvatar = gift.avatar || '/.s/src/profile/img/profile_photo_thumbnail.png';
const safeProfile = (gift.profile && gift.profile !== '#' && gift.profile !== 'javascript:;')
? gift.profile
: 'javascript:;';
const safeDate = gift.date || 'дата неизвестна';

// Проверка онлайна по нику (самый простой и совместимый способ)
const isOnline = safeNick !== 'Пользователь' &&
safeNick !== 'Загрузка...' &&
onlineListText.includes(safeNick.toLowerCase());

const statusHTML = `
<span class="statusDot ${isOnline ? 'onlineDot' : 'offlineDot'}"
title="${isOnline ? 'Онлайн' : 'Оффлайн'}"></span>
`;

const row = document.createElement('div');
row.className = 'vk-gift';

row.innerHTML = `
<div class="vk-user" style="position:relative;">
<a href="${safeProfile}" target="_blank" rel="noopener noreferrer" style="position:relative; display:inline-block;">
<img src="${safeAvatar}" alt="${safeNick}">
${statusHTML}
</a>
<div class="user-info">
<div class="name">
<a href="${safeProfile}" target="_blank" rel="noopener noreferrer">${safeNick}</a>
</div>
<div class="date">${safeDate}</div>
${gift.comment ? `<div class="vk-comment">${gift.comment}</div>` : ''}
</div>
</div>

<div class="vk-gift-img">
<img src="${gift.img}" alt="Подарок">
</div>

<div class="vk-reply"></div>
`;

const replyBlock = row.querySelector('.vk-reply');

if (gift.recipientId && String(gift.recipientId) !== String(userId)) {
const giftBtn = document.createElement('a');
giftBtn.href = 'javascript:;';
giftBtn.textContent = '🎁 Вручить подарок';
giftBtn.onclick = () => {
new _uWnd('AwD', 'Вручить награду', 380, 200, {
maxh: 300,
minh: 100,
closeonesc: 1
}, {
url: '/index/55-' + gift.recipientId
});
};
replyBlock.appendChild(giftBtn);
} else {
replyBlock.textContent = 'Нельзя вручить себе';
}

listContainer.appendChild(row);
});
}

// ====================== ЗАПУСК — МОМЕНТАЛЬНЫЙ РЕНДЕР ИЗ КЭША ======================
counter.style.cursor = 'pointer';
counter.onclick = showPopup;

// 1. Мгновенно показываем данные из кэша (если есть)
const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
if (saved.gifts && typeof saved.count === 'number') {
allGifts = saved.gifts;
cachedCount = saved.count;
renderCounter();
renderPreview();
updateShowAllButton();
console.log('[Gifts] Моментально показали кэш — страница готова');
} else {
// Если кэша нет — показываем 0
renderCounter(0);
console.log('[Gifts] Кэша нет — ждём загрузку');
}

// 2. Тихо, в фоне, проверяем актуальное количество
(async () => {
const liveCount = await getGiftCount();

// если не удалось получить число — НИЧЕГО не делаем
if (liveCount < 0) {
console.warn('[Gifts] Не удалось проверить количество — спим');
return;
}

// если кэш есть и число НЕ изменилось — СПИМ
if (cachedCount !== -1 && liveCount === cachedCount) {
console.log(`[Gifts] Число не изменилось (${liveCount}) — ничего не грузим`);
return;
}

// если кэша нет (первый запуск)
if (cachedCount === -1) {
console.log('[Gifts] Кэша нет — первая загрузка');
await fullRefresh();
return;
}

// если число изменилось — обновляем кэш
console.log(`[Gifts] Число изменилось: ${cachedCount} → ${liveCount} — обновляем кэш`);
await fullRefresh();
})();
})();
</script>


Скрин

Мурчанн

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