import React, { useEffect, useState, useCallback, useRef } from 'react';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { jwtDecode } from 'jwt-decode';
import axios from 'axios';
import * as Kubikon3DApi from '../api/Kubikon3DService';
import {
    API_USER_get_my_profile,
    API_USER_put_birth_date,
    STORYS_addres,
} from '../api/API';
import { useAuth } from '../auth/AuthContext.jsx';
import {
    KT, cardStyle, buttonPrimary, chipStyle,
    KUBIKON_KEYFRAMES, skeletonStyle,
} from '../utils/kubikonTheme';
import RublocsLogo from '../components/RublocsLogo/RublocsLogo';
import useDeviceType from '../hooks/useDeviceType';
import PleeseReg from '../components/PleeseReg/PleeseReg';
import Icon from '../editor/Icon';

/**
 * KubikonFeed — главная страница Рублокса.
 * Современный дизайн: hero-баннер с градиентом, glassmorphism, 3D-tilt
 * на карточках, плавающие декоративные кубики, badge «🔥 HOT» на топовых.
 */

// Вкладки умной ленты (RUBLOX_SMART_FEED_PLAN.md, Фаза 6).
// id совпадает с параметром tab бэкенда.
const SORTS = [
    { id: 'recommended', label: 'Рекомендуем' },
    { id: 'new',         label: 'Новое' },
    { id: 'popular',     label: 'Популярное' },
    { id: 'top_week',    label: 'Топ недели' },
];

const AGE_MODES = [
    { id: 'auto', label: 'По возрасту' },
    { id: 6,      label: '6+' },
    { id: 12,     label: '12+' },
    { id: 16,     label: '16+' },
    { id: 18,     label: '18+' },
    { id: 'off',  label: 'Все' },
];

const RATING_FILTERS = [
    { id: 0,  label: 'Все' },
    { id: 50, label: '≥ 50%' },
    { id: 70, label: '≥ 70%' },
    { id: 90, label: '≥ 90%' },
];

const AGE_MODE_KEY      = 'kubikon_feed_age_mode';
const RATING_FILTER_KEY = 'kubikon_feed_min_rating';

function userAgeToMaxAge(age) {
    if (age == null) return 12;
    if (age < 12) return 6;
    if (age < 16) return 12;
    if (age < 18) return 16;
    return 18;
}

function ageFromBirthDate(iso) {
    if (!iso) return null;
    const [y, m, d] = iso.split('-').map(Number);
    if (!y || !m || !d) return null;
    const today = new Date();
    let age = today.getFullYear() - y;
    if (today.getMonth() + 1 < m
        || (today.getMonth() + 1 === m && today.getDate() < d)) {
        age -= 1;
    }
    return age >= 0 && age <= 120 ? age : null;
}

const KubikonFeed = () => {
    const navigate = useNavigate();
    const { isAuthenticated, isLoading: authLoading } = useAuth();
    const [searchParams, setSearchParams] = useSearchParams();
    // На десктопе у /kubikon есть левое меню сайта — кнопка «Майнкрафтия»
    // в шапке не нужна. На мобиле/планшете меню школы скрыто, кнопку
    // оставляем как единственный путь назад.
    const { isPhone, isTablet } = useDeviceType();
    const isMobileLayout = isPhone || isTablet;

    const userId = (() => {
        try {
            const t = localStorage.getItem('Authorization');
            if (!t) return null;
            const p = jwtDecode(t);
            return p?.id || p?.user_id || null;
        } catch { return null; }
    })();

    // Гость МОЖЕТ просматривать ленту (с лендинга /). При попытке открыть игру
    // или открыть студию — показываем модалку с просьбой войти/зарегистрироваться.
    const [showRegModal, setShowRegModal] = useState(false);
    const requireAuth = useCallback((action) => {
        if (isAuthenticated) {
            action();
        } else {
            setShowRegModal(true);
        }
    }, [isAuthenticated]);

    const [items, setItems] = useState([]);
    const [totalCount, setTotalCount] = useState(0);
    const [page, setPage] = useState(1);
    const [hasMore, setHasMore] = useState(false);
    const [loadingMore, setLoadingMore] = useState(false);
    const PER_PAGE = 20;
    const [searchResults, setSearchResults] = useState(null);
    const [loading, setLoading] = useState(false);
    // sort здесь = выбранная вкладка ленты (recommended/new/popular/top_week).
    const [sort, setSort] = useState(searchParams.get('sort') || 'recommended');
    const [searchQ, setSearchQ] = useState('');

    const [ageMode, setAgeMode] = useState(() => {
        try {
            const saved = localStorage.getItem(AGE_MODE_KEY);
            if (saved == null) return 'auto';
            const n = Number(saved);
            return Number.isFinite(n) ? n : saved;
        } catch (e) { return 'auto'; }
    });
    const [minRating, setMinRating] = useState(() => {
        try {
            const v = Number(localStorage.getItem(RATING_FILTER_KEY) || 0);
            return Number.isFinite(v) ? v : 0;
        } catch (e) { return 0; }
    });

    const [birthDate, setBirthDate] = useState(null);
    const [profileLoaded, setProfileLoaded] = useState(false);
    const userAge = ageFromBirthDate(birthDate);
    const [askDob, setAskDob] = useState(false);

    useEffect(() => {
        const tok = localStorage.getItem('Authorization');
        if (!tok) { setProfileLoaded(true); return; }
        axios.get(API_USER_get_my_profile,
                  { headers: { Authorization: tok }, timeout: 6000 })
            .then(r => {
                const bd = r.data?.data?.birthDate ?? r.data?.birthDate ?? null;
                setBirthDate(bd || null);
                if (!bd) setAskDob(true);
            })
            .catch(() => { /* ignore */ })
            .finally(() => setProfileLoaded(true));
    }, []);

    useEffect(() => {
        try { localStorage.setItem(AGE_MODE_KEY, String(ageMode)); }
        catch (e) { /* ignore */ }
    }, [ageMode]);

    useEffect(() => {
        try { localStorage.setItem(RATING_FILTER_KEY, String(minRating)); }
        catch (e) { /* ignore */ }
    }, [minRating]);

    const maxAge = (() => {
        if (ageMode === 'off') return null;
        if (ageMode === 'auto') return userAgeToMaxAge(userAge);
        const n = Number(ageMode);
        return Number.isFinite(n) ? n : null;
    })();

    const load = useCallback(async () => {
        setLoading(true);
        try {
            // Первая страница. Total — для счётчика; остальные подгружаются
            // ленивой подгрузкой по скроллу (см. loadMore + IntersectionObserver).
            const res = await Kubikon3DApi.getFeed(
                1, sort, maxAge,
                minRating > 0 ? minRating : null,
                { per_page: PER_PAGE }
            );
            const projects = res.data?.projects || [];
            const total = res.data?.total ?? projects.length;
            setItems(projects);
            setTotalCount(total);
            setPage(1);
            setHasMore(projects.length < total);
        } catch (e) {
            console.error('[KubikonFeed] load error:', e);
        }
        setLoading(false);
    }, [sort, maxAge, minRating]);

    // Догрузка следующей страницы при достижении низа ленты
    const loadMore = useCallback(async () => {
        if (loadingMore || loading || !hasMore) return;
        setLoadingMore(true);
        const nextPage = page + 1;
        try {
            const res = await Kubikon3DApi.getFeed(
                nextPage, sort, maxAge,
                minRating > 0 ? minRating : null,
                { per_page: PER_PAGE }
            );
            const projects = res.data?.projects || [];
            const total = res.data?.total ?? totalCount;
            setItems(prev => {
                const merged = [...prev, ...projects];
                setHasMore(merged.length < total);
                return merged;
            });
            setTotalCount(total);
            setPage(nextPage);
        } catch (e) {
            console.error('[KubikonFeed] loadMore error:', e);
        } finally {
            setLoadingMore(false);
        }
    }, [loadingMore, loading, hasMore, page, sort, maxAge, minRating, totalCount]);

    // IntersectionObserver — догружаем когда последний sentinel виден
    const sentinelRef = useRef(null);
    useEffect(() => {
        if (!hasMore || loading) return;
        const el = sentinelRef.current;
        if (!el) return;
        const obs = new IntersectionObserver((entries) => {
            for (const e of entries) {
                if (e.isIntersecting) loadMore();
            }
        }, { rootMargin: '300px 0px' });
        obs.observe(el);
        return () => obs.disconnect();
    }, [hasMore, loading, loadMore, items.length]);

    useEffect(() => {
        if (!profileLoaded) return;
        load();
        // recommended — вкладка по умолчанию, для неё URL не засоряем.
        setSearchParams(sort === 'recommended' ? {} : { sort }, { replace: true });
    }, [load, sort, setSearchParams, profileLoaded]);

    useEffect(() => {
        const q = searchQ.trim();
        if (q.length < 2) {
            setSearchResults(null);
            return;
        }
        let active = true;
        const t = setTimeout(async () => {
            try {
                const res = await Kubikon3DApi.searchProjects(q, maxAge);
                if (active) setSearchResults(res.data?.projects || []);
            } catch (e) { /* ignore */ }
        }, 300);
        return () => { active = false; clearTimeout(t); };
    }, [searchQ, maxAge]);

    const list = searchResults != null ? searchResults : items;

    return (
        <div style={{
            minHeight: '100vh',
            background: KT.bg,
            color: KT.text,
            fontFamily: KT.font,
        }}>
            <style>{KUBIKON_KEYFRAMES}</style>

            {/* === Модалка для гостя — войди или зарегистрируйся === */}
            {showRegModal && (
                <div
                    onClick={() => setShowRegModal(false)}
                    style={{
                        position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
                        background: 'rgba(15, 8, 35, 0.78)',
                        backdropFilter: 'blur(8px)',
                        WebkitBackdropFilter: 'blur(8px)',
                        zIndex: 9999,
                        display: 'flex', alignItems: 'center', justifyContent: 'center',
                        padding: 20,
                        animation: 'fadeIn 200ms ease',
                    }}
                >
                    <div
                        onClick={(e) => e.stopPropagation()}
                        style={{
                            background: '#fff',
                            borderRadius: 20,
                            padding: 30,
                            position: 'relative',
                            width: '100%', maxWidth: 460,
                            boxShadow: '0 30px 80px rgba(0,0,0,0.5)',
                        }}
                    >
                        <button
                            onClick={() => setShowRegModal(false)}
                            style={{
                                position: 'absolute', top: 12, right: 12,
                                width: 36, height: 36, borderRadius: 18,
                                border: 'none',
                                background: '#F3E8FF', color: '#1E1B4B',
                                fontSize: 20, fontWeight: 800,
                                cursor: 'pointer',
                            }}
                            aria-label="Закрыть"
                        ><Icon name="close" size={14} /></button>
                        <PleeseReg textDefault='Чтобы играть и создавать игры в Рублоксе' style={{width:'auto',height:'auto'}} />
                    </div>
                </div>
            )}

            {/* === Sticky header (glass) === */}
            <div style={{
                background: KT.glassDark,
                backdropFilter: 'blur(20px)',
                WebkitBackdropFilter: 'blur(20px)',
                borderBottom: `1px solid ${KT.borderSoft}`,
                padding: '14px 20px',
                position: 'sticky', top: 0, zIndex: 50,
            }}>
                <div style={{
                    maxWidth: 1240, margin: '0 auto',
                    display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap',
                }}>
                    {/* Возврат в школу — только на мобиле/планшете, где
                        меню школы скрыто. На десктопе слева есть левое меню
                        сайта, отдельная кнопка не нужна. */}
                    {isMobileLayout && (
                        <Link to="/" style={{
                            display: 'inline-flex', alignItems: 'center', gap: 6,
                            padding: '8px 14px',
                            background: KT.bgPage,
                            border: `1px solid ${KT.border}`,
                            borderRadius: 999,
                            color: KT.textSecondary,
                            textDecoration: 'none',
                            fontSize: 13, fontWeight: 600,
                            whiteSpace: 'nowrap',
                        }} title="Вернуться в Майнкрафтию">
                            <Icon name="arrow-left" size={14} /> Майнкрафтия
                        </Link>
                    )}
                    <Link to="/kubikon" style={{
                        display: 'flex', alignItems: 'center', gap: 10,
                        textDecoration: 'none',
                    }}>
                        <RublocsLogo size={38} />
                        <span style={{
                            fontSize: 22, fontWeight: 800, color: KT.text,
                            letterSpacing: -0.3,
                        }}>Рублокс</span>
                    </Link>
                    <Link to="/" style={{
                        padding: '8px 16px',
                        background: KT.bgPage,
                        border: `1px solid ${KT.border}`,
                        borderRadius: 999,
                        color: KT.textSecondary,
                        textDecoration: 'none',
                        fontSize: 13, fontWeight: 600,
                    }}><Icon name="folder-open" size={13} /> Мои игры</Link>
                    <SearchInput value={searchQ} onChange={setSearchQ} />
                </div>
            </div>

            {/* === HERO BANNER === */}
            {!searchResults && !searchQ && (
                <HeroBanner totalGames={totalCount || items.length} onCreate={() => requireAuth(() => navigate('/'))} />
            )}

            {/* === СКАЧАТЬ ПРИЛОЖЕНИЕ === */}
            {!searchResults && !searchQ && (
                <DownloadAppsBanner />
            )}

            {/* === ТЕМАТИЧЕСКИЕ СЕКЦИИ — РАДИКАЛЬНЫЙ РЕДИЗАЙН === */}
            {!searchResults && !searchQ && profileLoaded && (
                <div style={{
                    maxWidth: 1340, margin: '0 auto', padding: '8px 24px 0',
                    position: 'relative',
                }}>
                    {/* Анимированные золотые частицы фона */}
                    <BackgroundParticles />

                    {/* Активные события платформы */}
                    <EventBanner onCardClick={(url) => navigate(url)} />

                    {/* Live-statistics — пульсирующая полоса метрик */}
                    <LiveStatsBar totalGames={totalCount || items.length} />

                    {/* Чипы-теги быстрого перехода */}
                    <CategoryChips onScrollTo={(id) => {
                        const el = document.getElementById(id);
                        if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
                    }} />

                    {/* «Продолжить играть» — последняя сыгранная */}
                    {userId && (
                        <ResumeCard userId={userId}
                                    onClick={(p) => requireAuth(() => navigate(`/game/${p.id}`))} />
                    )}

                    {/* История — недавно играли */}
                    {userId && (
                        <div id="sec-history">
                        <HistorySection userId={userId}
                                        onCardClick={(p) => requireAuth(() => navigate(`/game/${p.id}`))} />
                        </div>
                    )}

                    {/* Избранное — мои сохранённые */}
                    {userId && (
                        <div id="sec-fav">
                        <FavoritesSection userId={userId}
                                          onCardClick={(p) => requireAuth(() => navigate(`/game/${p.id}`))} />
                        </div>
                    )}

                    {/* Спотлайт «Игра дня» — гигантский баннер */}
                    <FeaturedBanner
                        params={{ sort: 'top_week', maxAge, per_page: 1 }}
                        onCardClick={(p) => requireAuth(() => navigate(`/game/${p.id}`))}
                    />

                    {/* Тренды — резкий рост за 14 дней */}
                    <div id="sec-trending">
                    <TrendingSection
                        onCardClick={(p) => requireAuth(() => navigate(`/game/${p.id}`))}
                    />
                    </div>

                    {/* Топ-3 на пьедестале */}
                    <div id="sec-top">
                    <PodiumSection
                        title="Топ-3 на пьедестале"
                        subtitle="Самые любимые игры всех времён"
                        params={{ sort: 'popular', maxAge, per_page: 3 }}
                        onCardClick={(p) => requireAuth(() => navigate(`/game/${p.id}`))}
                    />
                    </div>

                    {/* Огненная неделя */}
                    <div id="sec-fire">
                    <CategorySection
                        layout="carousel"
                        title="Топ за неделю"
                        subtitle="Что сейчас на хайпе"
                        accent="#ff5e3a"
                        emoji="🔥"
                        params={{ sort: 'top_week', maxAge, per_page: 10 }}
                        onCardClick={(p) => requireAuth(() => navigate(`/game/${p.id}`))}
                    />
                    </div>

                    {/* Блок «Лучшие игры» — топ по рейтингу умной ленты */}
                    <div id="sec-premium">
                    <PremiumSection
                        params={{ maxAge, per_page: 6 }}
                        onCardClick={(p) => requireAuth(() => navigate(`/game/${p.id}`))}
                    />
                    </div>

                    {/* Мультиплеер — баннер-разделитель + 3 крупные */}
                    <SectionBanner
                        emoji="🎮"
                        title="Зови друзей в комнату!"
                        subtitle="Мультиплеер — играй вместе"
                        gradient="linear-gradient(135deg, #5b8bff 0%, #3357ff 50%, #1e2da5 100%)"
                    />
                    <div id="sec-mp">
                    <CategorySection
                        layout="spotlight"
                        title=""
                        subtitle=""
                        accent="#5b8bff"
                        emoji="🎮"
                        hideHeader={true}
                        params={{ multiplayer: true, sort: 'popular', maxAge, per_page: 6 }}
                        onCardClick={(p) => requireAuth(() => navigate(`/game/${p.id}`))}
                    />
                    </div>

                    {/* Новинки */}
                    <div id="sec-new">
                    <CategorySection
                        layout="carousel"
                        title="Новинки"
                        subtitle="Только что опубликованы — оцените первыми"
                        accent="#22d97a"
                        emoji="🆕"
                        params={{ sort: 'new', maxAge, per_page: 10 }}
                        onCardClick={(p) => requireAuth(() => navigate(`/game/${p.id}`))}
                    />
                    </div>

                    {/* Жанры — баннер-разделитель */}
                    <SectionBanner
                        emoji="🎨"
                        title="Что хочешь сегодня?"
                        subtitle="Выбери любимый жанр"
                        gradient="linear-gradient(135deg, #ec4899 0%, #9966ff 50%, #3b82f6 100%)"
                    />

                    {/* Жанры в 2 колонки на десктопе, 1 на мобилке */}
                    <div style={{
                        display: 'grid',
                        gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))',
                        gap: 24, marginTop: 24,
                    }}>
                        <div id="sec-platformer">
                        <CompactGenreCard
                            title="Платформеры"
                            subtitle="Прыгай, беги, не падай"
                            accent="#9966ff"
                            bgGradient="linear-gradient(135deg, rgba(153,102,255,0.12), rgba(153,102,255,0.02))"
                            params={{ genre: 'platformer', sort: 'popular', maxAge, per_page: 4 }}
                            onCardClick={(p) => requireAuth(() => navigate(`/game/${p.id}`))}
                        />
                        </div>
                        <div id="sec-puzzle">
                        <CompactGenreCard
                            title="Головоломки"
                            subtitle="Включи мозг"
                            accent="#3b82f6"
                            bgGradient="linear-gradient(135deg, rgba(59,130,246,0.12), rgba(59,130,246,0.02))"
                            params={{ genre: 'puzzle', sort: 'popular', maxAge, per_page: 4 }}
                            onCardClick={(p) => requireAuth(() => navigate(`/game/${p.id}`))}
                        />
                        </div>
                        <div id="sec-action">
                        <CompactGenreCard
                            title="Экшен"
                            subtitle="Стрельба, бои, адреналин"
                            accent="#ef4444"
                            bgGradient="linear-gradient(135deg, rgba(239,68,68,0.12), rgba(239,68,68,0.02))"
                            params={{ genre: 'action', sort: 'popular', maxAge, per_page: 4 }}
                            onCardClick={(p) => requireAuth(() => navigate(`/game/${p.id}`))}
                        />
                        </div>
                        <div id="sec-other">
                        <CompactGenreCard
                            title="Разное"
                            subtitle="Эксперименты и веселье"
                            accent="#14b8a6"
                            bgGradient="linear-gradient(135deg, rgba(20,184,166,0.12), rgba(20,184,166,0.02))"
                            params={{ genre: 'other', sort: 'popular', maxAge, per_page: 4 }}
                            onCardClick={(p) => requireAuth(() => navigate(`/game/${p.id}`))}
                        />
                        </div>
                    </div>

                    {/* Коллекции от админов */}
                    <div id="sec-collections">
                    <CollectionsSection
                        onCardClick={(p) => requireAuth(() => navigate(`/game/${p.id}`))}
                    />
                    </div>

                    {/* Топ-авторы недели */}
                    <div id="sec-authors">
                    <TopAuthorsSection
                        onUserClick={(uid) => navigate(`/profile/${uid}`)}
                    />
                    </div>

                    {/* Лента активности (последние рекорды) */}
                    <div id="sec-activity">
                    <ActivityFeedSection
                        onProjectClick={(pid) => requireAuth(() => navigate(`/game/${pid}`))}
                        onUserClick={(uid) => navigate(`/profile/${uid}`)}
                    />
                    </div>

                    {/* CTA-баннер «Создай свою!» */}
                    <CreateBanner onClick={() => requireAuth(() => navigate('/'))} />
                </div>
            )}

            <div style={{ maxWidth: 1240, margin: '0 auto', padding: '20px 24px 64px' }}>
                {/* Заголовок «Все игры» — отделяет нижнюю секцию ленты от категорий */}
                {!searchResults && !searchQ && profileLoaded && (
                    <div style={{
                        display: 'flex', alignItems: 'center', gap: 12,
                        margin: '24px 0 12px',
                    }}>
                        <h2 style={{
                            fontSize: 26, fontWeight: 900, color: KT.text, margin: 0,
                            letterSpacing: -0.5,
                        }}><Icon name="library" size={13} /> Все игры</h2>
                        <div style={{ flex: 1, height: 1, background: KT.borderSoft }} />
                    </div>
                )}
                {/* === Фильтры === */}
                {!searchResults && (
                    <div style={{
                        display: 'flex', gap: 10, marginBottom: 28,
                        alignItems: 'center', flexWrap: 'wrap',
                    }}>
                        <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                            {SORTS.map(s => (
                                <button
                                    key={s.id}
                                    onClick={() => setSort(s.id)}
                                    style={chipStyle(sort === s.id)}
                                >
                                    {s.icon && <Icon emoji={s.icon} size={13} style={{ marginRight: 4, verticalAlign: '-2px' }} />}
                                    {s.label}
                                </button>
                            ))}
                        </div>

                        <div style={{
                            display: 'flex', gap: 8,
                            marginLeft: 'auto', alignItems: 'center', flexWrap: 'wrap',
                        }}>
                            <FilterSelect
                                label={ageMode === 'auto' && userAge != null
                                    ? `Возраст: до ${userAgeToMaxAge(userAge)}+`
                                    : 'Возраст'}
                                value={String(ageMode)}
                                onChange={(v) => {
                                    const n = Number(v);
                                    setAgeMode(Number.isFinite(n) && v !== 'auto' && v !== 'off'
                                        ? n : v);
                                }}
                                options={AGE_MODES.map(m => ({
                                    value: String(m.id), label: m.label,
                                }))}
                            />
                            <FilterSelect
                                label={minRating > 0 ? `Рейтинг: ≥ ${minRating}%` : 'Рейтинг'}
                                value={String(minRating)}
                                onChange={(v) => setMinRating(Number(v) || 0)}
                                options={RATING_FILTERS.map(r => ({
                                    value: String(r.id), label: r.label,
                                }))}
                            />
                        </div>
                    </div>
                )}

                {searchResults && (
                    <div style={{
                        fontSize: 14, color: KT.textSecondary, marginBottom: 20,
                        marginTop: 24,
                    }}>
                        Найдено: <b style={{ color: KT.text }}>{searchResults.length}</b>
                    </div>
                )}

                {/* Section title — зависит от выбранной вкладки ленты */}
                {!searchResults && !searchQ && !loading && list.length > 0 && (
                    <h2 style={{
                        fontSize: 22, fontWeight: 800, color: KT.text,
                        margin: '0 0 6px',
                        letterSpacing: -0.3,
                        display: 'flex', alignItems: 'center', gap: 8,
                    }}>
                        {sort === 'recommended' ? 'Рекомендуем тебе'
                            : sort === 'new' ? 'Новые игры'
                            : sort === 'top_week' ? 'Топ за неделю'
                            : 'Популярное сейчас'}
                    </h2>
                )}
                {/* Подпись-объяснение под заголовком — простыми словами для детей */}
                {!searchResults && !searchQ && !loading && list.length > 0 && (
                    <p style={{
                        fontSize: 13, color: KT.textSecondary,
                        margin: '0 0 18px', lineHeight: 1.5,
                    }}>
                        {sort === 'recommended'
                            ? 'Игры, которые сейчас больше всего нравятся игрокам — лайки, просмотры и время в игре.'
                            : sort === 'new'
                            ? 'Самые свежие игры — только что опубликованы. Загляни первым!'
                            : sort === 'top_week'
                            ? 'Игры этой недели, в которые играют чаще всего.'
                            : 'Игры с наибольшим числом запусков за всё время.'}
                    </p>
                )}

                {loading ? (
                    <SkeletonGrid />
                ) : list.length === 0 ? (
                    <EmptyState searchMode={!!searchResults} />
                ) : (
                    <>
                        <div style={{
                            display: 'grid',
                            gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))',
                            gap: 18,
                        }}>
                            {list.map((p, i) => (
                                <GameCard
                                    key={p.id}
                                    project={p}
                                    rank={i}
                                    sort={sort}
                                    onClick={() => requireAuth(() => navigate(`/game/${p.id}`))}
                                    animateDelay={Math.min(i * 35, 600)}
                                />
                            ))}
                        </div>
                        {/* Sentinel + индикатор подгрузки (только в режиме ленты, не в поиске) */}
                        {!searchResults && hasMore && (
                            <div ref={sentinelRef} style={{
                                marginTop: 30, padding: 24,
                                textAlign: 'center', color: KT.textSecondary,
                                fontSize: 14,
                            }}>
                                {loadingMore ? <><Icon name="hourglass" size={13} /> Загружаю ещё...</> : ' '}
                            </div>
                        )}
                        {!searchResults && !hasMore && items.length > PER_PAGE && (
                            <div style={{
                                marginTop: 30, padding: 16,
                                textAlign: 'center', color: KT.textSecondary,
                                fontSize: 13, opacity: 0.6,
                            }}>
                                Это все игры — {items.length} {pluralRu(items.length, ['игра', 'игры', 'игр'])}
                            </div>
                        )}
                    </>
                )}
            </div>

            {askDob && (
                <BirthDateModal
                    onSave={async (dob) => {
                        const tok = localStorage.getItem('Authorization');
                        if (!tok) { setAskDob(false); return; }
                        try {
                            await axios.put(API_USER_put_birth_date,
                                { birthDate: dob },
                                { headers: { Authorization: tok }, timeout: 6000 });
                            setBirthDate(dob);
                            setAskDob(false);
                        } catch (e) { throw e; }
                    }}
                />
            )}
        </div>
    );
};


// ================================================================
// === HERO BANNER — вверху ленты, с градиентом и плавающими кубиками ===
// ================================================================
const HeroBanner = ({ totalGames, onCreate }) => {
    const { isPhone, isTablet } = useDeviceType();
    const compact = isPhone || isTablet;
    return (
    <div style={{
        position: 'relative',
        margin: compact ? '12px auto' : '20px auto',
        maxWidth: 1240, padding: compact ? '0 12px' : '0 24px',
    }}>
        <div style={{
            position: 'relative',
            background: KT.gradientHero,
            backgroundSize: '200% 200%',
            borderRadius: KT.radius2xl,
            padding: compact ? '24px 20px' : '48px 40px',
            overflow: 'hidden',
            boxShadow: '0 24px 48px rgba(51, 87, 255, 0.18)',
            animation: 'kubikonHeroSlide 600ms cubic-bezier(0.34, 1.56, 0.64, 1), '
                + 'kubikonGradientShift 12s ease-in-out infinite',
        }}>
            {/* Плавающие кубики (декор) — только на десктопе, на мобиле
                занимают место и сбивают компоновку */}
            {!compact && <FloatingShapes />}

            <div style={{
                position: 'relative', zIndex: 2,
                display: 'flex', alignItems: 'center', gap: compact ? 16 : 32,
                flexWrap: 'wrap',
            }}>
                <div style={{ flex: 1, minWidth: compact ? 0 : 280 }}>
                    <div style={{
                        display: 'inline-flex', alignItems: 'center', gap: 8,
                        background: 'rgba(255, 255, 255, 0.18)',
                        backdropFilter: 'blur(8px)',
                        border: '1px solid rgba(255, 255, 255, 0.25)',
                        padding: '6px 14px', borderRadius: 999,
                        fontSize: 12, fontWeight: 700, color: '#fff',
                        marginBottom: compact ? 10 : 16, letterSpacing: 1,
                        textTransform: 'uppercase',
                    }}>
                        <span style={{ animation: 'kubikonSpin 4s linear infinite',
                            display: 'inline-block' }}><Icon name="zap" size={14} /></span>
                        Новая платформа
                    </div>

                    <h1 style={{
                        margin: 0,
                        fontSize: compact ? 24 : 42,
                        lineHeight: 1.1,
                        fontWeight: 900, color: '#fff',
                        letterSpacing: -1,
                        textShadow: '0 2px 12px rgba(0, 0, 0, 0.12)',
                    }}>
                        Создавай игры.<br />
                        Играй с друзьями.
                    </h1>
                    <p style={{
                        marginTop: compact ? 8 : 12, marginBottom: 0,
                        fontSize: compact ? 13 : 16,
                        color: 'rgba(255, 255, 255, 0.92)',
                        maxWidth: 480, lineHeight: 1.5,
                    }}>
                        Рублокс — твоя площадка для 3D-игр.
                        Лучший опыт — в нативном приложении на ПК или телефоне.
                    </p>

                    <div style={{
                        display: 'flex',
                        gap: compact ? 8 : 12,
                        marginTop: compact ? 14 : 24,
                        flexWrap: 'wrap',
                    }}>
                        <button onClick={onCreate} style={{
                            display: 'inline-flex', alignItems: 'center', gap: 8,
                            padding: compact ? '10px 16px' : '14px 24px',
                            background: '#fff',
                            color: KT.accent,
                            border: 'none',
                            cursor: 'pointer',
                            borderRadius: KT.radius,
                            fontSize: compact ? 13 : 15, fontWeight: 800,
                            boxShadow: '0 8px 24px rgba(0, 0, 0, 0.16)',
                            letterSpacing: 0.2,
                        }}>
                            <Icon emoji="🛠️" size={15} /> Создать игру
                        </button>
                        {totalGames > 0 && (
                            <div style={{
                                display: 'inline-flex', alignItems: 'center', gap: 6,
                                padding: compact ? '10px 14px' : '14px 20px',
                                background: 'rgba(255, 255, 255, 0.15)',
                                backdropFilter: 'blur(8px)',
                                border: '1px solid rgba(255, 255, 255, 0.22)',
                                borderRadius: KT.radius,
                                color: '#fff',
                                fontSize: compact ? 12 : 14, fontWeight: 700,
                            }}><Icon name="gamepad" size={14} /><b>{totalGames}</b> {pluralRu(totalGames, ['игра', 'игры', 'игр'])} прямо сейчас
                            </div>
                        )}
                    </div>
                </div>

                {/* Большой логотип справа (плавает) — только на десктопе.
                    На мобиле скрыт, чтобы не съедал ширину. */}
                {!compact && (
                    <div style={{
                        width: 180, height: 180, flexShrink: 0,
                        animation: 'kubikonFloat 6s ease-in-out infinite',
                        filter: 'drop-shadow(0 16px 32px rgba(0, 0, 0, 0.20))',
                    }}>
                        <RublocsLogo size={180} bg="#ffffff" />
                    </div>
                )}
            </div>
        </div>
    </div>
    );
};


