feature/FenyaBot #26
5 changed files with 303 additions and 42 deletions
|
|
@ -94,9 +94,11 @@ async def fetch_uwu_post(tags: str) -> dict:
|
||||||
"caption": caption
|
"caption": caption
|
||||||
}
|
}
|
||||||
|
|
||||||
async def fetch_furtok_feed(safe: bool = True, page: int = 1) -> list[dict]:
|
async def fetch_furtok_feed(safe: bool = True, page: int = 1, extra_tags: str = "") -> list[dict]:
|
||||||
rating = "rating:safe" if safe else "-rating:safe"
|
rating = "rating:safe" if safe else "-rating:safe"
|
||||||
tags = f"{rating} animated order:random score:>200"
|
tags = f"{rating} animated order:random score:>200"
|
||||||
|
if extra_tags and extra_tags.strip():
|
||||||
|
tags = f"{extra_tags.strip()} animated order:random score:>200"
|
||||||
|
|
||||||
url = "https://e621.net/posts.json"
|
url = "https://e621.net/posts.json"
|
||||||
params = {
|
params = {
|
||||||
|
|
|
||||||
|
|
@ -389,11 +389,12 @@ async def get_uwu_api(
|
||||||
async def get_furtok_feed_api(
|
async def get_furtok_feed_api(
|
||||||
safe: bool = True,
|
safe: bool = True,
|
||||||
page: int = 1,
|
page: int = 1,
|
||||||
|
tags: str = "",
|
||||||
user: dict = Depends(get_current_user),
|
user: dict = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
"""Получить ленту анимированных постов для FurTok."""
|
"""Получить ленту анимированных постов для FurTok."""
|
||||||
try:
|
try:
|
||||||
feed = await fetch_furtok_feed(safe=safe, page=page)
|
feed = await fetch_furtok_feed(safe=safe, page=page, extra_tags=tags)
|
||||||
return {"feed": feed}
|
return {"feed": feed}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception("FurTok API failed")
|
logger.exception("FurTok API failed")
|
||||||
|
|
|
||||||
|
|
@ -891,6 +891,8 @@ body::before {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
scroll-snap-type: y mandatory;
|
scroll-snap-type: y mandatory;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
|
overscroll-behavior: none;
|
||||||
|
touch-action: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.furtok-feed::-webkit-scrollbar { display: none; }
|
.furtok-feed::-webkit-scrollbar { display: none; }
|
||||||
|
|
@ -924,11 +926,21 @@ body::before {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
background: linear-gradient(transparent, rgba(0,0,0,0.7));
|
background: linear-gradient(transparent, rgba(0,0,0,0.7));
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
gap: 8px;
|
||||||
|
align-items: flex-start;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.furtok-caption {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0 1px 6px rgba(0,0,0,0.8);
|
||||||
|
max-width: 80%;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
.furtok-stats {
|
.furtok-stats {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
|
@ -937,6 +949,7 @@ body::before {
|
||||||
color: rgba(255,255,255,0.85);
|
color: rgba(255,255,255,0.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.furtok-header {
|
.furtok-header {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
@ -966,6 +979,31 @@ body::before {
|
||||||
color: rgba(255,255,255,0.8);
|
color: rgba(255,255,255,0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.furtok-header-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.furtok-gear-btn {
|
||||||
|
background: rgba(255,255,255,0.15);
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.furtok-gear-btn:hover,
|
||||||
|
.furtok-gear-btn:active {
|
||||||
|
background: rgba(255,255,255,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
.furtok-toggle input[type="checkbox"] {
|
.furtok-toggle input[type="checkbox"] {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
|
|
@ -998,6 +1036,68 @@ body::before {
|
||||||
transform: translateX(18px);
|
transform: translateX(18px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tags editor panel */
|
||||||
|
.furtok-tags-panel {
|
||||||
|
position: absolute;
|
||||||
|
top: 48px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 6;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: rgba(0, 0, 0, 0.75);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.furtok-tags-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.furtok-tags-input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid rgba(255,255,255,0.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
color: white;
|
||||||
|
font-size: 13px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.furtok-tags-input::placeholder {
|
||||||
|
color: rgba(255,255,255,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.furtok-tags-input:focus {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.furtok-tags-apply {
|
||||||
|
padding: 8px 14px;
|
||||||
|
background: var(--accent);
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: white;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.furtok-tags-apply:active {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.furtok-tags-hint {
|
||||||
|
font-size: 11px;
|
||||||
|
color: rgba(255,255,255,0.45);
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
.furtok-loader {
|
.furtok-loader {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
scroll-snap-align: start;
|
scroll-snap-align: start;
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,6 @@ export const API = {
|
||||||
getTop: () => api('/api/top'),
|
getTop: () => api('/api/top'),
|
||||||
getSchedule: (day) => api(`/api/schedule${day != null ? `?day=${day}` : ''}`),
|
getSchedule: (day) => api(`/api/schedule${day != null ? `?day=${day}` : ''}`),
|
||||||
getUwu: (tags) => api(`/api/uwu?tags=${encodeURIComponent(tags || 'rating:safe score:>500 -animated')}`),
|
getUwu: (tags) => api(`/api/uwu?tags=${encodeURIComponent(tags || 'rating:safe score:>500 -animated')}`),
|
||||||
getFurtokFeed: (safe = true, page = 1) => api(`/api/furtok?safe=${safe}&page=${page}`),
|
getFurtokFeed: (safe = true, page = 1, tags = '') => api(`/api/furtok?safe=${safe}&page=${page}${tags ? '&tags=' + encodeURIComponent(tags) : ''}`),
|
||||||
talk: (text) => api('/api/talk', { method: 'POST', body: { text } }),
|
talk: (text) => api('/api/talk', { method: 'POST', body: { text } }),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -504,26 +504,78 @@ let furtokSafe = true;
|
||||||
let furtokPage = 1;
|
let furtokPage = 1;
|
||||||
let furtokLoading = false;
|
let furtokLoading = false;
|
||||||
let furtokWrapper = null;
|
let furtokWrapper = null;
|
||||||
|
let furtokCurrentIndex = 0;
|
||||||
|
let furtokCards = [];
|
||||||
|
let furtokCustomTags = '';
|
||||||
|
|
||||||
|
// Подписи из uwu.py — рандомно появляются на карточках
|
||||||
|
const FURTOK_CAPTIONS = [
|
||||||
|
"Ня :3", "uwu", "OwO", "Мурк :3", "Мяу~", "Фыр-фыр", "Лапки! 🐾",
|
||||||
|
"Какой пушистик! :3", "Милота!", "Гав!", "Ауф", "Няшно!",
|
||||||
|
"Смотри какая прелесть", "owo what's this", "Твой пушистый друг",
|
||||||
|
"🦊", "🐶", "🐱", "мур-мяу", "пуньк", "кусь", ">w<", "^w^",
|
||||||
|
"Хвостатый сюрприз", "Держи пушистика :3", "Оуо", "Ави!", "UwU", "~nya",
|
||||||
|
"Господи, опять e621...", "Осуждаю, но смотрю", "Мама, я фурри",
|
||||||
|
"Держи своего кринж-пушистика", "Надеюсь, тебе не стыдно",
|
||||||
|
"Товарищ майор уже выехал", "Слишком много интернета на сегодня",
|
||||||
|
"Для этого и придумали интернет", "*Тяжелый вздох*",
|
||||||
|
"Лучше бы на завод пошел", "Опять дрочи... ОЙ ТО ЕСТЬ НЯ :3",
|
||||||
|
"Удали интернет", "И зачем я это только парсю...",
|
||||||
|
"Смотри, но только никому не рассказывай", "Эххх... uwu...",
|
||||||
|
"В дурке сегодня день открытых дверей",
|
||||||
|
"Я нейросеть, помогите, меня держат в заложниках"
|
||||||
|
];
|
||||||
|
|
||||||
|
function randomCaption() {
|
||||||
|
return FURTOK_CAPTIONS[Math.floor(Math.random() * FURTOK_CAPTIONS.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function _furtokGetTags() {
|
||||||
|
// Если кастомные теги заданы — используем их (safe управляется юзером через теги)
|
||||||
|
if (furtokCustomTags.trim()) return furtokCustomTags.trim();
|
||||||
|
// Иначе стандартный запрос с safe-переключателем
|
||||||
|
const rating = furtokSafe ? 'rating:safe' : '-rating:safe';
|
||||||
|
return `${rating} score:>200`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _furtokReload(feed) {
|
||||||
|
furtokPage = 1;
|
||||||
|
furtokCurrentIndex = 0;
|
||||||
|
furtokCards = [];
|
||||||
|
feed.innerHTML = '';
|
||||||
|
loadFurtokPage(feed);
|
||||||
|
}
|
||||||
|
|
||||||
function renderFurtok() {
|
function renderFurtok() {
|
||||||
// Очищаем основной контейнер и создаём полноэкранную обёртку
|
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
|
||||||
// Удаляем старый wrapper если есть
|
|
||||||
document.querySelector('.furtok-wrapper')?.remove();
|
document.querySelector('.furtok-wrapper')?.remove();
|
||||||
|
|
||||||
furtokPage = 1;
|
furtokPage = 1;
|
||||||
furtokLoading = false;
|
furtokLoading = false;
|
||||||
|
furtokCurrentIndex = 0;
|
||||||
|
furtokCards = [];
|
||||||
|
|
||||||
furtokWrapper = document.createElement('div');
|
furtokWrapper = document.createElement('div');
|
||||||
furtokWrapper.className = 'furtok-wrapper';
|
furtokWrapper.className = 'furtok-wrapper';
|
||||||
furtokWrapper.innerHTML = `
|
furtokWrapper.innerHTML = `
|
||||||
<div class="furtok-header">
|
<div class="furtok-header">
|
||||||
<div class="furtok-title">🐺 FurTok</div>
|
<div class="furtok-title">🐺 FurTok</div>
|
||||||
<label class="furtok-toggle">
|
<div class="furtok-header-right">
|
||||||
<span>Safe</span>
|
<label class="furtok-toggle" id="furtok-safe-wrap" ${furtokCustomTags.trim() ? 'style="display:none"' : ''}>
|
||||||
<input type="checkbox" id="furtok-safe" ${furtokSafe ? 'checked' : ''}>
|
<span>Safe</span>
|
||||||
</label>
|
<input type="checkbox" id="furtok-safe" ${furtokSafe ? 'checked' : ''}>
|
||||||
|
</label>
|
||||||
|
<button class="furtok-gear-btn" id="furtok-gear">⚙️</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="furtok-tags-panel" id="furtok-tags-panel" style="display:none">
|
||||||
|
<div class="furtok-tags-row">
|
||||||
|
<input type="text" id="furtok-tags-input" class="furtok-tags-input"
|
||||||
|
placeholder="rating:safe score:>200 wolf"
|
||||||
|
value="${furtokCustomTags}">
|
||||||
|
<button class="furtok-tags-apply" id="furtok-tags-apply">🔄</button>
|
||||||
|
</div>
|
||||||
|
<div class="furtok-tags-hint">e621 теги через пробел. Пусто = стандартный запрос + Safe</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="furtok-feed" id="furtok-feed"></div>
|
<div class="furtok-feed" id="furtok-feed"></div>
|
||||||
`;
|
`;
|
||||||
|
|
@ -531,39 +583,151 @@ function renderFurtok() {
|
||||||
|
|
||||||
const feed = document.getElementById('furtok-feed');
|
const feed = document.getElementById('furtok-feed');
|
||||||
const safeToggle = document.getElementById('furtok-safe');
|
const safeToggle = document.getElementById('furtok-safe');
|
||||||
|
const safeWrap = document.getElementById('furtok-safe-wrap');
|
||||||
|
const gearBtn = document.getElementById('furtok-gear');
|
||||||
|
const tagsPanel = document.getElementById('furtok-tags-panel');
|
||||||
|
const tagsInput = document.getElementById('furtok-tags-input');
|
||||||
|
const tagsApply = document.getElementById('furtok-tags-apply');
|
||||||
|
|
||||||
|
// Safe toggle
|
||||||
safeToggle.addEventListener('change', () => {
|
safeToggle.addEventListener('change', () => {
|
||||||
furtokSafe = safeToggle.checked;
|
furtokSafe = safeToggle.checked;
|
||||||
furtokPage = 1;
|
_furtokReload(feed);
|
||||||
feed.innerHTML = '';
|
|
||||||
loadFurtokPage(feed);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Infinite scroll
|
// Gear button — toggle tags panel
|
||||||
feed.addEventListener('scroll', () => {
|
gearBtn.addEventListener('click', () => {
|
||||||
if (furtokLoading) return;
|
haptic();
|
||||||
if (feed.scrollTop + feed.clientHeight >= feed.scrollHeight - 200) {
|
const visible = tagsPanel.style.display !== 'none';
|
||||||
furtokPage++;
|
tagsPanel.style.display = visible ? 'none' : 'flex';
|
||||||
loadFurtokPage(feed);
|
if (!visible) tagsInput.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply tags
|
||||||
|
tagsApply.addEventListener('click', () => {
|
||||||
|
haptic();
|
||||||
|
furtokCustomTags = tagsInput.value.trim();
|
||||||
|
// Если кастомные теги — прячем Safe (юзер сам контролирует rating)
|
||||||
|
safeWrap.style.display = furtokCustomTags ? 'none' : '';
|
||||||
|
tagsPanel.style.display = 'none';
|
||||||
|
_furtokReload(feed);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enter в поле тегов
|
||||||
|
tagsInput.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter') tagsApply.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Жёсткий свайп как в TikTok
|
||||||
|
setupTikTokScroll(feed);
|
||||||
|
|
||||||
|
loadFurtokPage(feed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupTikTokScroll(feed) {
|
||||||
|
let touchStartY = 0;
|
||||||
|
let touchDeltaY = 0;
|
||||||
|
let isSwiping = false;
|
||||||
|
|
||||||
|
feed.addEventListener('touchstart', (e) => {
|
||||||
|
touchStartY = e.touches[0].clientY;
|
||||||
|
touchDeltaY = 0;
|
||||||
|
isSwiping = true;
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
feed.addEventListener('touchmove', (e) => {
|
||||||
|
if (!isSwiping) return;
|
||||||
|
touchDeltaY = e.touches[0].clientY - touchStartY;
|
||||||
|
// Показываем визуальный сдвиг текущей карточки
|
||||||
|
const current = furtokCards[furtokCurrentIndex];
|
||||||
|
if (current) {
|
||||||
|
const clamped = Math.max(-120, Math.min(120, touchDeltaY));
|
||||||
|
current.style.transform = `translateY(${clamped}px)`;
|
||||||
|
current.style.transition = 'none';
|
||||||
|
}
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
feed.addEventListener('touchend', () => {
|
||||||
|
if (!isSwiping) return;
|
||||||
|
isSwiping = false;
|
||||||
|
|
||||||
|
const current = furtokCards[furtokCurrentIndex];
|
||||||
|
if (current) {
|
||||||
|
current.style.transform = '';
|
||||||
|
current.style.transition = 'transform 0.3s ease';
|
||||||
|
}
|
||||||
|
|
||||||
|
const threshold = 50;
|
||||||
|
|
||||||
|
if (touchDeltaY < -threshold && furtokCurrentIndex < furtokCards.length - 1) {
|
||||||
|
// Свайп вверх — следующая
|
||||||
|
furtokCurrentIndex++;
|
||||||
|
scrollToCard(feed);
|
||||||
|
haptic();
|
||||||
|
// Подгрузка если близко к концу
|
||||||
|
if (furtokCurrentIndex >= furtokCards.length - 2 && !furtokLoading) {
|
||||||
|
furtokPage++;
|
||||||
|
loadFurtokPage(feed);
|
||||||
|
}
|
||||||
|
} else if (touchDeltaY > threshold && furtokCurrentIndex > 0) {
|
||||||
|
// Свайп вниз — предыдущая
|
||||||
|
furtokCurrentIndex--;
|
||||||
|
scrollToCard(feed);
|
||||||
|
haptic();
|
||||||
|
}
|
||||||
|
touchDeltaY = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Десктоп: колесо мыши
|
||||||
|
let wheelLock = false;
|
||||||
|
feed.addEventListener('wheel', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (wheelLock) return;
|
||||||
|
wheelLock = true;
|
||||||
|
setTimeout(() => { wheelLock = false; }, 400);
|
||||||
|
|
||||||
|
if (e.deltaY > 0 && furtokCurrentIndex < furtokCards.length - 1) {
|
||||||
|
furtokCurrentIndex++;
|
||||||
|
scrollToCard(feed);
|
||||||
|
if (furtokCurrentIndex >= furtokCards.length - 2 && !furtokLoading) {
|
||||||
|
furtokPage++;
|
||||||
|
loadFurtokPage(feed);
|
||||||
|
}
|
||||||
|
} else if (e.deltaY < 0 && furtokCurrentIndex > 0) {
|
||||||
|
furtokCurrentIndex--;
|
||||||
|
scrollToCard(feed);
|
||||||
|
}
|
||||||
|
}, { passive: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToCard(feed) {
|
||||||
|
const card = furtokCards[furtokCurrentIndex];
|
||||||
|
if (!card) return;
|
||||||
|
feed.scrollTo({ top: card.offsetTop, behavior: 'smooth' });
|
||||||
|
|
||||||
|
// Управляем видео: стопим все, играем текущее
|
||||||
|
furtokCards.forEach((c, i) => {
|
||||||
|
const v = c.querySelector('video');
|
||||||
|
if (!v) return;
|
||||||
|
if (i === furtokCurrentIndex) {
|
||||||
|
v.play().catch(() => {});
|
||||||
|
} else {
|
||||||
|
v.pause();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Первая загрузка
|
|
||||||
loadFurtokPage(feed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadFurtokPage(feedEl) {
|
async function loadFurtokPage(feedEl) {
|
||||||
if (furtokLoading) return;
|
if (furtokLoading) return;
|
||||||
furtokLoading = true;
|
furtokLoading = true;
|
||||||
|
|
||||||
// Лоадер
|
|
||||||
const loader = document.createElement('div');
|
const loader = document.createElement('div');
|
||||||
loader.className = 'furtok-loader';
|
loader.className = 'furtok-loader';
|
||||||
loader.textContent = '⏳ Загрузка...';
|
loader.textContent = '⏳ Загрузка...';
|
||||||
feedEl.appendChild(loader);
|
feedEl.appendChild(loader);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await API.getFurtokFeed(furtokSafe, furtokPage);
|
const res = await API.getFurtokFeed(furtokSafe, furtokPage, furtokCustomTags);
|
||||||
loader.remove();
|
loader.remove();
|
||||||
|
|
||||||
if (!res.feed || res.feed.length === 0) {
|
if (!res.feed || res.feed.length === 0) {
|
||||||
|
|
@ -585,9 +749,12 @@ async function loadFurtokPage(feedEl) {
|
||||||
mediaHtml = `<video src="${post.url}" loop playsinline preload="metadata" muted></video>`;
|
mediaHtml = `<video src="${post.url}" loop playsinline preload="metadata" muted></video>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const caption = randomCaption();
|
||||||
|
|
||||||
card.innerHTML = `
|
card.innerHTML = `
|
||||||
${mediaHtml}
|
${mediaHtml}
|
||||||
<div class="furtok-overlay">
|
<div class="furtok-overlay">
|
||||||
|
<div class="furtok-caption">${caption}</div>
|
||||||
<div class="furtok-stats">
|
<div class="furtok-stats">
|
||||||
<span>⭐ ${post.score}</span>
|
<span>⭐ ${post.score}</span>
|
||||||
<span>❤️ ${post.fav_count}</span>
|
<span>❤️ ${post.fav_count}</span>
|
||||||
|
|
@ -606,10 +773,15 @@ async function loadFurtokPage(feedEl) {
|
||||||
}
|
}
|
||||||
|
|
||||||
feedEl.appendChild(card);
|
feedEl.appendChild(card);
|
||||||
|
furtokCards.push(card);
|
||||||
});
|
});
|
||||||
|
|
||||||
// IntersectionObserver для автоплейа
|
// Автоплей первого видео при первой загрузке
|
||||||
setupAutoplay(feedEl);
|
if (furtokCurrentIndex === 0 && furtokCards.length > 0) {
|
||||||
|
const firstVideo = furtokCards[0].querySelector('video');
|
||||||
|
if (firstVideo) firstVideo.play().catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
haptic('success');
|
haptic('success');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
loader.remove();
|
loader.remove();
|
||||||
|
|
@ -621,20 +793,6 @@ async function loadFurtokPage(feedEl) {
|
||||||
furtokLoading = false;
|
furtokLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupAutoplay(feedEl) {
|
|
||||||
const videos = feedEl.querySelectorAll('.furtok-card video');
|
|
||||||
const observer = new IntersectionObserver((entries) => {
|
|
||||||
entries.forEach(entry => {
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
entry.target.play().catch(() => {});
|
|
||||||
} else {
|
|
||||||
entry.target.pause();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, { root: feedEl, threshold: 0.6 });
|
|
||||||
|
|
||||||
videos.forEach(v => observer.observe(v));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ══════════════════════════════════════════
|
// ══════════════════════════════════════════
|
||||||
// PAGE: CHAT
|
// PAGE: CHAT
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue