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

1
Админ
Постов: 101
2
Элита
Постов: 34
3
Элита
Постов: 28
4
VIP
Постов: 26
5
Дизайнер
Постов: 25
6
Пользователи
Постов: 25
7
Пользователи
Постов: 24
8
Проверенные
Постов: 21

  • Страница 1 из 1
  • 1
Парсер горячих тем планшета - закрепленный стилиус UCOZ
Дата: Воскресенье, 26.10.2025, 17:07 | Сообщение # 1 | | Написал: Начинающий
Автор темы
Мурчанн не в сети
        Сообщений:101
         Регистрация:20.10.2016

Наблюдая за работой скриптов, виджетов и парсеров, я заметил несколько багов. В частности, стилус иногда «отлетает» непредсказуемо, поэтому я закрепил его рядом с планшетом, чтобы исключить такие случайные перемещения. В скрипте установлено обновление каждые 8 часов. При этом стало очевидно, что в данном разделе нет необходимости в частом обновлении, иначе искусственно накручиваются просмотры — в некотором смысле он превращается в скрытого «генератора» просмотров.



Код
<!-- <body> -->
<section class="sect flex-grow-1">
<div class="sect__header d-flex">
<h2 class="sect__title flex-grow-1">Горячая тема</h2>

<style>
/* Неоновая сиреневая обёртка */
.nf-dark-border {
position: relative;
border: 1px solid #9D4EDD;
padding: 30px;
background-color: #0a0710;
color: #fff;
width: 850px;
margin: 40px auto;
border-radius: 12px;
box-shadow:
0 0 25px rgba(157, 78, 221, 0.7),
0 0 50px rgba(157, 78, 221, 0.5),
inset 0 0 20px rgba(157, 78, 221, 0.25);
animation: nf-neonGlow 3s ease-in-out infinite alternate;
overflow: visible;
}

/* Анимация сияния */
@keyframes nf-neonGlow {
from {
box-shadow:
0 0 15px rgba(157, 78, 221, 0.5),
0 0 30px rgba(157, 78, 221, 0.3),
inset 0 0 12px rgba(157, 78, 221, 0.15);
}
to {
box-shadow:
0 0 35px rgba(157, 78, 221, 0.9),
0 0 70px rgba(157, 78, 221, 0.7),
inset 0 0 25px rgba(157, 78, 221, 0.3);
}
}

/* Контейнер карусели */
.nf-carousel-container {
position: relative;
width: 100%;
overflow: hidden;
}

/* Трек карточек */
.nf-carousel-track {
display: flex;
transition: transform 0.5s ease;
}

/* Карточки */
.nf-card {
flex: 0 0 100%;
background: linear-gradient(145deg, #1c1122, #2a1735);
border-radius: 14px;
overflow: hidden;
display: flex;
text-decoration: none;
color: #fff;
cursor: pointer;
position: relative;
transition: box-shadow 0.3s ease, filter 0.3s ease;
min-height: 250px;
aspect-ratio: 16 / 9;
}

/* Карточка при наведении */
.nf-card:hover {
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4);
filter: brightness(1.45); /* мягче подсветка */
}

/* Изображение */
.nf-card-img {
width: 55%;
height: 100%;
overflow: hidden;
}

.nf-card-img img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}

.nf-card:hover .nf-card-img img {
transform: scale(1.03);
}

/* Контент карточки */
.nf-card-content {
padding: 25px 30px;
display: flex;
flex-direction: column;
justify-content: flex-start; /* заголовок сверху */
width: 45%;
}

.nf-card-title {
font-weight: bold;
font-size: 26px;
margin-bottom: 20px; /* немного больше пространства */
color: #e0b3ff;
text-shadow: 0 0 10px rgba(157, 78, 221, 0.6);
}

.nf-card-desc {
font-size: 16px;
color: #d8c3f0;
line-height: 1.7;
text-align: justify;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 12; /* длиннее описание */
-webkit-box-orient: vertical;
flex-grow: 1; /* описание заполняет пространство */
}

/* Автор */
.nf-card-meta {
position: absolute;
bottom: 15px;
right: 18px;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
z-index: 5;
}

.nf-card-meta img {
width: 55px;
height: 55px;
border-radius: 50%;
border: 2px solid #b367ff;
object-fit: cover;
background: #1a0e25;
box-shadow: 0 0 10px rgba(157, 78, 221, 0.6);
}

.nf-card-meta .author-nick {
font-size: 14px;
font-weight: bold;
color: #d7a7ff;
background: rgba(0, 0, 0, 0.45);
padding: 3px 10px;
border-radius: 8px;
backdrop-filter: blur(3px);
text-shadow: 0 0 6px rgba(157, 78, 221, 0.8);
}

