|
| Дата: Воскресенье, 09.11.2025, 20:27 | Сообщение # 1 |
|
Написал: Узнаваемый
|
|
Автор темы
Мурчанн
не в сети
Сообщений:162
|
|
Тут полное описание как работает Парсер.
Описание работы Парсера

Код <style>
/* Контейнер */ #custom-forum-cards { display: flex; flex-wrap: wrap; gap: 20px; justify-content: flex-start; width: 100%;
/* Отступ снизу под последним рядом карточек */ margin-bottom: -25px; /* <- здесь можно регулировать отступы */ }
/* Карточка с эффектом DVD-диска */ .custom-card { flex: 0 0 calc(25% - 15px); box-sizing: border-box; position: relative; border-radius: 14px; overflow: hidden; color: #fff; cursor: pointer; text-decoration: none; margin-bottom: 10px; /* вертикальный отступ между рядами */
/* Основной фон + блики */ background: radial-gradient(circle at 30% 20%, #2a2a2a 0%, #141414 80%); background-image: radial-gradient(circle at 50% 10%, rgba(255,255,255,0.28) 0%, transparent 60%), radial-gradient(circle at 30% 80%, rgba(255,255,255,0.08) 0%, transparent 70%), radial-gradient(circle at 80% 30%, rgba(133,91,151,0.1) 0%, transparent 70%); background-blend-mode: screen;
border: 1px solid rgba(255,255,255,0.06);
/* Глубокие тени и выпуклость */ box-shadow: inset 0 4px 8px rgba(255,255,255,0.18), inset 0 -6px 12px rgba(0,0,0,0.9), 0 12px 30px rgba(0,0,0,0.95), 0 0 35px rgba(133,91,151,0.35);
transform: perspective(600px) translateZ(0); transition: transform 0.4s ease, box-shadow 0.4s ease, border-color 0.4s ease; }
.custom-card:hover { transform: translateY(-0px); box-shadow: inset 0 3px 6px rgba(255,255,255,0.15), inset 0 -6px 14px rgba(0,0,0,0.85), 0 24px 50px rgba(0,0,0,0.9), 0 0 50px rgba(133,91,151,0.45); border-color: #855b97; }
/* Верхняя обложка «DVD-крышка» */ .custom-card-header { position: absolute; top: -25px; left: 0; width: 100%; height: 50px; border-top-left-radius: 14px; border-top-right-radius: 14px; background: linear-gradient(145deg, #5b85a1 0%, #855b97 50%, #2a2a2a 100%); box-shadow: inset 0 4px 6px rgba(255,255,255,0.2), inset 0 -4px 6px rgba(0,0,0,0.6), 0 2px 6px rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 10; pointer-events: none; font-size: 12px; font-weight: bold; text-transform: uppercase; color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,0.6); }
/* Текст внутри крышки (его можно двигать) */ .custom-card-header-text { display: inline-block; transform: translate(var(--header-text-x, 0px), var(--header-text-y, 0px)); }
/* Обложка темы с учетом верхней крышки */ .custom-card-img { width: 100%; height: 170px; overflow: hidden; position: relative; border-bottom: 1px solid #333; margin-top: 20px; /* отступ, чтобы крышка не перекрывала */ }
.custom-card-img img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.6s ease, filter 0.4s ease; }
.custom-card:hover .custom-card-img img { transform: scale(1.08); filter: brightness(1.1); }
/* Заголовок карточки */ .custom-card-title { font-weight: 700; font-size: 14px; padding: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-shadow: 0 1px 3px rgba(0,0,0,0.8), 0 0 6px rgba(255,255,255,0.1); }
/* Краткое описание */ .custom-card-desc { font-size: 12px; color: #ccc; padding: 0 10px 10px 10px; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical; white-space: normal; opacity: 0.9; text-shadow: 0 1px 2px rgba(0,0,0,0.5); }
/* Бейдж */ .custom-card-badge { position: absolute; top: 12px; left: 12px; background: linear-gradient(135deg,#855b97,#5b85a1); padding: 4px 8px; border-radius: 8px; font-size: 11px; font-weight: bold; text-transform: uppercase; letter-spacing: 0.5px; box-shadow: 0 0 12px rgba(133,59,151,0.7), inset 0 1px 2px rgba(255,255,255,0.25); }
/* Эффект затемнения при наведении (сильный) */ .custom-card::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0); /* изначально прозрачный */ transition: background-color 0.3s ease; pointer-events: none; border-radius: 14px; /* чтобы совпадало с радиусом карточки */ }
.custom-card:hover::after { background-color: rgba(0, 0, 0, 0.6); /* сильное затемнение */ }
/* Контейнер для теней */ .dark-border { overflow: visible !important; position: relative; z-index: 2; background: transparent; } </style>
<section class="sect flex-grow-1"> <div class="sect__header d-flex"> <h2 class="sect__title flex-grow-1"> Forum Updates <img src="https://jordan.moy.su/forumimages/3droom.png" alt="3D Room Icon" style="height:24px; margin-left:-2px; vertical-align:middle;"> </h2> </div>
<div class="dark-border"> <div id="custom-forum-cards"></div> </div> </section>
<script> (async function() { const container = document.querySelector('#custom-forum-cards'); if (!container) return;
const forumListUrl = '/forum/0-0-1-34'; const MAX_CARDS = 8; const CACHE_KEY = 'forum_cards_cache'; const CACHE_TIME = 30 * 60 * 1000; // 30 минут
// Функция для рендера карточек function renderCards(cards) { container.innerHTML = ''; cards.forEach(parsed => { const title = parsed.title || 'Без названия'; const desc = parsed.text || ''; const img = parsed.img || '/forumimages/Newsletter-3.png';
const card = document.createElement('a'); card.className = 'custom-card'; card.href = parsed.href; card.target = '_self'; card.innerHTML = ` <div class="custom-card-header"> <div class="custom-card-header-text" style="--header-text-x: 0px; --header-text-y: 8px;"> Forum Updates </div> </div> <div class="custom-card-img"> <img src="${img}" alt="${title}"> <div class="custom-card-badge">Новое</div> </div> <div class="custom-card-title">${title}</div> <div class="custom-card-desc">${desc}</div> `; container.appendChild(card); }); }
// Функция для загрузки HTML async function fetchHTML(url) { const res = await fetch(url); return await res.text(); }
// Парсинг списка тем function parseThreadsFromList(html) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const arr = []; const items = doc.querySelectorAll('.threadNametd .threadLink'); for (let i = 0; i < items.length && arr.length < MAX_CARDS; i++) { const a = items[i]; const titleEl = a.querySelector('.ipsType_pagetitle') || a; const title = (titleEl && titleEl.textContent || '').trim(); let href = a.getAttribute('href') || a.href || ''; if (href && !href.startsWith('http')) { href = window.location.origin + (href.startsWith('/') ? href : '/' + href); } if (href) arr.push({ href, title }); } return arr; }
// Парсинг первого поста каждой темы function parseFirstPost(html) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const post = doc.querySelector('.post_content, .post_body, .post, .ipsType_richText'); let text = '', img = ''; if (post) { text = post.textContent.trim().substring(0, 89); const imgEl = post.querySelector('img'); if (imgEl) { img = imgEl.getAttribute('src') || ''; if (img && !img.startsWith('http')) { const base = window.location.origin; img = img.startsWith('/') ? (base + img) : (base + '/' + img); } } } return { text, img }; }
// Проверка кэша const cached = localStorage.getItem(CACHE_KEY); if (cached) { const data = JSON.parse(cached); const now = Date.now(); if (now - data.time < CACHE_TIME) { renderCards(data.cards); return; } }
// Загрузка и рендер try { const listHtml = await fetchHTML(forumListUrl); const threads = parseThreadsFromList(listHtml);
const promises = threads.map(t => fetchHTML(t.href) .then(html => ({ ...t, ...parseFirstPost(html) })) .catch(() => null) );
const results = (await Promise.all(promises)).filter(Boolean);
localStorage.setItem(CACHE_KEY, JSON.stringify({ time: Date.now(), cards: results }));
renderCards(results); } catch (e) { console.error('Ошибка:', e); } })(); </script>
Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
|
|
|
|
|