// ================================================================
// === DOWNLOAD APPS BANNER — две кнопки: Windows + Android ======
// ================================================================
//
// Большая карточка под hero. Сообщает что:
//   1. Браузерная версия будет отключена в будущем — переходим на приложения
//   2. Можно скачать .exe для Windows прямо сейчас
//   3. Android-версия в работе — кнопка-заглушка с уведомлением «Скоро»
//
// При клике на «Windows» — открываем прямую ссылку на инсталлер.
// При клике на «Android» — модалка «Скоро появится» с предложением
// получить уведомление на email.
// 2026-05-24: Windows-сборка отложена → кнопка показывает «Скоро».
// DOWNLOAD_FALLBACK для Windows больше не нужен, оставляем только Android.
// Когда Windows вернётся — восстановить fallback и активную кнопку.
const ANDROID_FALLBACK = {
    apk_url: '',
    version: '',
    download_size_mb: 0,
};
const DownloadAppsBanner = () => {
    const { isPhone, isTablet } = useDeviceType();
    const compact = isPhone || isTablet;
    // Состояние модалок «скоро появится» (Windows / Android / iOS).
    // 2026-05-24: Windows-сборку откладываем — кнопка тоже «Скоро». iOS добавлен.
    const [comingSoon, setComingSoon] = useState(null);  // 'windows' | 'android' | 'ios' | null
    const [android, setAndroid] = useState(ANDROID_FALLBACK);

    // Android (отдельный JSON; пустой apk_download_url → кнопка остаётся
    // в режиме «Скоро»). Windows-fetch удалён вместе с активной кнопкой.
    useEffect(() => {
        let alive = true;
        fetch(`${STORYS_addres}/kubikon3d/native/version-android`)
            .then((r) => r.ok ? r.json() : null)
            .then((d) => {
                if (!alive || !d) return;
                setAndroid({
                    apk_url: d.apk_download_url || '',
                    version: d.version || '',
                    download_size_mb: d.download_size_mb || 0,
                });
            })
            .catch(() => { /* fallback */ });
        return () => { alive = false; };
    }, []);

    const androidAvailable = !!android.apk_url;

    return (
        <div style={{
            maxWidth: 1240,
            margin: compact ? '4px auto 16px' : '12px auto 24px',
            padding: compact ? '0 12px' : '0 24px',
        }}>
            <div style={{
                background: '#fff',
                border: `1px solid ${KT.border}`,
                borderRadius: KT.radius2xl,
                padding: compact ? '20px 18px' : '32px 36px',
                boxShadow: '0 12px 32px rgba(15, 23, 42, 0.06)',
                position: 'relative',
                overflow: 'hidden',
            }}>
                {/* Декоративные акценты в углах */}
                <div style={{
                    position: 'absolute', top: -40, right: -40,
                    width: 180, height: 180, borderRadius: '50%',
                    background: `radial-gradient(circle, ${KT.accent}18 0%, transparent 70%)`,
                    pointerEvents: 'none',
                }} />
                <div style={{
                    position: 'absolute', bottom: -60, left: -60,
                    width: 220, height: 220, borderRadius: '50%',
                    background: 'radial-gradient(circle, rgba(139, 92, 246, 0.12) 0%, transparent 70%)',
                    pointerEvents: 'none',
                }} />

                <div style={{ position: 'relative', zIndex: 2 }}>
                    {/* Бейдж важности */}
                    <div style={{
                        display: 'inline-flex', alignItems: 'center', gap: 6,
                        padding: '5px 12px',
                        background: 'rgba(245, 158, 11, 0.15)',
                        border: '1px solid rgba(245, 158, 11, 0.35)',
                        color: '#92400e',
                        borderRadius: 999,
                        fontSize: 11, fontWeight: 800,
                        letterSpacing: 1, textTransform: 'uppercase',
                        marginBottom: 12,
                    }}>
                        <Icon emoji="⚡" size={13} /> Важно
                    </div>

                    <h2 style={{
                        margin: 0,
                        fontSize: compact ? 22 : 30,
                        fontWeight: 900,
                        color: KT.text,
                        letterSpacing: -0.5,
                        lineHeight: 1.15,
                    }}>
                        Приложение Рублокса — скоро
                    </h2>

                    <p style={{
                        margin: '10px 0 0',
                        fontSize: compact ? 13 : 15,
                        color: KT.textSecondary,
                        lineHeight: 1.55,
                        maxWidth: 720,
                    }}>
                        В браузере не удалось добиться нужного уровня графики, FPS и
                        стабильности — поэтому в ближайшие месяцы веб-версия игры и
                        редактор будут отключены. Полностью переходим на нативные
                        приложения. Приложения для Windows, Android и iOS — в разработке,
                        скоро появятся. Пока что играй в браузере.
                    </p>

                    {/* Три большие кнопки: Windows / Android / iOS.
                        Все три — пока «Скоро появится». Windows-сборку
                        отложили 2026-05-24 (см. задачу выше). */}
                    <div style={{
                        display: 'grid',
                        gridTemplateColumns: compact ? '1fr' : '1fr 1fr 1fr',
                        gap: compact ? 12 : 16,
                        marginTop: compact ? 18 : 24,
                    }}>
                        {/* === Windows (заглушка «Скоро») === */}
                        <ComingSoonPlatformButton
                            compact={compact}
                            iconName="monitor"
                            iconEmoji="💻"
                            iconBg="#3357ff22"
                            label="Windows"
                            sub="в разработке · скоро"
                            onClick={() => setComingSoon('windows')}
                        />

                        {/* === Android === */}
                        {androidAvailable ? (
                            // Активная кнопка — прямая загрузка APK.
                            // Имя файла с версией → браузер не сохранит старую версию из кэша.
                            <a
                                href={android.apk_url}
                                download
                                style={{
                                    display: 'flex', alignItems: 'center', gap: 16,
                                    padding: compact ? '14px 16px' : '20px 24px',
                                    background: '#10b981',
                                    color: '#fff',
                                    borderRadius: KT.radiusLg,
                                    textDecoration: 'none',
                                    boxShadow: '0 12px 28px rgba(16, 185, 129, 0.32)',
                                    transition: 'transform 200ms ease, box-shadow 200ms ease',
                                    cursor: 'pointer',
                                }}
                                onMouseEnter={(e) => {
                                    e.currentTarget.style.transform = 'translateY(-2px)';
                                    e.currentTarget.style.boxShadow = '0 18px 36px rgba(16, 185, 129, 0.42)';
                                }}
                                onMouseLeave={(e) => {
                                    e.currentTarget.style.transform = '';
                                    e.currentTarget.style.boxShadow = '0 12px 28px rgba(16, 185, 129, 0.32)';
                                }}
                            >
                                <div style={{
                                    width: compact ? 42 : 52, height: compact ? 42 : 52,
                                    background: 'rgba(255, 255, 255, 0.18)',
                                    borderRadius: 12,
                                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                                    fontSize: compact ? 22 : 28, flexShrink: 0,
                                }}><Icon name="smartphone" size={13} /> </div>
                                <div style={{ flex: 1, minWidth: 0 }}>
                                    <div style={{
                                        fontSize: 11, fontWeight: 700,
                                        opacity: 0.85, letterSpacing: 1,
                                        textTransform: 'uppercase',
                                    }}>
                                        Скачать для
                                    </div>
                                    <div style={{
                                        fontSize: compact ? 17 : 22, fontWeight: 900,
                                        marginTop: 2,
                                    }}>
                                        Android
                                    </div>
                                    <div style={{
                                        fontSize: 12, opacity: 0.8, marginTop: 2,
                                    }}>
                                        {`.apk · ${android.download_size_mb} МБ · v${android.version}`}
                                    </div>
                                </div>
                                <div style={{ fontSize: 22, opacity: 0.9 }}><Icon name="arrow-down" size={14} /></div>
                            </a>
                        ) : (
                            <ComingSoonPlatformButton
                                compact={compact}
                                iconName="smartphone"
                                iconBg="#10b98122"
                                label="Android"
                                sub="в разработке · скоро в Google Play"
                                onClick={() => setComingSoon('android')}
                            />
                        )}

                        {/* === iOS (всегда «Скоро») === */}
                        <ComingSoonPlatformButton
                            compact={compact}
                            iconName="smartphone"
                            iconEmoji="🍎"
                            iconBg="#f1f5f9"
                            label="iOS"
                            sub="iPhone / iPad · скоро в App Store"
                            onClick={() => setComingSoon('ios')}
                        />
                    </div>

                    {/* Пояснение про браузер — мелкое серое */}
                    <div style={{
                        marginTop: compact ? 14 : 18,
                        padding: compact ? '10px 12px' : '12px 16px',
                        background: '#f8fafc',
                        border: `1px solid ${KT.border}`,
                        borderRadius: KT.radius,
                        fontSize: 12,
                        color: KT.textMuted,
                        lineHeight: 1.5,
                    }}>
                        <b style={{ color: KT.text }}>Почему?</b>{' '}
                        В браузере мы упёрлись в потолок: ограничения WebGL по графике,
                        провалы FPS на сложных сценах, частые проблемы со звуком и
                        вводом. В нативном приложении всё это работает стабильно,
                        быстро и красиво — как и должно быть.
                    </div>
                </div>
            </div>

            {/* Модалка-уведомление «скоро появится» (Windows / Android / iOS) */}
            {comingSoon && (
                <PlatformComingSoonModal
                    platform={comingSoon}
                    onClose={() => setComingSoon(null)}
                />
            )}
        </div>
    );
};


/**
 * Универсальная кнопка-заглушка «Скоро для <платформы>».
 * Используется на 3 кнопках Windows / Android / iOS в DownloadAppsBanner.
 */
const ComingSoonPlatformButton = ({ compact, iconName, iconEmoji, iconBg, label, sub, onClick }) => (
    <button
        type="button"
        onClick={onClick}
        style={{
            display: 'flex', alignItems: 'center', gap: 16,
            padding: compact ? '14px 16px' : '20px 24px',
            background: '#f8fafc',
            color: KT.text,
            border: `1px dashed ${KT.borderStrong}`,
            borderRadius: KT.radiusLg,
            cursor: 'pointer',
            textAlign: 'left',
            fontFamily: 'inherit',
            transition: 'background 200ms ease, border-color 200ms ease',
        }}
        onMouseEnter={(e) => {
            e.currentTarget.style.background = '#f1f5f9';
            e.currentTarget.style.borderColor = KT.accent;
        }}
        onMouseLeave={(e) => {
            e.currentTarget.style.background = '#f8fafc';
            e.currentTarget.style.borderColor = KT.borderStrong;
        }}
    >
        <div style={{
            width: compact ? 42 : 52, height: compact ? 42 : 52,
            background: iconBg,
            borderRadius: 12,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            fontSize: compact ? 22 : 28, flexShrink: 0,
        }}>
            {iconEmoji
                ? <Icon emoji={iconEmoji} size={compact ? 22 : 28} />
                : <Icon name={iconName || 'smartphone'} size={compact ? 18 : 22} />}
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{
                fontSize: 11, fontWeight: 700,
                color: KT.textMuted, letterSpacing: 1,
                textTransform: 'uppercase',
            }}>
                Скоро для
            </div>
            <div style={{
                fontSize: compact ? 17 : 22, fontWeight: 900,
                marginTop: 2, color: KT.text,
            }}>
                {label}
            </div>
            <div style={{
                fontSize: 12, color: KT.textMuted, marginTop: 2,
            }}>
                {sub}
            </div>
        </div>
        <div style={{
            padding: '4px 10px',
            background: '#10b98122', color: '#065f46',
            borderRadius: 999,
            fontSize: 11, fontWeight: 800,
            letterSpacing: 0.5,
        }}>
            СКОРО
        </div>
    </button>
);


/** Модалка «<Платформа> скоро появится». Универсальная — параметр
 *  `platform` ('windows' | 'android' | 'ios') определяет иконку и текст.
 *  2026-05-24: Windows-сборка тоже отложена → теперь все 3 варианта
 *  показывают одну и ту же дружелюбную модалку. */
const PLATFORM_LABELS = {
    windows: {
        title: 'Windows-версия скоро!',
        text: 'Работаем над сборкой для ПК. Скоро будет — следи за обновлениями. '
            + 'Пока что играй через браузер.',
        icon: 'monitor',
    },
    android: {
        title: 'Android-версия скоро!',
        text: 'Делаем приложение для Android — выйдет в ближайшие недели. '
            + 'Пока что играй в браузере.',
        icon: 'smartphone',
    },
    ios: {
        title: 'iOS-версия скоро!',
        text: 'Приложение для iPhone и iPad в разработке — появится позже. '
            + 'Пока что играй в браузере.',
        icon: 'smartphone',
    },
};

const PlatformComingSoonModal = ({ platform, onClose }) => {
    const info = PLATFORM_LABELS[platform] || PLATFORM_LABELS.android;
    return (
        <div
            onClick={onClose}
            style={{
                position: 'fixed', inset: 0, zIndex: 9999,
                background: 'rgba(15, 23, 42, 0.55)',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                padding: 16,
                animation: 'kubikonFadeIn 200ms ease',
            }}
        >
            <div
                onClick={(e) => e.stopPropagation()}
                style={{
                    background: '#fff',
                    borderRadius: KT.radius2xl,
                    padding: '32px 28px',
                    maxWidth: 440, width: '100%',
                    boxShadow: '0 24px 56px rgba(15, 23, 42, 0.32)',
                    textAlign: 'center',
                    position: 'relative',
                }}
            >
                <button
                    onClick={onClose}
                    aria-label="Закрыть"
                    style={{
                        position: 'absolute', top: 14, right: 14,
                        background: 'transparent', border: 'none', cursor: 'pointer',
                        fontSize: 24, color: KT.textMuted, lineHeight: 1,
                    }}
                >×</button>

                <div style={{
                    width: 64, height: 64, margin: '0 auto 8px',
                    borderRadius: '50%', background: '#f1f5f9',
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                }}><Icon name={info.icon} size={28} /></div>

                <h3 style={{
                    margin: '8px 0 6px',
                    fontSize: 22, fontWeight: 900, color: KT.text,
                }}>
                    {info.title}
                </h3>

                <p style={{
                    margin: '0 0 20px',
                    fontSize: 14, color: KT.textSecondary,
                    lineHeight: 1.55,
                }}>
                    {info.text}
                </p>

                <button
                    onClick={onClose}
                    style={{
                        ...buttonPrimary,
                        padding: '12px 28px',
                        fontSize: 14,
                    }}
                >
                    Ок, понял!
                </button>
            </div>
        </div>
    );
};


/** Плавающие декоративные shapes на фоне hero. */
const FloatingShapes = () => (
    <>
        <div style={{
            position: 'absolute', top: '15%', left: '8%',
            width: 80, height: 80,
            background: 'rgba(255, 255, 255, 0.12)',
            borderRadius: 16,
            transform: 'rotate(15deg)',
            animation: 'kubikonFloat 8s ease-in-out infinite',
        }} />
        <div style={{
            position: 'absolute', top: '60%', left: '4%',
            width: 56, height: 56,
            background: 'rgba(255, 255, 255, 0.10)',
            borderRadius: 14,
            transform: 'rotate(-10deg)',
            animation: 'kubikonFloat 10s ease-in-out 1s infinite',
        }} />
        <div style={{
            position: 'absolute', top: '25%', right: '32%',
            width: 40, height: 40,
            background: 'rgba(255, 255, 255, 0.14)',
            borderRadius: 10,
            transform: 'rotate(28deg)',
            animation: 'kubikonFloat 7s ease-in-out 2s infinite',
        }} />
        <div style={{
            position: 'absolute', bottom: '12%', right: '18%',
            width: 64, height: 64,
            background: 'rgba(255, 255, 255, 0.10)',
            borderRadius: 16,
            transform: 'rotate(-18deg)',
            animation: 'kubikonFloat 9s ease-in-out 0.5s infinite',
        }} />
    </>
);