/* Бейдж */
.nf-card-badge {
position: absolute;
top: 10px;
left: 10px;
background: linear-gradient(135deg, #b367ff, #7a3dbd);
padding: 5px 9px;
border-radius: 8px;
font-size: 11px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 0.6px;
box-shadow: 0 0 12px rgba(157, 78, 221, 0.6);
}

/* Стрелки */
.nf-carousel-arrow {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 52px;
height: 52px;
background: radial-gradient(circle at center, rgba(157, 78, 221, 0.85), rgba(80, 30, 140, 0.8));
border-radius: 50%;
color: #fff;
font-size: 30px;
text-align: center;
line-height: 52px;
cursor: pointer;
user-select: none;
z-index: 20;
box-shadow: 0 0 20px rgba(157, 78, 221, 0.8), 0 0 35px rgba(157, 78, 221, 0.6);
transition: all 0.3s ease;
}

.nf-carousel-arrow:hover {
background: radial-gradient(circle at center, rgba(180, 100, 255, 0.9), rgba(100, 40, 160, 0.85));
transform: translateY(-50%) scale(1.1);
}

.nf-carousel-arrow:active {
background: rgba(157, 78, 221, 0.7);
}

.nf-carousel-arrow-left {
left: -25px;
}

.nf-carousel-arrow-right {
right: -25px;
}

/* Нижняя панель телевизора */
.nf-tv-bottom {
position: relative;
width: 100%;
height: 40px; /* высота панели */
background-color: #000; /* черная панель */
display: flex;
justify-content: center;
align-items: center;
font-family: 'Arial', sans-serif;
font-weight: bold;
font-size: 18px;
color: #fff;
letter-spacing: 2px;
text-shadow: 0 0 6px rgba(255,255,255,0.5);
border-top: 0px solid rgba(157,78,221,0.5); /* тонкая светящаяся линия сверху */
margin-top: 10px;
border-radius: 0 0 12px 12px; /* скругление как у рамки */
margin-top: 1px; /* или отрицательное значение, например -5px, чтобы поднять выше */
}

.tablet-stylus {
position: absolute;


/* Горизонтальное положение стилиуса: left или right */
left: -20px; ; /* если хочешь слева */
right: 100px; /* если хочешь справа, регулируй значение */

/* Вертикальное положение */
top: 42%; /* центр по высоте */
transform: translateY(-50%) rotate(0deg) scale(1); /* центровка + угол + масштаб */

/* Размеры */
width: 14px; /* толщина стилуса */
height: 400px; /* длина стилуса */

/* Дизайн */
background: linear-gradient(180deg, #ffffff, #e6e6e6);
border-radius: 10px;
box-shadow:
inset 0 0 4px rgba(255,255,255,0.7),
0 0 8px rgba(255,255,255,0.8),
0 0 12px rgba(255,255,255,0.5);
z-index: 10;

/* Дополнительно для регулировки */
/* margin-left или margin-right можно использовать для точной подстройки */
}

/* Кончик стилуса */
.tablet-stylus::after {
content: '';
position: absolute;
bottom: -6px;
left: 50%;
transform: translateX(-50%);
width: 8px;
height: 8px;
background: radial-gradient(circle at center, #ccc 20%, #999 80%);
border-radius: 50%;
box-shadow: 0 0 3px rgba(0,0,0,0.4);
}

</style>

<div class="nf-dark-border">
<div class="nf-carousel-container">
<div class="nf-carousel-track" id="nf-forum-cards"></div>

<!-- Нижняя панель телевизора -->
<div class="nf-tv-bottom">
<span>SAMSUNG</span>
</div>
</div>

<div class="nf-carousel-arrow nf-carousel-arrow-left">❮</div>
<div class="nf-carousel-arrow nf-carousel-arrow-right">❯</div>

<!-- Белый стилус теперь внутри планшета -->
<div class="tablet-stylus"></div>
</div>

<script>
(async function nfForumCarouselHot3_fixedCarousel() {
const track = document.querySelector('.nf-carousel-track');
if (!track) return;

const arrowLeft = document.querySelector('.nf-carousel-arrow-left');
const arrowRight = document.querySelector('.nf-carousel-arrow-right');
const container = track.closest('.nf-carousel-container') || track.parentElement;

const BASE_FORUM_URL = '/forum/0-0-1-34';
const CACHE_KEY = 'nf_forum_hot_3_clean';
const CACHE_TIME = 8 * 60 * 60 * 1000; // 8 часов кэш
const MIN_REFRESH_MS = 5000; // минимальная задержка 5 секунд
const MAX_CARDS = 16; // макс. количество карточек

let lastFetch = 0;
let inProgress = false;
let lastRenderedHrefs = '';
let index = 0;
let arrowsBound = false;

function toAbs(url) {
if (!url) return '';
if (url.startsWith('http')) return url;
if (url.startsWith('//')) return window.location.protocol + url;
return window.location.origin + (url.startsWith('/') ? url : '/' + url);
}

function escapeHtml(s) {
return String(s || '').replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));
}

// --- Чистый текст без спецсимволов и больших пробелов ---
function getCleanText(el, maxLength = 220) {
if (!el) return '';
let text = '';
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false);
let node;
while(node = walker.nextNode()) {
// пропускаем скрипты, стили и iframe
if(node.parentNode.closest('script, style, iframe')) continue;
let t = node.nodeValue;
if(!t) continue;

// убираем табы, переносы строк, неразрывные пробелы
t = t.replace(/[\t\n\r\u00A0]+/g, ' ');

// убираем лишние пробелы
t = t.replace(/\s+/g, ' ').trim();

if(t) text += t + ' ';
}

// пробелы перед знаками препинания
text = text.replace(/\s+([.,!?;:])/g, '$1');

// полностью удаляем HTML-теги, если что-то осталось
text = text.replace(/<[^>]+>/g, '');

// удаляем CSS свойства и подобное
text = text.replace(/\b(?:margin|padding|width|height|text-align|border-radius|box-shadow|color|background|font|line-height)\s*:[^;]+;/gi, '');

// убираем все странные спецсимволы, оставляем буквы, цифры, базовую пунктуацию
text = text.replace(/[^\wа-яА-Я0-9.,!?;: \-()'"«»]/g, '');

// обрезаем до maxLength символов
return text.trim().slice(0, maxLength);
}

async function fetchHTML(url) {
const res = await fetch(url, { credentials: 'same-origin', cache: 'no-cache' });
return await res.text();
}

function findHotInDoc(doc) {
const rows = Array.from(doc.querySelectorAll('td.threadNametd, .threadNametd, tr'));
const result = [];
for (let td of rows) {
const badge = td.querySelector('.ipsBadge');
const pages = td.querySelector('.postpSwithces, .postPSwithcesLink');
if (!((badge && /горяч/i.test(badge.textContent || '')) || pages)) continue;
const linkEl = td.querySelector('a.threadLink');
if (!linkEl) continue;
const href = toAbs(linkEl.getAttribute('href') || linkEl.href || '');
const title = (linkEl.textContent || '').trim() || '(без названия)';
const row = td.closest('tr');
let icon = row?.querySelector('img')?.getAttribute('src') || '';
icon = toAbs(icon) || '/forumimages/Newsletter-3.png';
const author = (row?.querySelector('.threadAuthTd')?.textContent || '').trim() || 'Аноним';
const views = parseInt((row?.querySelector('.threadViewTd, .views')?.textContent || '').replace(/\D/g, '')) || 0;
result.push({ href, title, icon, author, views });
if (result.length >= MAX_CARDS) break;
}
return result;
}

function uniqueThreads(arr) {
const seen = new Set();
return arr.filter(t => {
if (seen.has(t.href)) return false;
seen.add(t.href);
return true;
});
}

async function enrichThread(t) {
try {
const txt = await fetchHTML(t.href);
const doc = new DOMParser().parseFromString(txt, 'text/html');
const postEl = doc.querySelector('.post_content, .post_body, .post, .message, .ipsType_richText');
const text = getCleanText(postEl);
let img = '';
if (postEl) {
for (let im of Array.from(postEl.querySelectorAll('img'))) {
const src = im.getAttribute('data-src') || im.src || '';
if (!src) continue;
if (/avatar|forumimages|template/i.test(src)) continue;
img = toAbs(src); break;
}
}
if (!img) img = t.icon;
const authorEl = doc.querySelector('.postUser, .author, .post-author-name');
const author = (authorEl?.textContent?.trim()) || t.author || 'Аноним';
const avatarEl = doc.querySelector('.avatar img, .ipsUserPhoto');
const avatar = toAbs(avatarEl?.src || '/forumimages/default-avatar.png');
return { ...t, text, img, author, avatar };
} catch (e) {
return { ...t, text: '', img: t.icon, avatar: '/forumimages/default-avatar.png' };
}
}

function setSlideSizes() {
const slides = Array.from(track.children);
const width = (container?.clientWidth || track.clientWidth || 600);
slides.forEach(s => s.style.width = width + 'px');
track.style.display = 'flex';
track.style.transition = 'transform 0.45s ease';
}

function updateCarouselPosition() {
const total = track.children.length || 1;
if (index < 0) index = 0;
if (index > total - 1) index = Math.max(0, total - 1);
const width = (container?.clientWidth || track.clientWidth || 600);
track.style.transform = `translateX(${-index * width}px)`;
if (arrowLeft) arrowLeft.style.opacity = index === 0 ? '0.4' : '1';
if (arrowRight) arrowRight.style.opacity = index >= total - 1 ? '0.4' : '1';
}

function bindArrowsOnce() {
if (arrowsBound) return;
arrowsBound = true;
arrowLeft?.addEventListener('click', () => { index = Math.max(0, index - 1); updateCarouselPosition(); });
arrowRight?.addEventListener('click', () => { index = Math.min(track.children.length - 1, index + 1); updateCarouselPosition(); });
window.addEventListener('keydown', e => {
if (e.key === 'ArrowLeft') index = Math.max(0, index - 1), updateCarouselPosition();
if (e.key === 'ArrowRight') index = Math.min(track.children.length - 1, index + 1), updateCarouselPosition();
});
let startX = null;
track.addEventListener('touchstart', e => startX = e.touches[0].clientX, { passive:true });
track.addEventListener('touchend', e => {
if (startX === null) return;
const dx = e.changedTouches[0].clientX - startX;
if (Math.abs(dx) > 50) index = dx < 0 ? Math.min(track.children.length - 1, index + 1) : Math.max(0, index - 1);
updateCarouselPosition();
startX = null;
}, { passive:true });
}

function renderCards(cards, smooth = true) {
if (!cards?.length) return;
const hrefs = cards.map(c => c.href).join('|');
if (hrefs === lastRenderedHrefs) { setSlideSizes(); updateCarouselPosition(); return; }
lastRenderedHrefs = hrefs;

const temp = document.createElement('div');
temp.style.display = 'flex';
temp.style.flexWrap = 'nowrap';

for (const c of cards) {
const a = document.createElement('a');
a.className = 'nf-card';
a.href = c.href;
a.target = '_self';
a.style.flex = '0 0 100%';
a.innerHTML = `
<div class="nf-card-img"><img src="${c.img}" alt="${escapeHtml(c.title)}"><div class="nf-card-badge">🔥 Горячая тема</div></div>
<div class="nf-card-content">
<div class="nf-card-title">${escapeHtml(c.title)}</div>
<div class="nf-card-desc">${escapeHtml(c.text || '')}</div>
<div class="nf-card-meta" style="display:flex;align-items:center;gap:8px;">
<img src="${c.avatar}" style="width:48px;height:48px;border-radius:50%;">
<span class="author-nick">${escapeHtml(c.author)}</span>
<span class="views-count">👁️ ${c.views}</span>
</div>
</div>`;
temp.appendChild(a);
}

const rect = track.getBoundingClientRect();
const curH = rect.height || track.offsetHeight || 200;
track.style.minHeight = curH + 'px';

if (smooth) {
track.style.transition = 'opacity 220ms ease';
track.style.opacity = '0';
setTimeout(() => {
track.innerHTML = temp.innerHTML;
setSlideSizes();
if (index > track.children.length - 1) index = Math.max(0, track.children.length - 1);
updateCarouselPosition();
track.style.opacity = '1';
setTimeout(() => { track.style.minHeight = ''; }, 300);
}, 220);
} else {
track.innerHTML = temp.innerHTML;
setSlideSizes();
if (index > track.children.length - 1) index = Math.max(0, track.children.length - 1);
updateCarouselPosition();
track.style.minHeight = '';
}

bindArrowsOnce();
}

async function loadData(force=false) {
const now = Date.now();
if (inProgress || (!force && now - lastFetch < MIN_REFRESH_MS)) return;
inProgress = true;
lastFetch = now;
try {
let threads = findHotInDoc(document);
if (!threads.length) {
const html = await fetchHTML(BASE_FORUM_URL);
const doc = new DOMParser().parseFromString(html, 'text/html');
threads = findHotInDoc(doc);
}
threads = uniqueThreads(threads).slice(0, MAX_CARDS);
if (!threads.length) { inProgress=false; return; }
const cards = await Promise.all(threads.map(enrichThread));
try { localStorage.setItem(CACHE_KEY, JSON.stringify({ time: Date.now(), cards })); } catch(e){}
renderCards(cards, true);
} catch(err) {
console.error('nfCarousel loadData error', err);
} finally {
inProgress = false;
}
}

try {
const cached = JSON.parse(localStorage.getItem(CACHE_KEY) || '{}');
if (cached.cards && Date.now() - (cached.time || 0) < CACHE_TIME) {
renderCards(cached.cards, false);
} else {
await loadData(true);
}
} catch(e) {
await loadData(true);
}

// Авто-обновление каждые 10 секунд
setInterval(() => loadData(false), 10 * 1000);

window.addEventListener('resize', () => { setSlideSizes(); updateCarouselPosition(); }, { passive: true });

})();
</script>

</div>

Мурчанн

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

Скрипт изначально создавал чрезмерную нагрузку на сервер и браузер, поскольку каждые 10 секунд выполнял полное сканирование форума в поисках новых тем. В исправленной версии оптимизирована логика обновления: теперь проверка выполняется только раз в 5 минут, без лишних запросов и нагрузки на систему. 27

Код
<script>
(async function nfForumCarouselHot3_fixedCarousel() {
  try {
    const track = document.querySelector('.nf-carousel-track');
    if (!track) return console.warn('nfCarousel: .nf-carousel-track not found');

    const arrowLeft = document.querySelector('.nf-carousel-arrow-left');
    const arrowRight = document.querySelector('.nf-carousel-arrow-right');
    const container = track.closest('.nf-carousel-container') || track.parentElement;

    const BASE_FORUM_URL = '/forum/0-0-1-34';
    const CACHE_KEY = 'nf_forum_hot_3_clean';
    const CACHE_TIME = 8 * 60 * 60 * 1000; // 8 часов
    const MIN_REFRESH_MS = 5000; // минимальная задержка 5 секунд
    const MAX_CARDS = 16; // макс. карточек

    let lastFetch = 0;
    let inProgress = false;
    let lastRenderedHrefs = '';
    let index = 0;
    let arrowsBound = false;

    function toAbs(url) {
      if (!url) return '';
      if (url.startsWith('http')) return url;
      if (url.startsWith('//')) return window.location.protocol + url;
      return window.location.origin + (url.startsWith('/') ? url : '/' + url);
    }

    function escapeHtml(s) {
      return String(s || '').replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));
    }

    function getCleanText(el, maxLength = 220) {
      if (!el) return '';
      let text = '';
      const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false);
      let node;
      while (node = walker.nextNode()) {
        if (node.parentNode.closest && node.parentNode.closest('script, style, iframe')) continue;
        let t = node.nodeValue;
        if (!t) continue;
        t = t.replace(/[\t\n\r\u00A0]+/g, ' ');
        t = t.replace(/\s+/g, ' ').trim();
        if (t) text += t + ' ';
      }
      text = text.replace(/\s+([.,!?;:])/g, '$1');
      text = text.replace(/<[^>]+>/g, '');
      text = text.replace(/\b(?:margin|padding|width|height|text-align|border-radius|box-shadow|color|background|font|line-height)\s*:[^;]+;/gi, '');
      text = text.replace(/[^\wа-яА-Я0-9.,!?;: \-()'"«»]/g, '');
      return text.trim().slice(0, maxLength);
    }

    async function fetchHTML(url) {
      const res = await fetch(url, { credentials: 'same-origin', cache: 'no-cache' });
      return await res.text();
    }

    function findHotInDoc(doc) {
      const rows = Array.from(doc.querySelectorAll('td.threadNametd, .threadNametd, tr'));
      const result = [];
      for (let td of rows) {
        const badge = td.querySelector('.ipsBadge');
        const pages = td.querySelector('.postpSwithces, .postPSwithcesLink');
        if (!((badge && /горяч/i.test(badge.textContent || '')) || pages)) continue;
        const linkEl = td.querySelector('a.threadLink');
        if (!linkEl) continue;
        const href = toAbs(linkEl.getAttribute('href') || linkEl.href || '');
        const title = (linkEl.textContent || '').trim() || '(без названия)';
        const row = td.closest('tr');
        let icon = row?.querySelector('img')?.getAttribute('src') || '';
        icon = toAbs(icon) || '/forumimages/Newsletter-3.png';
        const author = (row?.querySelector('.threadAuthTd')?.textContent || '').trim() || 'Аноним';
        const views = parseInt((row?.querySelector('.threadViewTd, .views')?.textContent || '').replace(/\D/g, '')) || 0;
        result.push({ href, title, icon, author, views });
        if (result.length >= MAX_CARDS) break;
      }
      return result;
    }

    function uniqueThreads(arr) {
      const seen = new Set();
      return arr.filter(t => {
        if (seen.has(t.href)) return false;
        seen.add(t.href);
        return true;
      });
    }

    function setSlideSizes() {
      const slides = Array.from(track.children);
      const width = (container?.clientWidth || track.clientWidth || 600);
      slides.forEach(s => s.style.width = width + 'px');
      track.style.display = 'flex';
      track.style.transition = 'transform 0.45s ease';
    }

    function updateCarouselPosition() {
      const total = track.children.length || 1;
      if (index < 0) index = 0;
      if (index > total - 1) index = Math.max(0, total - 1);
      const width = (container?.clientWidth || track.clientWidth || 600);
      track.style.transform = `translateX(${-index * width}px)`;
      if (arrowLeft) arrowLeft.style.opacity = index === 0 ? '0.4' : '1';
      if (arrowRight) arrowRight.style.opacity = index >= total - 1 ? '0.4' : '1';
    }

    function bindArrowsOnce() {
      if (arrowsBound) return;
      arrowsBound = true;
      arrowLeft?.addEventListener('click', () => { index = Math.max(0, index - 1); updateCarouselPosition(); });
      arrowRight?.addEventListener('click', () => { index = Math.min(track.children.length - 1, index + 1); updateCarouselPosition(); });
      window.addEventListener('keydown', e => {
        if (e.key === 'ArrowLeft') { index = Math.max(0, index - 1); updateCarouselPosition(); }
        if (e.key === 'ArrowRight') { index = Math.min(track.children.length - 1, index + 1); updateCarouselPosition(); }
      });
      let startX = null;
      track.addEventListener('touchstart', e => startX = e.touches[0].clientX, { passive:true });
      track.addEventListener('touchend', e => {
        if (startX === null) return;
        const dx = e.changedTouches[0].clientX - startX;
        if (Math.abs(dx) > 50) index = dx < 0 ? Math.min(track.children.length - 1, index + 1) : Math.max(0, index - 1);
        updateCarouselPosition();
        startX = null;
      }, { passive:true });
    }

    function renderCards(cards, smooth = true) {
      try {
        if (!cards?.length) return;
        const hrefs = cards.map(c => c.href).join('|');
        if (hrefs === lastRenderedHrefs) { setSlideSizes(); updateCarouselPosition(); return; }
        lastRenderedHrefs = hrefs;

        // сохраняем высоту, чтобы не дергать layout сильно
        const rect = track.getBoundingClientRect();
        const curH = rect.height || track.offsetHeight || 200;
        track.style.minHeight = curH + 'px';

        // создаём фрагмент
        const frag = document.createDocumentFragment();
        for (const c of cards) {
          const a = document.createElement('a');
          a.className = 'nf-card';
          a.href = c.href;
          a.target = '_self';
          a.style.flex = '0 0 100%';
          a.innerHTML = `
            <div class="nf-card-img"><img src="${escapeHtml(c.img || c.icon)}" alt="${escapeHtml(c.title)}"><div class="nf-card-badge">🔥 Горячая тема</div></div>
            <div class="nf-card-content">
              <div class="nf-card-title">${escapeHtml(c.title)}</div>
              <div class="nf-card-desc">${escapeHtml(c.text || '')}</div>
              <div class="nf-card-meta" style="display:flex;align-items:center;gap:8px;">
                <img src="${escapeHtml(c.avatar || '/forumimages/default-avatar.png')}" style="width:48px;height:48px;border-radius:50%;">
                <span class="author-nick">${escapeHtml(c.author)}</span>
                <span class="views-count">👁️ ${c.views}</span>
              </div>
            </div>`;
          frag.appendChild(a);
        }

        // Плавно заменяем содержимое
        if (smooth) {
          track.style.transition = 'opacity 220ms ease';
          track.style.opacity = '0';
          setTimeout(() => {
            // очистка старого содержимого безопасным способом
            while (track.firstChild) track.removeChild(track.firstChild);
            track.appendChild(frag);
            setSlideSizes();
            if (index > track.children.length - 1) index = Math.max(0, track.children.length - 1);
            updateCarouselPosition();
            track.style.opacity = '1';
            setTimeout(() => { track.style.minHeight = ''; }, 300);
          }, 220);
        } else {
          while (track.firstChild) track.removeChild(track.firstChild);
          track.appendChild(frag);
          setSlideSizes();
          if (index > track.children.length - 1) index = Math.max(0, track.children.length - 1);
          updateCarouselPosition();
          track.style.minHeight = '';
        }

        bindArrowsOnce();
      } catch (e) {
        console.error('nfCarousel renderCards error', e);
      }
    }

    async function loadData(force = false) {
      const now = Date.now();
      if (inProgress || (!force && now - lastFetch < MIN_REFRESH_MS)) return;
      inProgress = true;
      lastFetch = now;
      try {
        // берем сначала с текущей страницы
        let threads = findHotInDoc(document);
        if (!threads.length) {
          const html = await fetchHTML(BASE_FORUM_URL);
          const doc = new DOMParser().parseFromString(html, 'text/html');
          threads = findHotInDoc(doc);
        }
        threads = uniqueThreads(threads).slice(0, MAX_CARDS);
        if (!threads.length) { inProgress = false; return; }

        // ⚡ ВАЖНО: не заходим в каждую тему — берём данные из списка
        const cards = threads.map(t => ({
          href: t.href,
          title: t.title,
          img: t.icon || t.img || '/forumimages/Newsletter-3.png',
          icon: t.icon,
          author: t.author || 'Аноним',
          views: t.views || 0,
          text: '', // нет глубокого текста
          avatar: '/forumimages/default-avatar.png'
        }));

        // кешируем результат
        try {
          localStorage.setItem(CACHE_KEY, JSON.stringify({ time: Date.now(), cards }));
        } catch (e) {
          // ignore quota errors
        }

        renderCards(cards, true);
      } catch (err) {
        console.error('nfCarousel loadData error', err);
      } finally {
        inProgress = false;
      }
    }

    // --- инициализация из кэша или загрузка ---
    try {
      const raw = localStorage.getItem(CACHE_KEY);
      if (raw) {
        const cached = JSON.parse(raw || '{}');
        if (cached.cards && Date.now() - (cached.time || 0) < CACHE_TIME) {
          renderCards(cached.cards, false);
        } else {
          await loadData(true);
        }
      } else {
        await loadData(true);
      }
    } catch (e) {
      await loadData(true);
    }

    // Авто-обновление каждые 5 минут
    setInterval(() => loadData(false), 5 * 60 * 1000);

    // ресайз
    window.addEventListener('resize', () => { setSlideSizes(); updateCarouselPosition(); }, { passive: true });

  } catch (e) {
    console.error('nfCarousel init error', e);
  }
})();
</script>

Мурчанн

Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
Дата: Понедельник, 27.10.2025, 00:16 | Сообщение # 3 | | Написал: Начинающий
Автор темы
Мурчанн не в сети
        Сообщений:101
         Регистрация:20.10.2016

Если скрипт работать не будет , используйте этот вариант. 2

Код
<script>
(async function nfForumCarouselHot3_fixedCarousel() {
const track = document.querySelector('.nf-carousel-track');
if (!track) return;

const arrowLeft = document.querySelector('.nf-carousel-arrow-left');
const arrowRight = document.querySelector('.nf-carousel-arrow-right');
const container = track.closest('.nf-carousel-container') || track.parentElement;

const BASE_FORUM_URL = '/forum/0-0-1-34';
const CACHE_KEY = 'nf_forum_hot_3_clean';
const CACHE_TIME = 30 * 60 * 1000; // 1 час кэш
const MIN_REFRESH_MS = 5000; // минимальная задержка 5 секунд
const MAX_CARDS = 16; // макс. количество карточек

let lastFetch = 0;
let inProgress = false;
let lastRenderedHrefs = '';
let index = 0;
let arrowsBound = false;

function toAbs(url) {
if (!url) return '';
if (url.startsWith('http')) return url;
if (url.startsWith('//')) return window.location.protocol + url;
return window.location.origin + (url.startsWith('/') ? url : '/' + url);
}

function escapeHtml(s) {
return String(s || '').replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));
}

// --- Чистый текст без спецсимволов и больших пробелов ---
function getCleanText(el, maxLength = 220) {
if (!el) return '';
let text = '';
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false);
let node;
while(node = walker.nextNode()) {
// пропускаем скрипты, стили и iframe
if(node.parentNode.closest('script, style, iframe')) continue;
let t = node.nodeValue;
if(!t) continue;

// убираем табы, переносы строк, неразрывные пробелы
t = t.replace(/[\t\n\r\u00A0]+/g, ' ');

// убираем лишние пробелы
t = t.replace(/\s+/g, ' ').trim();

if(t) text += t + ' ';
}

// пробелы перед знаками препинания
text = text.replace(/\s+([.,!?;:])/g, '$1');

// полностью удаляем HTML-теги, если что-то осталось
text = text.replace(/<[^>]+>/g, '');

// удаляем CSS свойства и подобное
text = text.replace(/\b(?:margin|padding|width|height|text-align|border-radius|box-shadow|color|background|font|line-height)\s*:[^;]+;/gi, '');

// убираем все странные спецсимволы, оставляем буквы, цифры, базовую пунктуацию
text = text.replace(/[^\wа-яА-Я0-9.,!?;: \-()'"«»]/g, '');

// обрезаем до maxLength символов
return text.trim().slice(0, maxLength);
}

async function fetchHTML(url) {
const res = await fetch(url, { credentials: 'same-origin', cache: 'no-cache' });
return await res.text();
}

function findHotInDoc(doc) {
const rows = Array.from(doc.querySelectorAll('td.threadNametd, .threadNametd, tr'));
const result = [];
for (let td of rows) {
const badge = td.querySelector('.ipsBadge');
const pages = td.querySelector('.postpSwithces, .postPSwithcesLink');
if (!((badge && /горяч/i.test(badge.textContent || '')) || pages)) continue;
const linkEl = td.querySelector('a.threadLink');
if (!linkEl) continue;
const href = toAbs(linkEl.getAttribute('href') || linkEl.href || '');
const title = (linkEl.textContent || '').trim() || '(без названия)';
const row = td.closest('tr');
let icon = row?.querySelector('img')?.getAttribute('src') || '';
icon = toAbs(icon) || '/forumimages/Newsletter-3.png';
const author = (row?.querySelector('.threadAuthTd')?.textContent || '').trim() || 'Аноним';
const views = parseInt((row?.querySelector('.threadViewTd, .views')?.textContent || '').replace(/\D/g, '')) || 0;
result.push({ href, title, icon, author, views });
if (result.length >= MAX_CARDS) break;
}
return result;
}

function uniqueThreads(arr) {
const seen = new Set();
return arr.filter(t => {
if (seen.has(t.href)) return false;
seen.add(t.href);
return true;
});
}

async function enrichThread(t) {
try {
const txt = await fetchHTML(t.href);
const doc = new DOMParser().parseFromString(txt, 'text/html');
const postEl = doc.querySelector('.post_content, .post_body, .post, .message, .ipsType_richText');
const text = getCleanText(postEl);
let img = '';
if (postEl) {
for (let im of Array.from(postEl.querySelectorAll('img'))) {
const src = im.getAttribute('data-src') || im.src || '';
if (!src) continue;
if (/avatar|forumimages|template/i.test(src)) continue;
img = toAbs(src); break;
}
}
if (!img) img = t.icon;
const authorEl = doc.querySelector('.postUser, .author, .post-author-name');
const author = (authorEl?.textContent?.trim()) || t.author || 'Аноним';
const avatarEl = doc.querySelector('.avatar img, .ipsUserPhoto');
const avatar = toAbs(avatarEl?.src || '/forumimages/default-avatar.png');
return { ...t, text, img, author, avatar };
} catch (e) {
return { ...t, text: '', img: t.icon, avatar: '/forumimages/default-avatar.png' };
}
}

function setSlideSizes() {
const slides = Array.from(track.children);
const width = (container?.clientWidth || track.clientWidth || 600);
slides.forEach(s => s.style.width = width + 'px');
track.style.display = 'flex';
track.style.transition = 'transform 0.45s ease';
}

function updateCarouselPosition() {
const total = track.children.length || 1;
if (index < 0) index = 0;
if (index > total - 1) index = Math.max(0, total - 1);
const width = (container?.clientWidth || track.clientWidth || 600);
track.style.transform = `translateX(${-index * width}px)`;
if (arrowLeft) arrowLeft.style.opacity = index === 0 ? '0.4' : '1';
if (arrowRight) arrowRight.style.opacity = index >= total - 1 ? '0.4' : '1';
}

function bindArrowsOnce() {
if (arrowsBound) return;
arrowsBound = true;
arrowLeft?.addEventListener('click', () => { index = Math.max(0, index - 1); updateCarouselPosition(); });
arrowRight?.addEventListener('click', () => { index = Math.min(track.children.length - 1, index + 1); updateCarouselPosition(); });
window.addEventListener('keydown', e => {
if (e.key === 'ArrowLeft') index = Math.max(0, index - 1), updateCarouselPosition();
if (e.key === 'ArrowRight') index = Math.min(track.children.length - 1, index + 1), updateCarouselPosition();
});
let startX = null;
track.addEventListener('touchstart', e => startX = e.touches[0].clientX, { passive:true });
track.addEventListener('touchend', e => {
if (startX === null) return;
const dx = e.changedTouches[0].clientX - startX;
if (Math.abs(dx) > 50) index = dx < 0 ? Math.min(track.children.length - 1, index + 1) : Math.max(0, index - 1);
updateCarouselPosition();
startX = null;
}, { passive:true });
}

function renderCards(cards, smooth = true) {
if (!cards?.length) return;
const hrefs = cards.map(c => c.href).join('|');
if (hrefs === lastRenderedHrefs) { setSlideSizes(); updateCarouselPosition(); return; }
lastRenderedHrefs = hrefs;

const temp = document.createElement('div');
temp.style.display = 'flex';
temp.style.flexWrap = 'nowrap';

for (const c of cards) {
const a = document.createElement('a');
a.className = 'nf-card';
a.href = c.href;
a.target = '_self';
a.style.flex = '0 0 100%';
a.innerHTML = `
<div class="nf-card-img"><img src="${c.img}" alt="${escapeHtml(c.title)}"><div class="nf-card-badge">🔥 Горячая тема</div></div>
<div class="nf-card-content">
<div class="nf-card-title">${escapeHtml(c.title)}</div>
<div class="nf-card-desc">${escapeHtml(c.text || '')}</div>
<div class="nf-card-meta" style="display:flex;align-items:center;gap:8px;">
<img src="${c.avatar}" style="width:48px;height:48px;border-radius:50%;">
<span class="author-nick">${escapeHtml(c.author)}</span>
<span class="views-count">👁️ ${c.views}</span>
</div>
</div>`;
temp.appendChild(a);
}

const rect = track.getBoundingClientRect();
const curH = rect.height || track.offsetHeight || 200;
track.style.minHeight = curH + 'px';

if (smooth) {
track.style.transition = 'opacity 220ms ease';
track.style.opacity = '0';
setTimeout(() => {
track.innerHTML = temp.innerHTML;
setSlideSizes();
if (index > track.children.length - 1) index = Math.max(0, track.children.length - 1);
updateCarouselPosition();
track.style.opacity = '1';
setTimeout(() => { track.style.minHeight = ''; }, 300);
}, 220);
} else {
track.innerHTML = temp.innerHTML;
setSlideSizes();
if (index > track.children.length - 1) index = Math.max(0, track.children.length - 1);
updateCarouselPosition();
track.style.minHeight = '';
}

bindArrowsOnce();
}

async function loadData(force=false) {
const now = Date.now();
if (inProgress || (!force && now - lastFetch < MIN_REFRESH_MS)) return;
inProgress = true;
lastFetch = now;
try {
let threads = findHotInDoc(document);
if (!threads.length) {
const html = await fetchHTML(BASE_FORUM_URL);
const doc = new DOMParser().parseFromString(html, 'text/html');
threads = findHotInDoc(doc);
}
threads = uniqueThreads(threads).slice(0, MAX_CARDS);
if (!threads.length) { inProgress=false; return; }
const cards = await Promise.all(threads.map(enrichThread));
try { localStorage.setItem(CACHE_KEY, JSON.stringify({ time: Date.now(), cards })); } catch(e){}
renderCards(cards, true);
} catch(err) {
console.error('nfCarousel loadData error', err);
} finally {
inProgress = false;
}
}

try {
const cached = JSON.parse(localStorage.getItem(CACHE_KEY) || '{}');
if (cached.cards && Date.now() - (cached.time || 0) < CACHE_TIME) {
renderCards(cached.cards, false);
} else {
await loadData(true);
}
} catch(e) {
await loadData(true);
}

// Авто-обновление каждые 10 минут
setInterval(() => loadData(false), 10 * 60 * 1000);

window.addEventListener('resize', () => { setSlideSizes(); updateCarouselPosition(); }, { passive: true });

})();
</script>

Мурчанн

Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
Дата: Понедельник, 27.10.2025, 00:18 | Сообщение # 4 | | Написал: Начинающий
Автор темы
Мурчанн не в сети
        Сообщений:101
         Регистрация:20.10.2016

Безопасность не дремлет, а разработчики браузеров уже в полном ах*е.

Срочно прячьте пароли в носках!

Мурчанн

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