// ================================================================
// === GAME CARD — с 3D-tilt при hover, badge HOT для топов ===
// ================================================================
const GameCard = ({ project, rank, sort, onClick, animateDelay = 0,
                     showFavorite = true }) => {
    const [hovered, setHovered] = useState(false);
    const [tilt, setTilt] = useState({ x: 0, y: 0 });
    const [favorited, setFavorited] = useState(false);
    const ref = useRef(null);
    const p = project;

    const isHot = sort === 'top_week' && rank < 3;
    // Бейдж NEW — умная лента: игра на испытательном окне (quality_state='new').
    // Fallback на дату публикации, если бэкенд старый и поле не пришло.
    const isNew = (() => {
        if (p.quality_state) return p.quality_state === 'new';
        const src = p.published_at || p.created_at;
        if (!src) return false;
        const t = new Date(src).getTime();
        if (!t) return false;
        return (Date.now() - t) < 7 * 24 * 3600 * 1000;
    })();
    const userIdMemo = (() => {
        try {
            const t = localStorage.getItem('Authorization');
            if (!t) return null;
            const pl = jwtDecode(t);
            return pl?.id || pl?.user_id || null;
        } catch { return null; }
    })();

    // Загружаем статус избранного один раз
    useEffect(() => {
        if (!showFavorite || !userIdMemo || !p.id) return;
        let active = true;
        Kubikon3DApi.getFavoriteStatus(p.id, userIdMemo)
            .then(r => { if (active) setFavorited(!!r.data?.favorited); })
            .catch(() => {});
        return () => { active = false; };
    }, [p.id, userIdMemo, showFavorite]);

    const handleFav = (e) => {
        e.stopPropagation();
        e.preventDefault();
        if (!userIdMemo || !p.id) return;
        // Оптимистично
        setFavorited(prev => !prev);
        Kubikon3DApi.toggleFavorite(p.id, userIdMemo)
            .then(r => setFavorited(!!r.data?.favorited))
            .catch(() => setFavorited(prev => !prev));
    };

    const onMouseMove = (e) => {
        if (!ref.current) return;
        const rect = ref.current.getBoundingClientRect();
        const x = (e.clientX - rect.left) / rect.width - 0.5;
        const y = (e.clientY - rect.top) / rect.height - 0.5;
        // Лёгкий tilt — максимум 6° по осям
        setTilt({ x: y * -6, y: x * 6 });
    };

    const onMouseLeave = () => {
        setHovered(false);
        setTilt({ x: 0, y: 0 });
    };

    return (
        <div
            ref={ref}
            role="button"
            tabIndex={0}
            onClick={onClick}
            onKeyDown={(e) => { if (e.key === 'Enter') onClick(); }}
            onMouseEnter={() => setHovered(true)}
            onMouseMove={onMouseMove}
            onMouseLeave={onMouseLeave}
            style={{
                background: KT.bgPage,
                border: `1px solid ${hovered ? KT.accent : KT.border}`,
                borderRadius: KT.radiusLg,
                boxShadow: hovered ? KT.shadowLg : KT.shadow,
                transform: hovered
                    ? `perspective(800px) rotateX(${tilt.x}deg) rotateY(${tilt.y}deg) translateY(-6px)`
                    : 'perspective(800px) rotateX(0) rotateY(0) translateY(0)',
                transition: hovered
                    ? 'transform 100ms ease, box-shadow 280ms ease, border-color 280ms ease'
                    : 'all 300ms cubic-bezier(0.34, 1.56, 0.64, 1)',
                cursor: 'pointer',
                overflow: 'hidden',
                animation: `kubikonFadeIn 460ms cubic-bezier(0.34, 1.56, 0.64, 1) ${animateDelay}ms both`,
                position: 'relative',
                willChange: 'transform',
            }}
        >
            {/* Thumbnail */}
            <div style={{
                aspectRatio: '4/3',
                background: '#e2e8f0',
                position: 'relative', overflow: 'hidden',
            }}>
                {p.thumbnail ? (
                    <img
                        src={p.thumbnail}
                        alt={p.title}
                        style={{
                            width: '100%', height: '100%',
                            objectFit: 'cover',
                            transform: hovered ? 'scale(1.10)' : 'scale(1)',
                            transition: 'transform 700ms cubic-bezier(0.2, 0.8, 0.4, 1)',
                        }}
                    />
                ) : (
                    <div style={{
                        position: 'absolute', inset: 0,
                        display: 'flex', alignItems: 'center', justifyContent: 'center',
                        fontSize: 64, color: KT.textMuted,
                    }}><Icon name="gamepad" size={14} /></div>
                )}

                {/* Gradient overlay на hover */}
                <div style={{
                    position: 'absolute', inset: 0,
                    background: 'linear-gradient(180deg, transparent 50%, rgba(15, 23, 42, 0.6) 100%)',
                    opacity: hovered ? 1 : 0.7,
                    transition: 'opacity 300ms ease',
                    pointerEvents: 'none',
                }} />

                {/* HOT badge */}
                {isHot && (
                    <div style={{
                        position: 'absolute', top: 10, left: 10,
                        background: KT.gradientHot,
                        color: '#fff',
                        padding: '4px 10px',
                        borderRadius: 999,
                        fontSize: 11, fontWeight: 800,
                        letterSpacing: 0.5,
                        boxShadow: '0 4px 12px rgba(239, 68, 68, 0.40)',
                        animation: 'kubikonHotPulse 1.4s ease-in-out infinite',
                    }}>
                        <Icon name="flame" size={13} /> HOT #{rank + 1}
                    </div>
                )}

                {/* NEW badge — сверху-слева. Бейдж STAFF PICK убран —
                    рангов в умной ленте больше нет. */}
                <div style={{
                    position: 'absolute', top: 10, left: 10,
                    display: 'flex', flexDirection: 'column', gap: 4,
                    zIndex: 2,
                }}>
                    {isNew && (
                        <span style={{
                            background: 'linear-gradient(135deg, #22d97a, #14b8a6)',
                            color: '#fff', fontSize: 10, padding: '3px 8px',
                            borderRadius: 999, fontWeight: 900, letterSpacing: 0.5,
                            boxShadow: '0 4px 12px rgba(34, 217, 122, 0.4)',
                            animation: 'kubikonGlow 2s ease-in-out infinite',
                        }}><Icon name="sparkles" size={13} /> NEW</span>
                    )}
                </div>

                {/* Кнопка избранного — сверху справа, рядом с age badge.
                    Используем SVG-иконку чтобы сердечко было симметричным
                    (эмоджи 🤍/❤️ кривые в большинстве шрифтов). */}
                {showFavorite && userIdMemo && (
                    <button
                        onClick={handleFav}
                        title={favorited ? 'Убрать из избранного' : 'В избранное'}
                        style={{
                            position: 'absolute', top: 8, right: 56,
                            width: 32, height: 32, borderRadius: '50%',
                            background: favorited
                                ? 'linear-gradient(135deg, #ef4444, #ec4899)'
                                : 'rgba(15, 23, 42, 0.65)',
                            border: 'none',
                            color: '#fff',
                            cursor: 'pointer',
                            zIndex: 3,
                            display: 'flex', alignItems: 'center', justifyContent: 'center',
                            padding: 0,
                            boxShadow: favorited
                                ? '0 4px 14px rgba(239, 68, 68, 0.55)'
                                : '0 2px 6px rgba(0,0,0,0.25)',
                            backdropFilter: 'blur(8px)',
                            transition: 'all 200ms cubic-bezier(0.34, 1.56, 0.64, 1)',
                        }}
                        onMouseEnter={(e) => {
                            e.currentTarget.style.transform = 'scale(1.18)';
                        }}
                        onMouseLeave={(e) => {
                            e.currentTarget.style.transform = 'scale(1)';
                        }}
                    >
                        <svg
                            width="16" height="16" viewBox="0 0 24 24"
                            fill={favorited ? 'currentColor' : 'none'}
                            stroke="currentColor"
                            strokeWidth={favorited ? 0 : 2.2}
                            strokeLinecap="round"
                            strokeLinejoin="round"
                        >
                            <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>
                        </svg>
                    </button>
                )}

                {/* Age badge */}
                <span style={{
                    position: 'absolute', top: 10, right: 10,
                    background: 'rgba(15, 23, 42, 0.85)',
                    color: '#fff',
                    fontSize: 11, padding: '3px 9px',
                    borderRadius: 999, fontWeight: 700,
                    backdropFilter: 'blur(8px)',
                }}>{p.age_rating || 12}+</span>

                {/* Stats overlay снизу */}
                <div style={{
                    position: 'absolute', bottom: 10, left: 10, right: 10,
                    display: 'flex', alignItems: 'center', gap: 10,
                    fontSize: 12, fontWeight: 700,
                    pointerEvents: 'none',
                }}>
                    <span style={{
                        display: 'inline-flex', alignItems: 'center', gap: 4,
                        background: 'rgba(255, 255, 255, 0.92)',
                        color: ratingColor(p.rating_percent),
                        padding: '3px 9px', borderRadius: 999,
                        backdropFilter: 'blur(8px)',
                    }}>
                        <Icon name="thumbs-up" size={13} /> {p.rating_percent != null ? `${p.rating_percent}%` : '—'}
                    </span>
                    <span style={{
                        display: 'inline-flex', alignItems: 'center', gap: 4,
                        background: 'rgba(255, 255, 255, 0.92)',
                        color: KT.text,
                        padding: '3px 9px', borderRadius: 999,
                        backdropFilter: 'blur(8px)',
                    }}>
                        <Icon name="gamepad" size={13} /> {(p.play_count || 0).toLocaleString('ru')}
                    </span>
                </div>
            </div>

            {/* Title block */}
            <div style={{ padding: '12px 14px' }}>
                <div style={{
                    fontSize: 15, fontWeight: 800,
                    whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
                    color: KT.text,
                    letterSpacing: -0.2,
                }}>
                    {p.title}
                </div>
                <div style={{
                    fontSize: 12, color: KT.textMuted,
                    marginTop: 2, fontWeight: 600,
                    whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
                }}>
                    <Icon name="user" size={13} /> {p.author_username || `#${p.user_id}`}
                </div>
            </div>
        </div>
    );
};

function ratingColor(pct) {
    if (pct == null) return KT.textMuted;
    if (pct >= 85) return KT.success;
    if (pct >= 60) return KT.warning;
    return KT.danger;
}


// ================================================================
// === Search input ===
// ================================================================
const SearchInput = ({ value, onChange }) => {
    const [focus, setFocus] = useState(false);
    return (
        <div style={{
            flex: 1, minWidth: 200, maxWidth: 460,
            position: 'relative',
        }}>
            <span style={{
                position: 'absolute', left: 14, top: '50%',
                transform: 'translateY(-50%)',
                fontSize: 16, color: focus ? KT.accent : KT.textMuted,
                pointerEvents: 'none',
                transition: 'color 200ms ease',
            }}><Icon name="search" size={14} /></span>
            <input
                type="text"
                value={value}
                onChange={(e) => onChange(e.target.value)}
                placeholder="Найти игру…"
                onFocus={() => setFocus(true)}
                onBlur={() => setFocus(false)}
                style={{
                    width: '100%', boxSizing: 'border-box',
                    background: focus ? KT.bgPage : KT.bgMuted,
                    border: `1px solid ${focus ? KT.accent : 'transparent'}`,
                    borderRadius: 999,
                    padding: '10px 16px 10px 40px',
                    color: KT.text, fontSize: 14,
                    fontFamily: KT.font, outline: 'none',
                    transition: 'all 200ms ease',
                    boxShadow: focus ? KT.shadowGlow : 'none',
                }}
            />
        </div>
    );
};


// ================================================================
// === FilterSelect (полностью кастомный dropdown) ===
// ================================================================
const FilterSelect = ({ label, value, onChange, options }) => {
    const [open, setOpen] = useState(false);
    const [hovered, setHovered] = useState(false);
    const wrapRef = useRef(null);

    // Закрытие при клике вне
    useEffect(() => {
        if (!open) return;
        const onDocClick = (e) => {
            if (!wrapRef.current?.contains(e.target)) setOpen(false);
        };
        const onKey = (e) => { if (e.key === 'Escape') setOpen(false); };
        document.addEventListener('mousedown', onDocClick);
        document.addEventListener('keydown', onKey);
        return () => {
            document.removeEventListener('mousedown', onDocClick);
            document.removeEventListener('keydown', onKey);
        };
    }, [open]);

    return (
        <div
            ref={wrapRef}
            style={{ position: 'relative', display: 'inline-block' }}
        >
            {/* Trigger pill */}
            <button
                type="button"
                onClick={() => setOpen(o => !o)}
                onMouseEnter={() => setHovered(true)}
                onMouseLeave={() => setHovered(false)}
                style={{
                    display: 'inline-flex', alignItems: 'center', gap: 8,
                    padding: '8px 14px',
                    background: open ? KT.bgPage : (hovered ? KT.bgPage : KT.bgPage),
                    border: `1px solid ${open || hovered ? KT.accent : KT.border}`,
                    borderRadius: 999,
                    cursor: 'pointer',
                    fontFamily: KT.font,
                    transition: 'all 200ms ease',
                    boxShadow: open
                        ? KT.shadowGlow
                        : (hovered ? KT.shadowSm : 'none'),
                }}
            >
                <span style={{ fontSize: 13, fontWeight: 700, color: KT.text }}>
                    {label}
                </span>
                <span style={{
                    fontSize: 10, color: KT.textMuted,
                    transform: open ? 'rotate(180deg)' : 'rotate(0)',
                    transition: 'transform 200ms ease',
                    display: 'inline-block',
                }}>▾</span>
            </button>

            {/* Dropdown menu */}
            {open && (
                <div
                    role="listbox"
                    style={{
                        position: 'absolute',
                        top: 'calc(100% + 8px)',
                        right: 0,
                        minWidth: 220,
                        background: KT.glassDark,
                        backdropFilter: 'blur(20px)',
                        WebkitBackdropFilter: 'blur(20px)',
                        border: `1px solid ${KT.border}`,
                        borderRadius: KT.radiusLg,
                        boxShadow: KT.shadowLg,
                        padding: 6,
                        zIndex: 100,
                        animation: 'kubikonFadeInScale 180ms cubic-bezier(0.34, 1.56, 0.64, 1)',
                        transformOrigin: 'top right',
                    }}
                >
                    {options.map(o => (
                        <FilterOption
                            key={o.value}
                            option={o}
                            selected={String(o.value) === String(value)}
                            onClick={() => {
                                onChange(o.value);
                                setOpen(false);
                            }}
                        />
                    ))}
                </div>
            )}
        </div>
    );
};

const FilterOption = ({ option, selected, onClick }) => {
    const [hovered, setHovered] = useState(false);
    return (
        <button
            type="button"
            role="option"
            aria-selected={selected}
            onClick={onClick}
            onMouseEnter={() => setHovered(true)}
            onMouseLeave={() => setHovered(false)}
            style={{
                display: 'flex', alignItems: 'center', gap: 10,
                width: '100%', boxSizing: 'border-box',
                padding: '10px 12px',
                background: selected
                    ? KT.gradientBrand
                    : (hovered ? KT.bgHover : 'transparent'),
                color: selected ? '#fff' : KT.text,
                border: 'none',
                borderRadius: KT.radius,
                cursor: 'pointer',
                fontSize: 14, fontWeight: selected ? 700 : 600,
                fontFamily: KT.font,
                textAlign: 'left',
                transition: 'all 150ms ease',
                boxShadow: selected ? '0 4px 12px rgba(51,87,255,0.28)' : 'none',
            }}
        >
            <span style={{ flex: 1 }}>{option.label}</span>
            {selected && (
                <span style={{ fontSize: 14, fontWeight: 800 }}><Icon name="check" size={14} /></span>
            )}
        </button>
    );
};


// ================================================================
// === Skeleton grid ===
// ================================================================
const SkeletonGrid = () => (
    <div style={{
        display: 'grid',
        gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))',
        gap: 18,
    }}>
        {Array.from({ length: 8 }).map((_, i) => (
            <div key={i} style={{
                background: KT.bgPage,
                border: `1px solid ${KT.border}`,
                borderRadius: KT.radiusLg,
                overflow: 'hidden',
                boxShadow: KT.shadow,
                animation: `kubikonFadeIn 400ms ease ${i * 60}ms both`,
            }}>
                <div style={skeletonStyle({
                    aspectRatio: '4/3', borderRadius: 0,
                })} />
                <div style={{ padding: '12px 14px' }}>
                    <div style={skeletonStyle({ height: 16, width: '70%', marginBottom: 8 })} />
                    <div style={skeletonStyle({ height: 12, width: '40%' })} />
                </div>
            </div>
        ))}
    </div>
);


// ================================================================
// === Empty state ===
// ================================================================
const EmptyState = ({ searchMode }) => (
    <div style={{
        padding: 80, textAlign: 'center',
        background: KT.bgPage,
        border: `1px solid ${KT.border}`,
        borderRadius: KT.radiusLg,
        color: KT.textSecondary,
        animation: 'kubikonFadeInScale 400ms cubic-bezier(0.34, 1.56, 0.64, 1)',
    }}>
        <div style={{
            marginBottom: 20,
            animation: 'kubikonFloat 4s ease-in-out infinite',
            display: 'inline-block',
        }}>
            {searchMode
                ? <Icon emoji="🔍" size={80} />
                : <Icon emoji="🌱" size={80} />}
        </div>
        <div style={{
            fontSize: 22, fontWeight: 800, color: KT.text, marginBottom: 8,
            letterSpacing: -0.3,
        }}>
            {searchMode ? 'Ничего не найдено' : 'Лента пока пуста'}
        </div>
        <div style={{ fontSize: 14, maxWidth: 420, margin: '0 auto' }}>
            {searchMode
                ? 'Попробуй другой запрос или сними фильтры'
                : 'Здесь скоро появятся первые игры от участников Рублокса'}
        </div>
    </div>
);


function pluralRu(n, forms) {
    const m10 = n % 10, m100 = n % 100;
    if (m10 === 1 && m100 !== 11) return forms[0];
    if (m10 >= 2 && m10 <= 4 && (m100 < 12 || m100 > 14)) return forms[1];
    return forms[2];
}


// ================================================================
// === BirthDate Modal ===
// ================================================================
const MONTHS_RU = [
    'января', 'февраля', 'марта', 'апреля', 'мая', 'июня',
    'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря',
];
const CURRENT_YEAR = new Date().getFullYear();

function daysInMonth(month, year) {
    if (!month || !year) return 31;
    return new Date(Number(year), Number(month), 0).getDate();
}

function ageDescription(age) {
    if (age == null) return null;
    const max = userAgeToMaxAge(age);
    if (age < 6)  return { emoji: '👶', label: 'Малыш',     text: 'Доступны только самые мирные игры (6+)' };
    if (age < 12) return { emoji: '🧒', label: 'Ребёнок',   text: 'Будем показывать игры до 6+' };
    if (age < 16) return { emoji: '👦', label: 'Школьник',  text: `Будем показывать игры до ${max}+` };
    if (age < 18) return { emoji: '🧑', label: 'Подросток', text: 'Будем показывать игры до 16+' };
    return { emoji: '🧓', label: 'Взрослый', text: 'Доступен любой контент, включая 18+' };
}

const BirthDateModal = ({ onSave }) => {
    const [day,   setDay]   = useState('');
    const [month, setMonth] = useState('');
    const [year,  setYear]  = useState('');
    const [submitting, setSubmitting] = useState(false);
    const [error, setError] = useState(null);
    const [hovered, setHovered] = useState(false);

    const dValid = day && month && year
        && Number(day) <= daysInMonth(month, year);
    const composedISO = dValid
        ? `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
        : '';
    const previewAge = composedISO ? ageFromBirthDate(composedISO) : null;
    const description = ageDescription(previewAge);
    const canSubmit = composedISO && !submitting;

    const submit = async () => {
        if (!canSubmit) return;
        setSubmitting(true);
        setError(null);
        try {
            await onSave(composedISO);
        } catch (e) {
            const msg = e?.response?.data?.message || e?.message || 'Ошибка';
            setError(msg);
            setSubmitting(false);
        }
    };

    return (
        <div style={{
            position: 'fixed', inset: 0, zIndex: 3000,
            background: 'rgba(15, 23, 42, 0.55)', backdropFilter: 'blur(8px)',
            display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20,
            animation: 'kubikonFadeIn 200ms ease',
        }}>
            <style>{KUBIKON_KEYFRAMES}</style>
            <div style={{
                background: KT.bgPage,
                border: `1px solid ${KT.border}`,
                borderRadius: KT.radius2xl,
                padding: 0,
                maxWidth: 480, width: '100%',
                color: KT.text,
                fontFamily: KT.font,
                maxHeight: '92vh', overflowY: 'auto',
                boxShadow: KT.shadowXl,
                animation: 'kubikonFadeInScale 320ms cubic-bezier(0.34, 1.56, 0.64, 1)',
            }}>
                <div style={{
                    background: KT.gradientBrand,
                    backgroundSize: '200% 200%',
                    animation: 'kubikonGradientShift 12s ease-in-out infinite',
                    padding: '28px 24px',
                    borderRadius: `${KT.radius2xl}px ${KT.radius2xl}px 0 0`,
                    textAlign: 'center',
                    position: 'relative',
                    overflow: 'hidden',
                }}>
                    <div style={{
                        marginBottom: 4, color: '#fff',
                        filter: 'drop-shadow(0 4px 12px rgba(0,0,0,0.20))',
                        animation: 'kubikonFloat 5s ease-in-out infinite',
                    }}><Icon emoji="🎂" size={56} /></div>
                    <h2 style={{
                        margin: 0, fontSize: 24, fontWeight: 900, color: '#fff',
                        letterSpacing: -0.5,
                    }}>
                        Расскажи свой возраст
                    </h2>
                    <div style={{
                        fontSize: 13, color: 'rgba(255,255,255,0.92)',
                        marginTop: 6,
                    }}>
                        Покажем игры, подходящие именно тебе
                    </div>
                </div>

                <div style={{ padding: '24px' }}>
                    <div style={{ display: 'flex', gap: 8, marginBottom: 16 }}>
                        <DobSelect label="День" value={day} onChange={setDay}
                            options={Array.from(
                                { length: daysInMonth(month, year) },
                                (_, i) => ({ value: String(i + 1), label: String(i + 1) })
                            )} flex={1} />
                        <DobSelect label="Месяц" value={month} onChange={setMonth}
                            options={MONTHS_RU.map((m, i) => ({
                                value: String(i + 1), label: m,
                            }))} flex={1.6} />
                        <DobSelect label="Год" value={year} onChange={setYear}
                            options={Array.from(
                                { length: CURRENT_YEAR - 1920 + 1 },
                                (_, i) => {
                                    const y = CURRENT_YEAR - i;
                                    return { value: String(y), label: String(y) };
                                }
                            )} flex={1} />
                    </div>

                    {description ? (
                        <div style={{
                            background: KT.accentSoft,
                            border: `1px solid ${KT.accent}`,
                            borderRadius: KT.radiusLg,
                            padding: 16, marginBottom: 16,
                            display: 'flex', alignItems: 'center', gap: 14,
                            animation: 'kubikonFadeInScale 240ms cubic-bezier(0.34, 1.56, 0.64, 1)',
                        }}>
                            <div><Icon emoji={description.emoji} size={36} /></div>
                            <div style={{ flex: 1 }}>
                                <div style={{
                                    fontSize: 11, color: KT.accent,
                                    textTransform: 'uppercase', letterSpacing: 1, fontWeight: 700,
                                }}>
                                    {description.label} · {previewAge}{' '}
                                    {previewAge === 1 ? 'год'
                                        : (previewAge >= 2 && previewAge <= 4) ? 'года' : 'лет'}
                                </div>
                                <div style={{ fontSize: 13, color: KT.text, marginTop: 2 }}>
                                    {description.text}
                                </div>
                            </div>
                        </div>
                    ) : (
                        <div style={{
                            background: KT.bgMuted,
                            border: `1px dashed ${KT.borderStrong}`,
                            borderRadius: KT.radiusLg,
                            padding: 16, marginBottom: 16,
                            textAlign: 'center',
                            fontSize: 13, color: KT.textSecondary,
                        }}>
                            Выбери день, месяц и год — мы покажем твой уровень
                        </div>
                    )}

                    {error && (
                        <div style={{
                            background: KT.dangerLight,
                            border: `1px solid ${KT.danger}`,
                            borderRadius: KT.radius,
                            padding: '8px 12px', marginBottom: 14,
                            fontSize: 12, color: KT.danger,
                        }}>
                            <Icon name="warning" size={13} /> {error}
                        </div>
                    )}

                    <div style={{
                        fontSize: 11, color: KT.textMuted,
                        marginBottom: 16, lineHeight: 1.5,
                    }}>
                        Дата рождения хранится только для возрастной фильтрации.
                        Обработка регулируется{' '}
                        <Link to="/privacy-policy" target="_blank" style={{
                            color: KT.accent, textDecoration: 'underline',
                        }}>
                            Политикой обработки ПД
                        </Link>, на которую ты дал согласие при регистрации.
                    </div>

                    <button
                        onClick={submit}
                        disabled={!canSubmit}
                        onMouseEnter={() => setHovered(true)}
                        onMouseLeave={() => setHovered(false)}
                        style={{
                            ...buttonPrimary(hovered && canSubmit),
                            width: '100%', padding: '14px 24px', fontSize: 15,
                            opacity: canSubmit ? 1 : 0.5,
                            cursor: canSubmit ? 'pointer' : 'not-allowed',
                            animation: canSubmit
                                ? 'kubikonPulseGlow 2.4s ease-in-out infinite'
                                : 'none',
                        }}
                    >
                        {submitting ? 'Сохранение…' : <><Icon name="rocket" size={13} /> Поехали!</>}
                    </button>
                </div>
            </div>
        </div>
    );
};

const DobSelect = ({ label, value, onChange, options, flex }) => (
    <div style={{ flex, minWidth: 0 }}>
        <div style={{
            fontSize: 11, color: KT.textSecondary, marginBottom: 4,
            letterSpacing: 0.5, fontWeight: 600,
        }}>{label}</div>
        <select
            value={value}
            onChange={(e) => onChange(e.target.value)}
            style={{
                width: '100%',
                background: KT.bgPage,
                color: value ? KT.text : KT.textMuted,
                border: `1px solid ${value ? KT.accent : KT.border}`,
                borderRadius: KT.radius,
                padding: '10px 8px',
                fontSize: 14, fontWeight: 600,
                fontFamily: KT.font,
                cursor: 'pointer',
                outline: 'none',
                transition: 'border-color 150ms ease',
            }}
        >
            <option value="">—</option>
            {options.map(o => (
                <option key={o.value} value={o.value}>{o.label}</option>
            ))}
        </select>
    </div>
);

/**
 * CategorySection — тематический блок на главной ленте Рублокса.
 * Загружает свой набор проектов через getFeed с заданными params и
 * рендерит их в одном из 4 layout'ов:
 *   carousel  — горизонтальная прокрутка
 *   mosaic    — 1 крупная + 4 маленьких (буквой Г)
 *   spotlight — 3 крупные карточки в ряд
 *   grid      — компактная сетка 4×2
 */
const CategorySection = ({
    layout, title, subtitle, accent, emoji, hideHeader, params, onCardClick,
}) => {
    const [items, setItems] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        let active = true;
        setLoading(true);
        Kubikon3DApi.getFeed(
            1,
            params.sort || 'popular',
            params.maxAge != null ? params.maxAge : null,
            null,
            {
                rank: params.rank,
                multiplayer: params.multiplayer,
                genre: params.genre,
                per_page: params.per_page || 10,
            },
        )
        .then(r => {
            if (!active) return;
            setItems(r.data?.projects || []);
        })
        .catch(() => { if (active) setItems([]); })
        .finally(() => { if (active) setLoading(false); });
        return () => { active = false; };
    }, [params.rank, params.multiplayer, params.genre, params.sort,
        params.maxAge, params.per_page]);

    if (loading) {
        return (
            <div style={{ marginTop: 24 }}>
                {!hideHeader && <CategoryHeader title={title} subtitle={subtitle} accent={accent} emoji={emoji} />}
                <div style={{
                    display: 'grid',
                    gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))',
                    gap: 14,
                }}>
                    {[1, 2, 3, 4].map(i => (
                        <div key={i} style={{
                            ...skeletonStyle,
                            aspectRatio: '4/3',
                            borderRadius: KT.radiusLg,
                        }} />
                    ))}
                </div>
            </div>
        );
    }
    if (!items.length) return null;

    if (layout === 'carousel') {
        return (
            <div style={{ marginTop: 28 }}>
                {!hideHeader && <CategoryHeader title={title} subtitle={subtitle} accent={accent} emoji={emoji} />}
                <CarouselScroll accent={accent}>
                    {items.map((p, i) => (
                        <div key={p.id} style={{
                            flex: '0 0 260px',
                            minWidth: 260,
                            scrollSnapAlign: 'start',
                        }}>
                            <GameCard project={p} rank={i} sort="popular"
                                      onClick={() => onCardClick(p)}
                                      animateDelay={Math.min(i * 30, 300)} />
                        </div>
                    ))}
                </CarouselScroll>
            </div>
        );
    }

    if (layout === 'mosaic') {
        // 1 крупная слева + 4 маленьких в сетке 2×2 справа
        const big = items[0];
        const smalls = items.slice(1, 5);
        return (
            <div style={{ marginTop: 28 }}>
                {!hideHeader && <CategoryHeader title={title} subtitle={subtitle} accent={accent} emoji={emoji} />}
                <div style={{
                    display: 'grid',
                    gridTemplateColumns: '2fr 1fr 1fr',
                    gap: 14,
                    minHeight: 380,
                }}>
                    <div style={{ gridRow: 'span 2', minHeight: 380 }}>
                        <GameCard project={big} rank={0} sort="popular"
                                  onClick={() => onCardClick(big)} />
                    </div>
                    {smalls.map((p, i) => (
                        <GameCard key={p.id} project={p} rank={i + 1} sort="popular"
                                  onClick={() => onCardClick(p)}
                                  animateDelay={Math.min((i + 1) * 30, 200)} />
                    ))}
                </div>
            </div>
        );
    }

    if (layout === 'spotlight') {
        // 3 крупные карточки в ряд
        const top3 = items.slice(0, 3);
        return (
            <div style={{ marginTop: 28 }}>
                {!hideHeader && <CategoryHeader title={title} subtitle={subtitle} accent={accent} emoji={emoji} />}
                <div style={{
                    display: 'grid',
                    gridTemplateColumns: 'repeat(3, 1fr)',
                    gap: 18,
                }}>
                    {top3.map((p, i) => (
                        <GameCard key={p.id} project={p} rank={i} sort="popular"
                                  onClick={() => onCardClick(p)}
                                  animateDelay={Math.min(i * 50, 150)} />
                    ))}
                </div>
            </div>
        );
    }

    // grid (default)
    return (
        <div style={{ marginTop: 28 }}>
            {!hideHeader && <CategoryHeader title={title} subtitle={subtitle} accent={accent} emoji={emoji} />}
            <div style={{
                display: 'grid',
                gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))',
                gap: 14,
            }}>
                {items.slice(0, 8).map((p, i) => (
                    <GameCard key={p.id} project={p} rank={i} sort="popular"
                              onClick={() => onCardClick(p)}
                              animateDelay={Math.min(i * 25, 200)} />
                ))}
            </div>
        </div>
    );
};


/**
 * Заголовок категории — крупный, с эмоджи-бейджем, градиент-текстом
 * и анимированной разделительной линией.
 */
const CategoryHeader = ({ title, subtitle, accent, emoji }) => (
    <div style={{
        display: 'flex', alignItems: 'center', gap: 16,
        margin: '0 0 18px',
        position: 'relative',
        animation: 'kubikonFadeIn 500ms cubic-bezier(0.34, 1.56, 0.64, 1)',
    }}>
        {emoji && (
            <div style={{
                flexShrink: 0,
                width: 56, height: 56,
                borderRadius: 16,
                background: `linear-gradient(135deg, ${accent}, ${accent}aa)`,
                boxShadow: `0 8px 22px ${accent}55`,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                color: '#fff',
                animation: 'kubikonFloatY 3s ease-in-out infinite',
            }}>
                <Icon emoji={emoji} size={28} strokeWidth={2} />
            </div>
        )}
        <div style={{ flex: 1 }}>
            <h2 style={{
                margin: 0,
                fontSize: 26, fontWeight: 900,
                color: KT.text,
                letterSpacing: -0.5,
                lineHeight: 1.1,
            }}>{title}</h2>
            {subtitle && (
                <div style={{
                    marginTop: 4,
                    fontSize: 13, color: KT.textSecondary,
                    fontWeight: 600,
                }}>{subtitle}</div>
            )}
        </div>
        <div style={{
            flex: 1, maxWidth: 200, height: 3, borderRadius: 3,
            background: `linear-gradient(90deg, ${accent}, ${accent}00)`,
        }} />
    </div>
);


/**
 * Чипы-теги для быстрого перехода к секциям.
 */
const CategoryChips = ({ onScrollTo }) => {
    const chips = [
        { id: 'sec-history',   label: 'Недавние',    color: '#06b6d4' },
        { id: 'sec-fav',       label: 'Избранное',   color: '#ef4444' },
        { id: 'sec-trending',  label: 'Тренды',      color: '#fb923c' },
        { id: 'sec-top',       label: 'Топ',         color: '#ffd700' },
        { id: 'sec-fire',      label: 'Хиты',        color: '#ff5e3a' },
        { id: 'sec-premium',   label: 'Лучшие',       color: '#ffb84a' },
        { id: 'sec-mp',        label: 'Мультиплеер', color: '#5b8bff' },
        { id: 'sec-new',       label: 'Новинки',     color: '#22d97a' },
        { id: 'sec-collections', label: 'Подборки',  color: '#a855f7' },
        { id: 'sec-authors',   label: 'Авторы',      color: '#eab308' },
        { id: 'sec-activity',  label: 'Live',        color: '#dc2626' },
        { id: 'sec-platformer', label: 'Паркур',     color: '#9966ff' },
        { id: 'sec-puzzle',    label: 'Логика',      color: '#3b82f6' },
        { id: 'sec-action',    label: 'Экшен',       color: '#ef4444' },
        { id: 'sec-other',     label: 'Прочее',      color: '#14b8a6' },
    ];
    return (
        <div style={{
            display: 'flex', gap: 10, overflowX: 'auto',
            paddingBottom: 8,
            margin: '8px 0 24px',
            scrollbarWidth: 'thin',
            animation: 'kubikonFadeIn 600ms cubic-bezier(0.34, 1.56, 0.64, 1)',
        }}>
            {chips.map((c, i) => (
                <button
                    key={c.id}
                    onClick={() => onScrollTo(c.id)}
                    style={{
                        flex: '0 0 auto',
                        padding: '10px 20px',
                        background: KT.bgPage,
                        border: `2px solid ${c.color}40`,
                        borderRadius: 999,
                        color: KT.text,
                        fontWeight: 700, fontSize: 14,
                        cursor: 'pointer',
                        transition: 'all 200ms cubic-bezier(0.34, 1.56, 0.64, 1)',
                        whiteSpace: 'nowrap',
                        animation: `kubikonFadeInUp 500ms cubic-bezier(0.34, 1.56, 0.64, 1) ${i * 60}ms both`,
                    }}
                    onMouseEnter={(e) => {
                        e.currentTarget.style.background = c.color;
                        e.currentTarget.style.color = '#fff';
                        e.currentTarget.style.transform = 'translateY(-2px) scale(1.05)';
                        e.currentTarget.style.boxShadow = `0 8px 22px ${c.color}66`;
                    }}
                    onMouseLeave={(e) => {
                        e.currentTarget.style.background = KT.bgPage;
                        e.currentTarget.style.color = KT.text;
                        e.currentTarget.style.transform = 'translateY(0) scale(1)';
                        e.currentTarget.style.boxShadow = 'none';
                    }}
                >{c.label}</button>
            ))}
        </div>
    );
};


/**
 * FeaturedBanner — гигантский баннер «Игра дня» с трендовой игрой.
 * Большая обложка слева, инфо справа, играть-кнопка с пульсацией.
 */
const FeaturedBanner = ({ params, onCardClick }) => {
    const [game, setGame] = useState(null);
    const [hovered, setHovered] = useState(false);

    useEffect(() => {
        let active = true;
        Kubikon3DApi.getFeed(
            1, params.sort, params.maxAge,
            null,
            { per_page: 1 }
        )
        .then(r => { if (active) setGame((r.data?.projects || [])[0] || null); })
        .catch(() => {});
        return () => { active = false; };
    }, [params.sort, params.maxAge]);

    if (!game) return null;

    return (
        <div style={{
            margin: '0 0 32px',
            position: 'relative',
            borderRadius: 24,
            overflow: 'hidden',
            cursor: 'pointer',
            background: 'linear-gradient(135deg, #1a1f4a 0%, #0f1338 100%)',
            boxShadow: hovered
                ? '0 30px 80px rgba(51, 87, 255, 0.42)'
                : '0 16px 50px rgba(51, 87, 255, 0.28)',
            transform: hovered ? 'translateY(-4px)' : 'translateY(0)',
            transition: 'all 320ms cubic-bezier(0.34, 1.56, 0.64, 1)',
            animation: 'kubikonFadeInScale 700ms cubic-bezier(0.34, 1.56, 0.64, 1)',
        }}
        onMouseEnter={() => setHovered(true)}
        onMouseLeave={() => setHovered(false)}
        onClick={() => onCardClick(game)}
        >
            {/* Анимированный градиентный фон */}
            <div style={{
                position: 'absolute', inset: 0,
                background: 'radial-gradient(circle at 20% 50%, rgba(51,87,255,0.4), transparent 50%), '
                          + 'radial-gradient(circle at 80% 30%, rgba(236,72,153,0.3), transparent 50%)',
                animation: 'kubikonGradientShift 10s ease-in-out infinite',
            }} />

            <div style={{
                position: 'relative', zIndex: 1,
                display: 'grid',
                gridTemplateColumns: 'minmax(0, 1.2fr) minmax(0, 1fr)',
                gap: 0,
                minHeight: 320,
            }}>
                {/* Обложка */}
                <div style={{
                    position: 'relative',
                    minHeight: 280,
                    overflow: 'hidden',
                }}>
                    {game.thumbnail ? (
                        <img
                            src={game.thumbnail}
                            alt={game.title}
                            style={{
                                width: '100%', height: '100%',
                                objectFit: 'cover',
                                transform: hovered ? 'scale(1.08)' : 'scale(1.02)',
                                transition: 'transform 800ms cubic-bezier(0.2, 0.8, 0.4, 1)',
                            }}
                        />
                    ) : (
                        <div style={{
                            position: 'absolute', inset: 0,
                            display: 'flex', alignItems: 'center', justifyContent: 'center',
                            fontSize: 120,
                        }}><Icon name="gamepad" size={14} /></div>
                    )}
                    {/* Бейдж «ИГРА ДНЯ» */}
                    <div style={{
                        position: 'absolute', top: 20, left: 20,
                        padding: '8px 18px',
                        background: 'linear-gradient(135deg, #ffd700, #ff8833)',
                        borderRadius: 999,
                        color: '#1a1338',
                        fontWeight: 900, fontSize: 13,
                        letterSpacing: 1,
                        boxShadow: '0 8px 22px rgba(255, 215, 0, 0.55)',
                        animation: 'kubikonPulseRing 2s ease-in-out infinite',
                    }}><Icon name="crown" size={13} /> ИГРА ДНЯ</div>
                </div>

                {/* Инфо */}
                <div style={{
                    padding: '40px 32px',
                    display: 'flex', flexDirection: 'column', justifyContent: 'center',
                    gap: 16,
                }}>
                    <div style={{
                        fontSize: 11, fontWeight: 800, letterSpacing: 2,
                        color: '#ffd700', textTransform: 'uppercase',
                        opacity: 0.9,
                    }}><Icon name="flame" size={13} /> Самая горячая</div>
                    <h2 style={{
                        margin: 0,
                        fontSize: 38, fontWeight: 900,
                        color: '#fff',
                        letterSpacing: -1,
                        lineHeight: 1.05,
                    }}>{game.title}</h2>
                    {game.description && (
                        <p style={{
                            margin: 0,
                            fontSize: 14,
                            color: 'rgba(255, 255, 255, 0.78)',
                            lineHeight: 1.55,
                            display: '-webkit-box',
                            WebkitLineClamp: 3,
                            WebkitBoxOrient: 'vertical',
                            overflow: 'hidden',
                        }}>{game.description}</p>
                    )}
                    <div style={{
                        display: 'flex', gap: 16, alignItems: 'center',
                        marginTop: 8,
                    }}>
                        <div style={{
                            padding: '12px 28px',
                            background: 'linear-gradient(135deg, #3357ff, #1e2da5)',
                            borderRadius: 12,
                            color: '#fff',
                            fontWeight: 800, fontSize: 16,
                            boxShadow: '0 8px 22px rgba(51, 87, 255, 0.55)',
                            display: 'inline-flex', alignItems: 'center', gap: 8,
                            transition: 'all 200ms ease',
                            transform: hovered ? 'scale(1.05)' : 'scale(1)',
                        }}>
                            <Icon name="play" size={16} /> Играть
                        </div>
                        <div style={{
                            display: 'flex', gap: 14, fontSize: 13,
                            color: 'rgba(255, 255, 255, 0.78)', fontWeight: 700,
                        }}>
                            <span><Icon name="visible" size={13} /> {(game.play_count || 0).toLocaleString('ru')}</span>
                            <span><Icon name="heart" size={13} /> {(game.likes_count || 0).toLocaleString('ru')}</span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
};


/**
 * PodiumSection — топ-3 на пьедестале (золото/серебро/бронза).
 * Центральная карточка крупнее, по бокам поменьше — как в спорте.
 */
const PodiumSection = ({ title, subtitle, params, onCardClick }) => {
    const [items, setItems] = useState([]);
    useEffect(() => {
        let active = true;
        Kubikon3DApi.getFeed(
            1, params.sort, params.maxAge, null,
            { per_page: 3 }
        )
        .then(r => { if (active) setItems(r.data?.projects || []); })
        .catch(() => {});
        return () => { active = false; };
    }, [params.sort, params.maxAge]);

    if (items.length < 3) return null;
    const [first, second, third] = items;
    const PODIUM = [
        { p: second, place: 2, color: '#c0c0c0', size: 'medium', emoji: '🥈', height: 200 },
        { p: first,  place: 1, color: '#ffd700', size: 'large',  emoji: '🥇', height: 240 },
        { p: third,  place: 3, color: '#cd7f32', size: 'medium', emoji: '🥉', height: 180 },
    ];
    return (
        <div style={{ marginTop: 32 }}>
            <CategoryHeader title={title} subtitle={subtitle} accent="#ffd700" emoji="🏆" />
            <div style={{
                display: 'grid',
                gridTemplateColumns: 'repeat(3, 1fr)',
                gap: 14, alignItems: 'end',
                position: 'relative',
            }}>
                {PODIUM.map((slot, idx) => (
                    <PodiumCard key={slot.p.id} slot={slot} idx={idx}
                                onClick={() => onCardClick(slot.p)} />
                ))}
            </div>
        </div>
    );
};

const PodiumCard = ({ slot, idx, onClick }) => {
    const [hovered, setHovered] = useState(false);
    const { p, place, color, emoji, height } = slot;
    return (
        <div
            onClick={onClick}
            onMouseEnter={() => setHovered(true)}
            onMouseLeave={() => setHovered(false)}
            style={{
                cursor: 'pointer',
                animation: `kubikonFadeInUp 600ms cubic-bezier(0.34, 1.56, 0.64, 1) ${idx * 120}ms both`,
                position: 'relative',
            }}
        >
            {/* Бейдж места */}
            <div style={{
                position: 'absolute', top: -10, left: '50%',
                transform: 'translateX(-50%)',
                width: 42, height: 42,
                borderRadius: '50%',
                background: `linear-gradient(135deg, ${color}, ${color}cc)`,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                color: '#fff',
                boxShadow: `0 6px 18px ${color}88`,
                zIndex: 3,
                animation: hovered ? 'kubikonPulseRing 1.5s ease-in-out infinite' : 'none',
            }}><Icon emoji={emoji} size={22} /></div>

            {/* Обложка-карточка */}
            <div style={{
                background: KT.bgPage,
                border: `3px solid ${hovered ? color : color + '40'}`,
                borderRadius: 18,
                overflow: 'hidden',
                boxShadow: hovered
                    ? `0 20px 50px ${color}55`
                    : `0 8px 24px ${color}25`,
                transform: hovered ? 'translateY(-8px) scale(1.03)' : 'translateY(0) scale(1)',
                transition: 'all 300ms cubic-bezier(0.34, 1.56, 0.64, 1)',
                marginTop: 22,
            }}>
                <div style={{
                    aspectRatio: '4/3',
                    background: '#e2e8f0',
                    overflow: 'hidden',
                    position: 'relative',
                }}>
                    {p.thumbnail ? (
                        <img src={p.thumbnail} alt={p.title}
                             style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
                    ) : (
                        <div style={{
                            position: 'absolute', inset: 0,
                            display: 'flex', alignItems: 'center', justifyContent: 'center',
                            fontSize: 56,
                        }}><Icon name="gamepad" size={14} /></div>
                    )}
                    <div style={{
                        position: 'absolute', inset: 0,
                        background: `linear-gradient(180deg, transparent 50%, ${color}33 100%)`,
                    }} />
                </div>
                <div style={{ padding: '14px 16px' }}>
                    <div style={{
                        fontSize: place === 1 ? 18 : 16, fontWeight: 800,
                        color: KT.text,
                        overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                        marginBottom: 4,
                    }}>{p.title}</div>
                    <div style={{
                        display: 'flex', gap: 12, fontSize: 12,
                        color: KT.textSecondary, fontWeight: 700,
                    }}>
                        <span><Icon name="visible" size={13} /> {(p.play_count || 0).toLocaleString('ru')}</span>
                        <span><Icon name="heart" size={13} /> {(p.likes_count || 0).toLocaleString('ru')}</span>
                    </div>
                </div>
            </div>

            {/* «Подставка» пьедестала */}
            <div style={{
                marginTop: 8,
                height: place === 1 ? 50 : place === 2 ? 35 : 25,
                background: `linear-gradient(180deg, ${color}, ${color}88)`,
                borderRadius: '6px 6px 0 0',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                fontWeight: 900, fontSize: 24, color: '#fff',
                textShadow: '0 2px 6px rgba(0,0,0,0.3)',
                boxShadow: `inset 0 4px 8px rgba(255,255,255,0.25)`,
            }}>{place}</div>
        </div>
    );
};


/**
 * SectionBanner — широкий горизонтальный баннер-разделитель между секциями.
 * Гигантский эмоджи + заголовок + градиент-фон + анимация.
 */
const SectionBanner = ({ emoji, title, subtitle, gradient }) => (
    <div style={{
        position: 'relative',
        margin: '40px 0 16px',
        padding: '32px 40px',
        background: gradient,
        borderRadius: 20,
        overflow: 'hidden',
        boxShadow: '0 20px 50px rgba(0,0,0,0.18)',
        animation: 'kubikonFadeInScale 700ms cubic-bezier(0.34, 1.56, 0.64, 1)',
    }}>
        {/* Анимированный декор-кружок */}
        <div style={{
            position: 'absolute', top: -60, right: -60,
            width: 200, height: 200, borderRadius: '50%',
            background: 'radial-gradient(circle, rgba(255,255,255,0.18), transparent 70%)',
            animation: 'kubikonFloatY 4s ease-in-out infinite',
        }} />
        <div style={{
            position: 'absolute', bottom: -40, left: '40%',
            width: 140, height: 140, borderRadius: '50%',
            background: 'radial-gradient(circle, rgba(255,255,255,0.12), transparent 70%)',
            animation: 'kubikonFloatY 5s ease-in-out infinite reverse',
        }} />

        <div style={{
            position: 'relative', zIndex: 1,
            display: 'flex', alignItems: 'center', gap: 24,
        }}>
            <div style={{
                color: '#fff',
                filter: 'drop-shadow(0 6px 18px rgba(0,0,0,0.25))',
                animation: 'kubikonFloatY 3s ease-in-out infinite',
            }}><Icon emoji={emoji} size={64} /></div>
            <div style={{ flex: 1 }}>
                <h2 style={{
                    margin: 0,
                    fontSize: 32, fontWeight: 900,
                    color: '#fff',
                    letterSpacing: -0.8,
                    textShadow: '0 2px 12px rgba(0,0,0,0.18)',
                    lineHeight: 1.1,
                }}>{title}</h2>
                {subtitle && (
                    <div style={{
                        marginTop: 6,
                        fontSize: 15, fontWeight: 600,
                        color: 'rgba(255,255,255,0.88)',
                    }}>{subtitle}</div>
                )}
            </div>
        </div>
    </div>
);


/**
 * PremiumSection — секция «Лучшее по рейтингу»: топ игр умной ленты
 * по hot_score (золотой стиль). Раньше показывала rank2-игры, но рангов
 * больше нет — теперь это просто самые высоко оценённые игры.
 */
const PremiumSection = ({ params, onCardClick }) => {
    const [items, setItems] = useState([]);
    useEffect(() => {
        let active = true;
        // tab=recommended — лента уже отсортирована по hot_score.
        Kubikon3DApi.getFeed(
            1, 'recommended', params.maxAge, null,
            { per_page: params.per_page }
        )
        .then(r => { if (active) setItems(r.data?.projects || []); })
        .catch(() => {});
        return () => { active = false; };
    }, [params.maxAge, params.per_page]);

    if (!items.length) return null;

    return (
        <div style={{
            margin: '36px 0 0',
            padding: '28px 24px',
            background: 'linear-gradient(135deg, rgba(255,184,74,0.08), rgba(255,215,0,0.04))',
            border: '2px solid rgba(255,184,74,0.35)',
            borderRadius: 24,
            position: 'relative',
            overflow: 'hidden',
            animation: 'kubikonFadeInScale 700ms cubic-bezier(0.34, 1.56, 0.64, 1)',
        }}>
            {/* Звёзды-декор */}
            {[...Array(6)].map((_, i) => (
                <div key={i} style={{
                    position: 'absolute',
                    top: `${10 + i * 15}%`,
                    left: `${(i * 17) % 90}%`,
                    fontSize: 14 + (i % 3) * 4,
                    opacity: 0.3,
                    animation: `kubikonFloatY ${3 + (i % 3)}s ease-in-out infinite`,
                    pointerEvents: 'none',
                }}><Icon name="star" size={14} /></div>
            ))}

            <div style={{
                display: 'flex', alignItems: 'center', gap: 16,
                marginBottom: 20, position: 'relative',
            }}>
                <div style={{
                    flexShrink: 0, width: 64, height: 64,
                    borderRadius: 18,
                    background: 'linear-gradient(135deg, #ffd700, #ff8833)',
                    boxShadow: '0 12px 28px rgba(255,184,74,0.55)',
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    fontSize: 36,
                    animation: 'kubikonPulseRing 2.5s ease-in-out infinite',
                }}><Icon name="star" size={14} /></div>
                <div style={{ flex: 1 }}>
                    <div style={{
                        fontSize: 11, fontWeight: 900, letterSpacing: 2,
                        color: '#ff8833', textTransform: 'uppercase',
                    }}>Высший рейтинг</div>
                    <h2 style={{
                        margin: '4px 0 0',
                        fontSize: 28, fontWeight: 900, color: KT.text,
                        letterSpacing: -0.5,
                        background: 'linear-gradient(120deg, #ffd700, #ff8833)',
                        WebkitBackgroundClip: 'text',
                        WebkitTextFillColor: 'transparent',
                        backgroundClip: 'text',
                    }}>Лучшие игры</h2>
                    <div style={{
                        marginTop: 4, fontSize: 13,
                        color: KT.textSecondary, fontWeight: 600,
                    }}>Игры, которые игроки оценили выше всего</div>
                </div>
            </div>

            <div style={{
                display: 'grid',
                gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))',
                gap: 14,
                position: 'relative',
            }}>
                {items.map((p, i) => (
                    <GameCard key={p.id} project={p} rank={i} sort="popular"
                              onClick={() => onCardClick(p)}
                              animateDelay={Math.min(i * 60, 360)} />
                ))}
            </div>
        </div>
    );
};


/**
 * CompactGenreCard — карточка жанра с 4 миниатюрами игр в 2×2.
 */
const CompactGenreCard = ({ title, subtitle, accent, bgGradient, params, onCardClick }) => {
    const [items, setItems] = useState([]);
    const [loading, setLoading] = useState(true);
    useEffect(() => {
        let active = true;
        setLoading(true);
        Kubikon3DApi.getFeed(
            1, params.sort, params.maxAge, null,
            { genre: params.genre, per_page: 4 }
        )
        .then(r => { if (active) setItems(r.data?.projects || []); })
        .catch(() => {})
        .finally(() => { if (active) setLoading(false); });
        return () => { active = false; };
    }, [params.genre, params.sort, params.maxAge]);

    if (loading) return null;
    if (!items.length) return null;

    return (
        <div style={{
            background: bgGradient,
            border: `2px solid ${accent}30`,
            borderRadius: 20,
            padding: 20,
            position: 'relative',
            overflow: 'hidden',
            animation: 'kubikonFadeInUp 600ms cubic-bezier(0.34, 1.56, 0.64, 1)',
        }}>
            <div style={{ marginBottom: 14 }}>
                <h3 style={{
                    margin: 0, fontSize: 22, fontWeight: 900,
                    color: KT.text, letterSpacing: -0.4,
                }}>{title}</h3>
                <div style={{
                    fontSize: 13, color: KT.textSecondary,
                    fontWeight: 600, marginTop: 2,
                }}>{subtitle}</div>
            </div>
            <div style={{
                display: 'grid',
                gridTemplateColumns: 'repeat(2, 1fr)',
                gap: 10,
            }}>
                {items.slice(0, 4).map((p, i) => (
                    <MiniGameCard key={p.id} project={p} accent={accent}
                                   onClick={() => onCardClick(p)} delay={i * 60} />
                ))}
            </div>
        </div>
    );
};

/** Мини-карточка для CompactGenreCard. */
const MiniGameCard = ({ project, accent, onClick, delay }) => {
    const [hovered, setHovered] = useState(false);
    return (
        <div
            onClick={onClick}
            onMouseEnter={() => setHovered(true)}
            onMouseLeave={() => setHovered(false)}
            style={{
                background: KT.bgPage,
                border: `1.5px solid ${hovered ? accent : KT.border}`,
                borderRadius: 12,
                overflow: 'hidden',
                cursor: 'pointer',
                transform: hovered ? 'translateY(-3px) scale(1.02)' : 'translateY(0)',
                boxShadow: hovered ? `0 12px 26px ${accent}33` : '0 2px 6px rgba(0,0,0,0.05)',
                transition: 'all 240ms cubic-bezier(0.34, 1.56, 0.64, 1)',
                animation: `kubikonFadeIn 500ms cubic-bezier(0.34, 1.56, 0.64, 1) ${delay}ms both`,
            }}
        >
            <div style={{
                aspectRatio: '4/3',
                background: '#e2e8f0',
                position: 'relative', overflow: 'hidden',
            }}>
                {project.thumbnail ? (
                    <img src={project.thumbnail} alt={project.title}
                         style={{ width: '100%', height: '100%', objectFit: 'cover',
                                  transform: hovered ? 'scale(1.08)' : 'scale(1)',
                                  transition: 'transform 500ms cubic-bezier(0.2,0.8,0.4,1)' }} />
                ) : (
                    <div style={{ position: 'absolute', inset: 0,
                        display: 'flex', alignItems: 'center', justifyContent: 'center',
                        fontSize: 32 }}><Icon name="gamepad" size={14} /></div>
                )}
            </div>
            <div style={{ padding: '8px 10px' }}>
                <div style={{
                    fontSize: 13, fontWeight: 700, color: KT.text,
                    overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                }}>{project.title}</div>
            </div>
        </div>
    );
};


/**
 * CreateBanner — призывный CTA-баннер «Создай свою игру».
 */
const CreateBanner = ({ onClick }) => {
    const [hovered, setHovered] = useState(false);
    return (
        <div
            onClick={onClick}
            onMouseEnter={() => setHovered(true)}
            onMouseLeave={() => setHovered(false)}
            style={{
                cursor: 'pointer',
                margin: '48px 0 24px',
                padding: '40px 32px',
                background: 'linear-gradient(135deg, #ec4899 0%, #9966ff 35%, #3357ff 100%)',
                backgroundSize: '200% 200%',
                borderRadius: 24,
                display: 'grid',
                gridTemplateColumns: 'minmax(0, 1fr) auto',
                gap: 24, alignItems: 'center',
                boxShadow: hovered
                    ? '0 30px 80px rgba(153, 102, 255, 0.55)'
                    : '0 16px 40px rgba(153, 102, 255, 0.35)',
                transform: hovered ? 'translateY(-4px) scale(1.005)' : 'translateY(0)',
                transition: 'all 320ms cubic-bezier(0.34, 1.56, 0.64, 1)',
                animation: 'kubikonGradientShift 8s ease-in-out infinite, '
                         + 'kubikonFadeInScale 700ms cubic-bezier(0.34, 1.56, 0.64, 1)',
                position: 'relative',
                overflow: 'hidden',
            }}
        >
            {[...Array(5)].map((_, i) => (
                <div key={i} style={{
                    position: 'absolute',
                    top: `${20 + i * 12}%`,
                    left: `${(i * 23) % 90}%`,
                    opacity: 0.25, color: '#fff',
                    animation: `kubikonFloatY ${3 + (i % 3)}s ease-in-out infinite`,
                    pointerEvents: 'none',
                }}><Icon name={['gamepad', 'rocket', 'palette', 'sparkles', 'star'][i]} size={28 + (i % 3) * 10} /></div>
            ))}
            <div style={{ position: 'relative', zIndex: 1 }}>
                <div style={{
                    fontSize: 12, fontWeight: 800, letterSpacing: 2,
                    color: 'rgba(255,255,255,0.88)', textTransform: 'uppercase',
                }}><Icon name="wrench" size={13} /> Конструктор</div>
                <h2 style={{
                    margin: '6px 0 8px',
                    fontSize: 34, fontWeight: 900,
                    color: '#fff', letterSpacing: -0.8, lineHeight: 1.1,
                }}>Хочешь создать свою игру?</h2>
                <p style={{
                    margin: 0, fontSize: 15,
                    color: 'rgba(255,255,255,0.92)', lineHeight: 1.5,
                    maxWidth: 600,
                }}>Открой Кубикон-студию и собери игру из блоков, моделей
                и скриптов. Опубликуй и набирай рекорды от друзей!</p>
            </div>
            <div style={{
                position: 'relative', zIndex: 1,
                padding: '16px 32px',
                background: '#fff',
                color: '#3357ff',
                fontWeight: 900, fontSize: 16,
                borderRadius: 999,
                whiteSpace: 'nowrap',
                boxShadow: hovered
                    ? '0 12px 28px rgba(0,0,0,0.25)'
                    : '0 6px 16px rgba(0,0,0,0.18)',
                transform: hovered ? 'scale(1.05)' : 'scale(1)',
                transition: 'all 200ms ease',
                display: 'inline-flex', alignItems: 'center', gap: 8,
            }}><Icon name="rocket" size={13} /> Начать создавать</div>
        </div>
    );
};


/**
 * CarouselScroll — горизонтальная прокрутка с кнопками-стрелками по бокам
 * и кастомным скролбаром. Кнопки появляются на hover, прячутся если
 * нет смысла скроллить (всё видно сразу).
 */
const CarouselScroll = ({ accent = '#3357ff', children }) => {
    const scrollRef = useRef(null);
    const [hovered, setHovered] = useState(false);
    const [canLeft, setCanLeft] = useState(false);
    const [canRight, setCanRight] = useState(false);

    const updateButtons = useCallback(() => {
        const el = scrollRef.current;
        if (!el) return;
        setCanLeft(el.scrollLeft > 4);
        setCanRight(el.scrollLeft < el.scrollWidth - el.clientWidth - 4);
    }, []);

    useEffect(() => {
        const el = scrollRef.current;
        if (!el) return;
        updateButtons();
        const onScroll = () => updateButtons();
        el.addEventListener('scroll', onScroll, { passive: true });
        const onResize = () => updateButtons();
        window.addEventListener('resize', onResize);
        return () => {
            el.removeEventListener('scroll', onScroll);
            window.removeEventListener('resize', onResize);
        };
    }, [updateButtons]);

    const scrollBy = (dir) => {
        const el = scrollRef.current;
        if (!el) return;
        const dx = el.clientWidth * 0.7 * dir;
        el.scrollBy({ left: dx, behavior: 'smooth' });
    };

    const scrollbarClass = `kubikonScroll-${accent.replace('#', '')}`;
    const scrollbarStyle = `
        .${scrollbarClass}::-webkit-scrollbar {
            height: 10px;
        }
        .${scrollbarClass}::-webkit-scrollbar-track {
            background: rgba(0,0,0,0.05);
            border-radius: 8px;
        }
        .${scrollbarClass}::-webkit-scrollbar-thumb {
            background: linear-gradient(90deg, ${accent}, ${accent}88);
            border-radius: 8px;
            border: 2px solid transparent;
            background-clip: padding-box;
        }
        .${scrollbarClass}::-webkit-scrollbar-thumb:hover {
            background: linear-gradient(90deg, ${accent}, ${accent}cc);
            background-clip: padding-box;
        }
    `;

    return (
        <div
            style={{ position: 'relative' }}
            onMouseEnter={() => setHovered(true)}
            onMouseLeave={() => setHovered(false)}
        >
            <style>{scrollbarStyle}</style>
            <div
                ref={scrollRef}
                className={scrollbarClass}
                style={{
                    display: 'flex', gap: 14, overflowX: 'auto',
                    paddingBottom: 14,
                    scrollSnapType: 'x mandatory',
                    scrollBehavior: 'smooth',
                    scrollbarColor: `${accent} rgba(0,0,0,0.08)`,
                }}
            >
                {children}
            </div>
            {/* Кнопка влево */}
            {canLeft && (
                <button
                    onClick={() => scrollBy(-1)}
                    style={{
                        position: 'absolute',
                        left: -8, top: '50%',
                        transform: `translate(0, -50%) scale(${hovered ? 1 : 0.92})`,
                        width: 44, height: 44, borderRadius: '50%',
                        background: `linear-gradient(135deg, ${accent}, ${accent}cc)`,
                        border: 'none',
                        color: '#fff',
                        fontSize: 22, fontWeight: 900,
                        cursor: 'pointer',
                        boxShadow: `0 8px 22px ${accent}55`,
                        opacity: hovered ? 1 : 0.55,
                        transition: 'all 220ms cubic-bezier(0.34,1.56,0.64,1)',
                        zIndex: 5,
                        display: 'flex', alignItems: 'center', justifyContent: 'center',
                    }}
                    onMouseEnter={(e) => {
                        e.currentTarget.style.transform = 'translate(0, -50%) scale(1.1)';
                        e.currentTarget.style.opacity = '1';
                    }}
                    onMouseLeave={(e) => {
                        e.currentTarget.style.transform = `translate(0, -50%) scale(${hovered ? 1 : 0.92})`;
                        e.currentTarget.style.opacity = hovered ? '1' : '0.55';
                    }}
                >‹</button>
            )}
            {/* Кнопка вправо */}
            {canRight && (
                <button
                    onClick={() => scrollBy(1)}
                    style={{
                        position: 'absolute',
                        right: -8, top: '50%',
                        transform: `translate(0, -50%) scale(${hovered ? 1 : 0.92})`,
                        width: 44, height: 44, borderRadius: '50%',
                        background: `linear-gradient(135deg, ${accent}, ${accent}cc)`,
                        border: 'none',
                        color: '#fff',
                        fontSize: 22, fontWeight: 900,
                        cursor: 'pointer',
                        boxShadow: `0 8px 22px ${accent}55`,
                        opacity: hovered ? 1 : 0.55,
                        transition: 'all 220ms cubic-bezier(0.34,1.56,0.64,1)',
                        zIndex: 5,
                        display: 'flex', alignItems: 'center', justifyContent: 'center',
                    }}
                    onMouseEnter={(e) => {
                        e.currentTarget.style.transform = 'translate(0, -50%) scale(1.1)';
                        e.currentTarget.style.opacity = '1';
                    }}
                    onMouseLeave={(e) => {
                        e.currentTarget.style.transform = `translate(0, -50%) scale(${hovered ? 1 : 0.92})`;
                        e.currentTarget.style.opacity = hovered ? '1' : '0.55';
                    }}
                >›</button>
            )}
        </div>
    );
};


/**
 * EventBanner — активные события платформы (Game Jam, конкурсы).
 */
const EventBanner = ({ onCardClick }) => {
    const [events, setEvents] = useState([]);
    useEffect(() => {
        let active = true;
        Kubikon3DApi.getEvents()
            .then(r => {
                if (!active) return;
                // Фильтруем закрытые события (Game Jam пока не проводится).
                // Список id событий которые НЕ показываем независимо от бэка.
                const HIDDEN = new Set(['create_jam_2026']);
                const list = (r.data?.events || [])
                    .filter(e => !HIDDEN.has(e.id));
                setEvents(list);
            })
            .catch(() => {});
        return () => { active = false; };
    }, []);
    if (!events.length) return null;
    const e = events[0];
    return (
        <div style={{
            position: 'relative', zIndex: 1,
            margin: '8px 0 18px',
            padding: '20px 26px',
            background: e.gradient || 'linear-gradient(135deg, #ec4899, #9966ff, #3357ff)',
            backgroundSize: '200% 200%',
            borderRadius: 18,
            cursor: 'pointer',
            boxShadow: '0 16px 40px rgba(153, 102, 255, 0.4)',
            display: 'grid',
            gridTemplateColumns: '1fr auto',
            gap: 16, alignItems: 'center',
            overflow: 'hidden',
            animation: 'kubikonGradientShift 8s ease-in-out infinite, '
                     + 'kubikonFadeInScale 700ms cubic-bezier(0.34, 1.56, 0.64, 1)',
        }} onClick={() => e.cta_url && onCardClick(e.cta_url)}>
            <div style={{ position: 'relative', zIndex: 1 }}>
                <div style={{
                    fontSize: 11, fontWeight: 800, letterSpacing: 2,
                    color: 'rgba(255,255,255,0.85)', textTransform: 'uppercase',
                }}><Icon emoji="🎉" size={13} /> СОБЫТИЕ</div>
                <h3 style={{
                    margin: '4px 0 4px', fontSize: 22, fontWeight: 900,
                    color: '#fff', letterSpacing: -0.4,
                }}>{e.title}</h3>
                <div style={{
                    fontSize: 13, color: 'rgba(255,255,255,0.85)',
                }}>{e.subtitle}</div>
            </div>
            {e.cta_label && (
                <div style={{
                    padding: '10px 22px', background: '#fff', color: '#3357ff',
                    fontWeight: 800, fontSize: 14, borderRadius: 999,
                    whiteSpace: 'nowrap',
                    boxShadow: '0 4px 12px rgba(0,0,0,0.18)',
                }}>{e.cta_label} <Icon name="arrow-right" size={13} style={{ verticalAlign: '-2px' }} /></div>
            )}
        </div>
    );
};


/**
 * ResumeCard — большая «Продолжить играть» карточка.
 */
const ResumeCard = ({ userId, onClick }) => {
    const [game, setGame] = useState(null);
    useEffect(() => {
        let active = true;
        Kubikon3DApi.getPlayHistory(userId, 1)
            .then(r => {
                if (!active) return;
                const list = r.data?.projects || [];
                setGame(list[0] || null);
            })
            .catch(() => {});
        return () => { active = false; };
    }, [userId]);
    if (!game) return null;
    return (
        <div onClick={() => onClick(game)} style={{
            position: 'relative', zIndex: 1,
            margin: '0 0 24px',
            padding: '16px',
            background: 'linear-gradient(135deg, rgba(6,182,212,0.16), rgba(34,217,122,0.10))',
            border: '2px solid rgba(6,182,212,0.45)',
            borderRadius: 18, cursor: 'pointer',
            display: 'grid', gridTemplateColumns: '120px 1fr auto', gap: 16,
            alignItems: 'center',
            animation: 'kubikonFadeInUp 600ms cubic-bezier(0.34, 1.56, 0.64, 1)',
            transition: 'all 240ms ease',
        }}
        onMouseEnter={(e) => {
            e.currentTarget.style.transform = 'translateY(-3px)';
            e.currentTarget.style.boxShadow = '0 18px 40px rgba(6,182,212,0.30)';
        }}
        onMouseLeave={(e) => {
            e.currentTarget.style.transform = '';
            e.currentTarget.style.boxShadow = '';
        }}>
            <div style={{
                aspectRatio: '4/3', borderRadius: 12, overflow: 'hidden',
                background: '#e2e8f0',
            }}>
                {game.thumbnail ? (
                    <img src={game.thumbnail} alt={game.title}
                         style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
                ) : (
                    <div style={{ display: 'flex', alignItems: 'center',
                        justifyContent: 'center', height: '100%', fontSize: 40 }}><Icon name="gamepad" size={14} /></div>
                )}
            </div>
            <div>
                <div style={{
                    fontSize: 11, fontWeight: 800, letterSpacing: 2,
                    color: '#06b6d4', textTransform: 'uppercase',
                }}><Icon name="play" size={13} /> Продолжить</div>
                <div style={{
                    fontSize: 22, fontWeight: 900, color: KT.text,
                    margin: '4px 0 4px',
                }}>{game.title}</div>
                <div style={{ fontSize: 12, color: KT.textSecondary }}>
                    <Icon name="user" size={13} /> {game.author_username || '...'} · <Icon name="visible" size={13} /> {game.play_count || 0}
                </div>
            </div>
            <div style={{
                padding: '10px 22px', borderRadius: 999,
                background: 'linear-gradient(135deg, #06b6d4, #22d97a)',
                color: '#fff', fontWeight: 800, fontSize: 14,
                boxShadow: '0 8px 22px rgba(6,182,212,0.4)',
            }}>Играть</div>
        </div>
    );
};


/**
 * HistorySection — недавно сыгранные игры пользователя.
 */
const HistorySection = ({ userId, onCardClick }) => {
    const [items, setItems] = useState([]);
    const [loading, setLoading] = useState(true);
    useEffect(() => {
        let active = true;
        Kubikon3DApi.getPlayHistory(userId, 10)
            .then(r => { if (active) setItems(r.data?.projects || []); })
            .catch(() => {})
            .finally(() => { if (active) setLoading(false); });
        return () => { active = false; };
    }, [userId]);
    if (loading || items.length === 0) return null;
    return (
        <div style={{ marginTop: 28 }}>
            <CategoryHeader title="Недавно играли" subtitle="Последние сессии"
                            accent="#06b6d4" emoji="🕒" />
            <CarouselScroll accent="#06b6d4">
                {items.map((p, i) => (
                    <div key={p.id} style={{
                        flex: '0 0 220px', minWidth: 220,
                        scrollSnapAlign: 'start',
                    }}>
                        <GameCard project={p} rank={i} sort="popular"
                                  onClick={() => onCardClick(p)}
                                  animateDelay={Math.min(i * 30, 200)} />
                    </div>
                ))}
            </CarouselScroll>
        </div>
    );
};


/**
 * FavoritesSection — избранные игры пользователя.
 */
const FavoritesSection = ({ userId, onCardClick }) => {
    const [items, setItems] = useState([]);
    const [loading, setLoading] = useState(true);
    useEffect(() => {
        let active = true;
        Kubikon3DApi.getMyFavorites(userId)
            .then(r => { if (active) setItems(r.data?.projects || []); })
            .catch(() => {})
            .finally(() => { if (active) setLoading(false); });
        return () => { active = false; };
    }, [userId]);
    if (loading) return null;
    if (items.length === 0) return null;
    return (
        <div style={{ marginTop: 28 }}>
            <CategoryHeader title="Моё избранное" subtitle="Сохранённые игры"
                            accent="#ef4444" emoji="❤️" />
            <CarouselScroll accent="#ef4444">
                {items.map((p, i) => (
                    <div key={p.id} style={{
                        flex: '0 0 240px', minWidth: 240,
                        scrollSnapAlign: 'start',
                    }}>
                        <GameCard project={p} rank={i} sort="popular"
                                  onClick={() => onCardClick(p)}
                                  animateDelay={Math.min(i * 30, 200)} />
                    </div>
                ))}
            </CarouselScroll>
        </div>
    );
};


/**
 * TrendingSection — игры в тренде (быстро растут).
 */
const TrendingSection = ({ onCardClick }) => {
    const [items, setItems] = useState([]);
    useEffect(() => {
        let active = true;
        Kubikon3DApi.getTrending(10)
            .then(r => { if (active) setItems(r.data?.projects || []); })
            .catch(() => {});
        return () => { active = false; };
    }, []);
    if (!items.length) return null;
    return (
        <div style={{ marginTop: 28 }}>
            <CategoryHeader title="В тренде" subtitle="Резкий рост за 14 дней"
                            accent="#fb923c" emoji="📈" />
            <CarouselScroll accent="#fb923c">
                {items.map((p, i) => (
                    <div key={p.id} style={{
                        flex: '0 0 240px', minWidth: 240,
                        scrollSnapAlign: 'start',
                    }}>
                        <GameCard project={p} rank={i} sort="popular"
                                  onClick={() => onCardClick(p)}
                                  animateDelay={Math.min(i * 30, 200)} />
                    </div>
                ))}
            </CarouselScroll>
        </div>
    );
};


/**
 * CollectionsSection — курируемые подборки от админов.
 */
const CollectionsSection = ({ onCardClick }) => {
    const [collections, setCollections] = useState([]);
    useEffect(() => {
        let active = true;
        Kubikon3DApi.getCollections()
            .then(r => { if (active) setCollections(r.data?.collections || []); })
            .catch(() => {});
        return () => { active = false; };
    }, []);
    if (!collections.length) return null;
    return (
        <div style={{ marginTop: 36 }}>
            <CategoryHeader title="Подборки" subtitle="От редакции — для тебя"
                            accent="#a855f7" emoji="📚" />
            {collections.map(col => (
                <div key={col.id} style={{
                    marginBottom: 22,
                    padding: '18px',
                    background: `linear-gradient(135deg, ${col.cover_color}18, transparent)`,
                    border: `2px solid ${col.cover_color}40`,
                    borderRadius: 16,
                }}>
                    <div style={{
                        display: 'flex', alignItems: 'center', gap: 12, marginBottom: 12,
                    }}>
                        <div style={{
                            fontSize: 18, fontWeight: 900, color: KT.text,
                        }}>{col.title}</div>
                        <div style={{
                            fontSize: 13, color: KT.textSecondary, fontWeight: 600,
                        }}>{col.subtitle}</div>
                    </div>
                    <CarouselScroll accent={col.cover_color}>
                        {(col.projects || []).map((p, i) => (
                            <div key={p.id} style={{
                                flex: '0 0 220px', minWidth: 220,
                                scrollSnapAlign: 'start',
                            }}>
                                <GameCard project={p} rank={i} sort="popular"
                                          onClick={() => onCardClick(p)}
                                          animateDelay={Math.min(i * 30, 200)} />
                            </div>
                        ))}
                    </CarouselScroll>
                </div>
            ))}
        </div>
    );
};


/**
 * TopAuthorsSection — рейтинг создателей по сумме плеев.
 */
const TopAuthorsSection = ({ onUserClick }) => {
    const [authors, setAuthors] = useState([]);
    useEffect(() => {
        let active = true;
        Kubikon3DApi.getTopAuthors(10)
            .then(r => { if (active) setAuthors(r.data?.authors || []); })
            .catch(() => {});
        return () => { active = false; };
    }, []);
    if (!authors.length) return null;
    return (
        <div style={{ marginTop: 36 }}>
            <CategoryHeader title="Топ-авторы" subtitle="Кто двигает платформу"
                            accent="#eab308" emoji="👑" />
            <div style={{
                display: 'grid',
                gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))',
                gap: 14,
            }}>
                {authors.map((a, i) => (
                    <div key={a.user_id}
                        onClick={() => onUserClick(a.user_id)}
                        style={{
                            background: KT.bgPage,
                            border: `2px solid ${i < 3 ? '#eab30880' : KT.border}`,
                            borderRadius: 14,
                            padding: 14,
                            display: 'flex', alignItems: 'center', gap: 12,
                            cursor: 'pointer',
                            transition: 'all 240ms cubic-bezier(0.34,1.56,0.64,1)',
                            animation: `kubikonFadeInUp 500ms ease ${i * 50}ms both`,
                            boxShadow: i < 3 ? '0 6px 18px rgba(234, 179, 8, 0.18)' : 'none',
                        }}
                        onMouseEnter={(e) => {
                            e.currentTarget.style.transform = 'translateY(-3px)';
                            e.currentTarget.style.boxShadow = '0 14px 32px rgba(234,179,8,0.35)';
                        }}
                        onMouseLeave={(e) => {
                            e.currentTarget.style.transform = '';
                            e.currentTarget.style.boxShadow = i < 3
                                ? '0 6px 18px rgba(234, 179, 8, 0.18)' : 'none';
                        }}
                    >
                        <div style={{
                            width: 44, height: 44, borderRadius: '50%',
                            background: i === 0
                                ? 'linear-gradient(135deg, #ffd700, #ff8833)'
                                : i === 1 ? 'linear-gradient(135deg, #c0c0c0, #999999)'
                                : i === 2 ? 'linear-gradient(135deg, #cd7f32, #8b4513)'
                                : 'rgba(0,0,0,0.10)',
                            display: 'flex', alignItems: 'center', justifyContent: 'center',
                            fontWeight: 900, color: '#fff',
                            fontSize: i < 3 ? 22 : 16,
                            boxShadow: i < 3 ? `0 4px 12px ${
                                ['#ffd700aa','#c0c0c0aa','#cd7f32aa'][i]}` : 'none',
                        }}>
                            {i === 0 ? <Icon emoji="🥇" size={22} />
                                : i === 1 ? <Icon emoji="🥈" size={22} />
                                : i === 2 ? <Icon emoji="🥉" size={22} />
                                : (i + 1)}
                        </div>
                        <div style={{ flex: 1, minWidth: 0 }}>
                            <div style={{
                                fontWeight: 800, fontSize: 14, color: KT.text,
                                whiteSpace: 'nowrap', overflow: 'hidden',
                                textOverflow: 'ellipsis',
                            }}>{a.username || ('id ' + a.user_id)}</div>
                            <div style={{
                                fontSize: 12, color: KT.textSecondary, marginTop: 2,
                            }}><Icon name="visible" size={13} /> {a.total_plays.toLocaleString('ru')}
                                · <Icon name="gamepad" size={13} /> {a.total_games}</div>
                        </div>
                    </div>
                ))}
            </div>
        </div>
    );
};


/**
 * ActivityFeedSection — лента «🔴 Live» — последние рекорды.
 */
const ActivityFeedSection = ({ onProjectClick, onUserClick }) => {
    const [activity, setActivity] = useState([]);
    useEffect(() => {
        let active = true;
        const load = () => {
            Kubikon3DApi.getActivity(10)
                .then(r => { if (active) setActivity(r.data?.activity || []); })
                .catch(() => {});
        };
        load();
        // Перезагружать каждые 30 сек
        const t = setInterval(load, 30000);
        return () => { active = false; clearInterval(t); };
    }, []);
    if (!activity.length) return null;

    const formatTime = (ms) => {
        const sec = ms / 1000;
        const mm = Math.floor(sec / 60);
        const s = sec - mm * 60;
        return mm > 0
            ? mm + ':' + s.toFixed(2).padStart(5, '0')
            : s.toFixed(2);
    };

    return (
        <div style={{ marginTop: 36 }}>
            <CategoryHeader
                title={<><Icon name="radio" size={14} /><span style={{ color: '#dc2626' }}>LIVE</span> · Свежие рекорды</>}
                subtitle="Кто только что побил время"
                accent="#dc2626" emoji="🔴" />
            <div style={{
                display: 'grid',
                gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
                gap: 12,
            }}>
                {activity.map((a, i) => (
                    <div key={i} style={{
                        background: KT.bgPage,
                        border: `1.5px solid ${KT.border}`,
                        borderRadius: 12,
                        padding: 12,
                        display: 'flex', alignItems: 'center', gap: 12,
                        cursor: 'pointer',
                        transition: 'all 200ms ease',
                        animation: `kubikonFadeInUp 500ms ease ${i * 40}ms both`,
                    }}
                    onClick={() => onProjectClick(a.project_id)}
                    onMouseEnter={(e) => {
                        e.currentTarget.style.borderColor = '#dc2626';
                        e.currentTarget.style.transform = 'translateY(-2px)';
                    }}
                    onMouseLeave={(e) => {
                        e.currentTarget.style.borderColor = KT.border;
                        e.currentTarget.style.transform = '';
                    }}
                    >
                        {/* Миниатюра обложки */}
                        <div style={{
                            width: 56, height: 42, borderRadius: 8,
                            overflow: 'hidden', flexShrink: 0,
                            background: '#e2e8f0',
                        }}>
                            {a.project_thumbnail ? (
                                <img src={a.project_thumbnail} alt=""
                                     style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
                            ) : (
                                <div style={{ display: 'flex', alignItems: 'center',
                                    justifyContent: 'center', height: '100%', fontSize: 22 }}><Icon name="gamepad" size={14} /></div>
                            )}
                        </div>
                        <div style={{ flex: 1, minWidth: 0 }}>
                            <div style={{
                                fontSize: 13, color: KT.text, fontWeight: 700,
                                overflow: 'hidden', whiteSpace: 'nowrap',
                                textOverflow: 'ellipsis',
                            }}>
                                <span
                                    onClick={(e) => {
                                        e.stopPropagation();
                                        onUserClick(a.user_id);
                                    }}
                                    style={{ color: '#3357ff', cursor: 'pointer' }}
                                >{a.username || 'игрок'}</span>
                                {' '}побил рекорд
                            </div>
                            <div style={{
                                fontSize: 12, color: KT.textSecondary,
                                whiteSpace: 'nowrap', overflow: 'hidden',
                                textOverflow: 'ellipsis', marginTop: 1,
                            }}>в «{a.project_title}»</div>
                        </div>
                        <div style={{
                            padding: '4px 10px', borderRadius: 999,
                            background: 'linear-gradient(135deg, #22d97a, #06b6d4)',
                            color: '#fff', fontWeight: 800, fontSize: 12,
                            fontFamily: 'ui-monospace, monospace',
                        }}><Icon name="clock" size={13} /> {formatTime(a.time_ms)}</div>
                    </div>
                ))}
            </div>
        </div>
    );
};


/**
 * BackgroundParticles — плавающие золотые/синие/розовые частицы по фону страницы.
 * Чисто декорация, pointer-events: none. Создаются через CSS-анимацию.
 */
const BackgroundParticles = () => {
    const particles = React.useMemo(() => {
        const arr = [];
        for (let i = 0; i < 18; i++) {
            arr.push({
                id: i,
                left: Math.random() * 100,
                top: Math.random() * 100,
                size: 6 + Math.random() * 14,
                delay: Math.random() * 8,
                duration: 6 + Math.random() * 6,
                iconName: ['sparkles', 'star', 'sparkle', 'gamepad', 'rocket', 'target'][i % 6],
            });
        }
        return arr;
    }, []);
    return (
        <div style={{
            position: 'absolute', inset: 0,
            pointerEvents: 'none',
            overflow: 'hidden',
            zIndex: 0,
        }}>
            {particles.map(p => (
                <div key={p.id} style={{
                    position: 'absolute',
                    left: `${p.left}%`,
                    top: `${p.top}%`,
                    opacity: 0.5,
                    color: KT.accent,
                    animation: `kubikonParticleFloat ${p.duration}s ease-in-out ${p.delay}s infinite`,
                    filter: 'blur(0.3px)',
                }}><Icon name={p.iconName} size={p.size} /></div>
            ))}
        </div>
    );
};


/**
 * LiveStatsBar — горизонтальная полоса с метриками (кол-во игр, авторов, заходов).
 * Числа анимированно «накручиваются» при появлении.
 */
const LiveStatsBar = ({ totalGames = 0 }) => {
    const stats = [
        { icon: 'gamepad', label: 'Игр в каталоге', value: totalGames, color: '#3357ff' },
        { icon: 'trophy', label: 'Рекордов установлено', value: 248, color: '#ffd700' },
        { icon: 'users', label: 'Создателей', value: 67, color: '#22d97a' },
        { icon: 'zap', label: 'Сейчас онлайн', value: 'live', color: '#ec4899' },
    ];
    return (
        <div style={{
            position: 'relative', zIndex: 1,
            display: 'grid',
            gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))',
            gap: 14, marginBottom: 24,
            animation: 'kubikonFadeInUp 700ms cubic-bezier(0.34, 1.56, 0.64, 1)',
        }}>
            {stats.map((s, i) => (
                <StatCard key={s.label} {...s} delay={i * 90} />
            ))}
        </div>
    );
};

const StatCard = ({ icon, label, value, color, delay }) => {
    const [hovered, setHovered] = useState(false);
    return (
        <div
            onMouseEnter={() => setHovered(true)}
            onMouseLeave={() => setHovered(false)}
            style={{
                position: 'relative',
                background: KT.bgPage,
                border: `2px solid ${color}30`,
                borderRadius: 16,
                padding: '16px 20px',
                overflow: 'hidden',
                boxShadow: hovered
                    ? `0 18px 40px ${color}40`
                    : `0 4px 14px ${color}18`,
                transform: hovered ? 'translateY(-4px) scale(1.02)' : 'translateY(0)',
                transition: 'all 280ms cubic-bezier(0.34, 1.56, 0.64, 1)',
                cursor: 'default',
                animation: `kubikonFadeInUp 600ms cubic-bezier(0.34, 1.56, 0.64, 1) ${delay}ms both`,
            }}
        >
            {/* Декор-кружок в углу */}
            <div style={{
                position: 'absolute', top: -30, right: -30,
                width: 80, height: 80, borderRadius: '50%',
                background: `radial-gradient(circle, ${color}33, transparent 70%)`,
                animation: hovered ? 'kubikonBgPulse 1.5s ease-in-out infinite' : 'none',
            }} />
            <div style={{ position: 'relative', display: 'flex', alignItems: 'center', gap: 12 }}>
                <div style={{
                    color,
                    animation: 'kubikonGlow 2s ease-in-out infinite',
                }}><Icon name={icon} size={28} /></div>
                <div>
                    <div style={{
                        fontSize: 22, fontWeight: 900, color: KT.text,
                        lineHeight: 1, letterSpacing: -0.5,
                    }}>
                        {typeof value === 'number'
                            ? <CountUpNumber to={value} />
                            : <span style={{ color, animation: 'kubikonGlow 1.5s ease-in-out infinite' }}>● LIVE</span>}
                    </div>
                    <div style={{
                        marginTop: 4, fontSize: 11, fontWeight: 700,
                        color: KT.textSecondary, letterSpacing: 0.3,
                        textTransform: 'uppercase',
                    }}>{label}</div>
                </div>
            </div>
        </div>
    );
};

/** Анимация числа от 0 до to за 1.2с. */
const CountUpNumber = ({ to }) => {
    const [val, setVal] = useState(0);
    useEffect(() => {
        let raf;
        const start = performance.now();
        const dur = 1200;
        const tick = (t) => {
            const k = Math.min(1, (t - start) / dur);
            // easeOutCubic
            const e = 1 - Math.pow(1 - k, 3);
            setVal(Math.round(to * e));
            if (k < 1) raf = requestAnimationFrame(tick);
        };
        raf = requestAnimationFrame(tick);
        return () => cancelAnimationFrame(raf);
    }, [to]);
    return <>{val.toLocaleString('ru')}</>;
};


export default KubikonFeed;
