feat(tv): zona-like leanback navigation, hidden header in tv mode, left-edge drawer summon without collapse toggle to 0.0.5
This commit is contained in:
parent
6750d7d437
commit
f380fc2add
5 changed files with 237 additions and 124 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "has",
|
||||
"private": true,
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "has"
|
||||
version = "0.0.4"
|
||||
version = "0.0.5"
|
||||
description = "has"
|
||||
authors = ["autli"]
|
||||
edition = "2024"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "has",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"identifier": "com.has.tauri-app",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
|
|
|
|||
|
|
@ -2017,21 +2017,22 @@
|
|||
}
|
||||
|
||||
/* Android TV Leanback Navigation Drawer (Slide-in Overlay) */
|
||||
/* Android TV Leanback Navigation Drawer (Zona / Android TV 10-Foot UI) */
|
||||
.has-cinema-tv-nav-drawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 270px;
|
||||
background: rgba(8, 12, 24, 0.97);
|
||||
width: 280px;
|
||||
background: rgba(8, 12, 24, 0.98);
|
||||
backdrop-filter: blur(28px);
|
||||
border-right: 1px solid rgba(129, 140, 248, 0.35);
|
||||
box-shadow: 15px 0 50px rgba(0, 0, 0, 0.9);
|
||||
box-shadow: 20px 0 60px rgba(0, 0, 0, 0.95);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 2000;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.26s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
|
@ -2045,63 +2046,40 @@
|
|||
.has-cinema-tv-nav-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 18px 16px 14px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
justify-content: flex-start;
|
||||
padding: 24px 20px 18px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.has-cinema-tv-nav-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
overflow: hidden;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.has-cinema-tv-nav-logo-badge {
|
||||
background: linear-gradient(135deg, #6366f1, #a855f7);
|
||||
color: #fff;
|
||||
font-weight: 800;
|
||||
font-size: 13px;
|
||||
padding: 4px 8px;
|
||||
font-weight: 900;
|
||||
font-size: 14px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 8px;
|
||||
letter-spacing: 1px;
|
||||
letter-spacing: 1.5px;
|
||||
box-shadow: 0 0 16px rgba(99, 102, 241, 0.5);
|
||||
}
|
||||
|
||||
.has-cinema-tv-nav-logo-text {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
color: #f8fafc;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.has-cinema-tv-nav-toggle-btn {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
border-radius: 8px;
|
||||
color: #cbd5e1;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.has-cinema-tv-nav-toggle-btn:focus,
|
||||
.has-cinema-tv-nav-toggle-btn:hover {
|
||||
background: #6366f1;
|
||||
color: #ffffff;
|
||||
outline: 3px solid #818cf8 !important;
|
||||
box-shadow: 0 0 16px rgba(129, 140, 248, 0.7) !important;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.has-cinema-tv-nav-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 12px 10px;
|
||||
padding: 16px 12px;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
|
@ -2111,12 +2089,12 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 12px 14px;
|
||||
padding: 12px 16px;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 12px;
|
||||
color: #94a3b8;
|
||||
font-size: 15px;
|
||||
font-size: 15.5px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
|
|
@ -2125,18 +2103,19 @@
|
|||
|
||||
.has-cinema-tv-nav-item:hover,
|
||||
.has-cinema-tv-nav-item:focus {
|
||||
background: rgba(99, 102, 241, 0.25) !important;
|
||||
background: rgba(99, 102, 241, 0.3) !important;
|
||||
border-color: #818cf8 !important;
|
||||
color: #ffffff !important;
|
||||
outline: 3px solid #818cf8 !important;
|
||||
box-shadow: 0 0 24px rgba(129, 140, 248, 0.8) !important;
|
||||
transform: scale(1.04) !important;
|
||||
box-shadow: 0 0 24px rgba(129, 140, 248, 0.85) !important;
|
||||
transform: scale(1.03) !important;
|
||||
}
|
||||
|
||||
.has-cinema-tv-nav-item.active {
|
||||
background: linear-gradient(135deg, rgba(99, 102, 241, 0.35), rgba(139, 92, 246, 0.35));
|
||||
background: linear-gradient(135deg, rgba(99, 102, 241, 0.4), rgba(139, 92, 246, 0.4));
|
||||
border-color: rgba(129, 140, 248, 0.6);
|
||||
color: #ffffff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.has-cinema-tv-nav-item.logout {
|
||||
|
|
@ -2168,36 +2147,100 @@
|
|||
}
|
||||
|
||||
.has-cinema-tv-nav-footer {
|
||||
padding: 12px;
|
||||
font-size: 11.5px;
|
||||
padding: 14px 16px;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
text-align: center;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
/* TV Mode Root Overrides (Safe Overscan Area & Compact Proportions) */
|
||||
/* TV Mode Root Overrides (Zona TV & Android TV Experience) */
|
||||
.has-tv-mode {
|
||||
font-size: 14.5px;
|
||||
}
|
||||
|
||||
/* Navigation is completely invisible on cinema screen (summoned by pressing Left from edge) */
|
||||
.has-tv-mode .has-cinema-header {
|
||||
padding: 16px 54px !important;
|
||||
}
|
||||
|
||||
.has-tv-mode .has-cinema-nav-links {
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.has-tv-mode .has-cinema-nav-item {
|
||||
font-size: 15px;
|
||||
padding: 6px 10px;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.has-tv-mode .has-cinema-hero {
|
||||
padding: 85px 54px 30px !important;
|
||||
padding: 40px 54px 30px !important;
|
||||
min-height: 360px;
|
||||
}
|
||||
|
||||
/* Dedicated TV Search Section when activeTab === "search" */
|
||||
.has-cinema-tv-search-page {
|
||||
padding: 40px 54px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.has-cinema-tv-search-box-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
background: rgba(15, 23, 42, 0.85);
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
border-radius: 16px;
|
||||
padding: 14px 22px;
|
||||
backdrop-filter: blur(16px);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6);
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.has-cinema-tv-search-box-wrap:focus-within {
|
||||
border-color: #818cf8;
|
||||
box-shadow: 0 0 25px rgba(129, 140, 248, 0.6);
|
||||
}
|
||||
|
||||
.has-cinema-tv-search-lens {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.has-cinema-tv-search-input-field {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: #ffffff;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.has-cinema-tv-search-input-field::placeholder {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.has-cinema-tv-search-clear-btn {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: none;
|
||||
color: #94a3b8;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.has-cinema-tv-search-clear-btn:hover,
|
||||
.has-cinema-tv-search-clear-btn:focus {
|
||||
background: #ef4444;
|
||||
color: #ffffff;
|
||||
outline: 2px solid #ffffff;
|
||||
}
|
||||
|
||||
.has-cinema-tv-search-hint {
|
||||
font-size: 13.5px;
|
||||
color: #94a3b8;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
.has-tv-mode .has-cinema-hero-title {
|
||||
font-size: 30px;
|
||||
margin-bottom: 8px;
|
||||
|
|
|
|||
|
|
@ -594,7 +594,7 @@ export default function Cinema({
|
|||
const [, setConfig] = useState<CinemaConfig>({ tmdb_api_key: null });
|
||||
|
||||
// Navigation Filter
|
||||
const [activeTab, setActiveTab] = useState<"home" | "movies" | "series" | "anime" | "bookmarks">("home");
|
||||
const [activeTab, setActiveTab] = useState<"home" | "movies" | "series" | "anime" | "bookmarks" | "search">("home");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [searchResults, setSearchResults] = useState<CinemaMediaItem[]>([]);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
|
|
@ -1764,7 +1764,19 @@ export default function Cinema({
|
|||
if (isTvNavOpen) {
|
||||
e.preventDefault();
|
||||
setIsTvNavOpen(false);
|
||||
focusFirstContentCard();
|
||||
setTimeout(() => {
|
||||
if (lastFocusedCardRef.current && document.contains(lastFocusedCardRef.current)) {
|
||||
lastFocusedCardRef.current.focus();
|
||||
lastFocusedCardRef.current.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center",
|
||||
inline: "center",
|
||||
});
|
||||
playTvBeep(640, 0.03);
|
||||
} else {
|
||||
focusFirstContentCard();
|
||||
}
|
||||
}, 40);
|
||||
return;
|
||||
}
|
||||
if (showTrailerModal) {
|
||||
|
|
@ -1807,6 +1819,22 @@ export default function Cinema({
|
|||
focusFirstContentCard();
|
||||
return;
|
||||
}
|
||||
// At home root on TV, Back button summons the navigation drawer
|
||||
if (isTvMode && !isTvNavOpen) {
|
||||
e.preventDefault();
|
||||
lastFocusedCardRef.current = document.activeElement as HTMLElement | null;
|
||||
setIsTvNavOpen(true);
|
||||
setTimeout(() => {
|
||||
const targetNav =
|
||||
document.querySelector<HTMLElement>(".has-cinema-tv-nav-item.active") ||
|
||||
document.querySelector<HTMLElement>(".has-cinema-tv-nav-item");
|
||||
if (targetNav) {
|
||||
targetNav.focus();
|
||||
playTvBeep(640, 0.03);
|
||||
}
|
||||
}, 40);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. D-Pad Directional Arrows Navigation
|
||||
|
|
@ -1853,14 +1881,26 @@ export default function Cinema({
|
|||
if (direction === "right") {
|
||||
e.preventDefault();
|
||||
setIsTvNavOpen(false);
|
||||
focusFirstContentCard();
|
||||
setTimeout(() => {
|
||||
if (lastFocusedCardRef.current && document.contains(lastFocusedCardRef.current)) {
|
||||
lastFocusedCardRef.current.focus();
|
||||
lastFocusedCardRef.current.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center",
|
||||
inline: "center",
|
||||
});
|
||||
playTvBeep(640, 0.03);
|
||||
} else {
|
||||
focusFirstContentCard();
|
||||
}
|
||||
}, 40);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let focusables = Array.from(
|
||||
document.querySelectorAll<HTMLElement>(
|
||||
'[data-tv-focusable="true"], .has-cinema-nav-item, .has-cinema-tv-nav-item, .has-cinema-tv-menu-pill, .has-cinema-btn-play, .has-cinema-btn-bookmark, .has-cinema-search-input, .has-cinema-media-card, .has-cinema-grid-card, .has-cinema-episode-card, .has-cinema-player-hud-btn, .has-cinema-player-btn-back, .has-cinema-drawer-option-btn, .has-cinema-drawer-chip, .has-cinema-drawer-ep-btn, .has-cinema-drawer-close-btn, .has-cinema-drawer-btn-primary, .has-cinema-drawer-btn-secondary, .has-cinema-player-pill-nav, .has-cinema-season-tab, .has-cinema-details-btn-play, .has-cinema-details-close-btn, .has-cinema-details-btn-bookmark, .has-cinema-details-btn-trailer, .has-cinema-actor-card, .has-cinema-btn-info-wide'
|
||||
'[data-tv-focusable="true"], .has-cinema-nav-item, .has-cinema-tv-nav-item, .has-cinema-btn-play, .has-cinema-btn-bookmark, .has-cinema-media-card, .has-cinema-grid-card, .has-cinema-episode-card, .has-cinema-player-hud-btn, .has-cinema-player-btn-back, .has-cinema-drawer-option-btn, .has-cinema-drawer-chip, .has-cinema-drawer-ep-btn, .has-cinema-drawer-close-btn, .has-cinema-drawer-btn-primary, .has-cinema-drawer-btn-secondary, .has-cinema-player-pill-nav, .has-cinema-season-tab, .has-cinema-details-btn-play, .has-cinema-details-close-btn, .has-cinema-details-btn-bookmark, .has-cinema-details-btn-trailer, .has-cinema-actor-card, .has-cinema-btn-info-wide'
|
||||
)
|
||||
).filter((el) => {
|
||||
const r = el.getBoundingClientRect();
|
||||
|
|
@ -1873,6 +1913,22 @@ export default function Cinema({
|
|||
);
|
||||
});
|
||||
|
||||
// TV navigation drawer focus isolation
|
||||
if (isTvNavOpen) {
|
||||
const drawerEl = document.querySelector<HTMLElement>(".has-cinema-tv-nav-drawer");
|
||||
if (drawerEl) {
|
||||
focusables = focusables.filter((el) => drawerEl.contains(el));
|
||||
}
|
||||
} else {
|
||||
// When drawer is closed, NEVER include drawer items in focusable pool
|
||||
focusables = focusables.filter((el) => !el.closest(".has-cinema-tv-nav-drawer"));
|
||||
}
|
||||
|
||||
// In TV mode, top header is hidden, so never include header elements
|
||||
if (isTvMode) {
|
||||
focusables = focusables.filter((el) => !el.closest(".has-cinema-header"));
|
||||
}
|
||||
|
||||
// Trap focus inside modal or drawer if open
|
||||
if (settingsSheet) {
|
||||
focusables = focusables.filter((el) => settingsSheet.contains(el));
|
||||
|
|
@ -1952,10 +2008,11 @@ export default function Cinema({
|
|||
playTvBeep(640, 0.025);
|
||||
} else {
|
||||
// If no element found in this direction outside modals:
|
||||
// Trigger the TV Navigation Drawer on Left (from leftmost card) or Right (from rightmost edge)
|
||||
// When moving LEFT from the leftmost card, summon the TV Navigation Drawer!
|
||||
if (!detailsEl && !playerEl && !trailerEl && !settingsSheet && !episodesSheet) {
|
||||
if (direction === "left" || direction === "right") {
|
||||
if (direction === "left") {
|
||||
e.preventDefault();
|
||||
lastFocusedCardRef.current = current;
|
||||
setIsTvNavOpen(true);
|
||||
setTimeout(() => {
|
||||
const targetNav =
|
||||
|
|
@ -2276,46 +2333,38 @@ export default function Cinema({
|
|||
)}
|
||||
|
||||
{/* ========================================================
|
||||
ANDROID TV LEANBACK NAVIGATION DRAWER
|
||||
(Opens when pressing Left from cards, remote Menu, or Menu pill)
|
||||
ANDROID TV LEANBACK NAVIGATION DRAWER (Zona TV Experience)
|
||||
(Summoned only by moving Left from the leftmost card)
|
||||
======================================================== */}
|
||||
{isTvMode && (
|
||||
<aside
|
||||
className={`has-cinema-tv-nav-drawer ${isTvNavOpen ? "open" : "collapsed"}`}
|
||||
className={`has-cinema-tv-nav-drawer ${isTvNavOpen ? "open" : ""}`}
|
||||
aria-label="Навигация для ТВ"
|
||||
>
|
||||
<div className="has-cinema-tv-nav-header">
|
||||
<div className="has-cinema-tv-nav-logo">
|
||||
<span className="has-cinema-tv-nav-logo-badge">HAS</span>
|
||||
{isTvNavOpen && <span className="has-cinema-tv-nav-logo-text">Кинотеатр</span>}
|
||||
<span className="has-cinema-tv-nav-logo-text">Кинотеатр</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="has-cinema-tv-nav-toggle-btn"
|
||||
onClick={() => setIsTvNavOpen((prev) => !prev)}
|
||||
title={isTvNavOpen ? "Свернуть меню (Вправо)" : "Развернуть меню"}
|
||||
data-tv-focusable="true"
|
||||
>
|
||||
{isTvNavOpen ? "◀" : "▶"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="has-cinema-tv-nav-list">
|
||||
<button
|
||||
type="button"
|
||||
className={`has-cinema-tv-nav-item ${searchQuery ? "active" : ""}`}
|
||||
className={`has-cinema-tv-nav-item ${activeTab === "search" || searchQuery ? "active" : ""}`}
|
||||
onClick={() => {
|
||||
setActiveTab("search");
|
||||
setIsTvNavOpen(false);
|
||||
const searchInput = document.querySelector<HTMLInputElement>(".has-cinema-search-input");
|
||||
if (searchInput) {
|
||||
searchInput.focus();
|
||||
}
|
||||
setTimeout(() => {
|
||||
const s = document.querySelector<HTMLInputElement>(".has-cinema-tv-search-input-field") || document.querySelector<HTMLInputElement>(".has-cinema-search-input");
|
||||
if (s) s.focus();
|
||||
}, 60);
|
||||
}}
|
||||
data-tv-focusable="true"
|
||||
title="Поиск"
|
||||
>
|
||||
<span className="has-cinema-tv-nav-icon">🔍</span>
|
||||
{isTvNavOpen && <span className="has-cinema-tv-nav-label">Поиск</span>}
|
||||
<span className="has-cinema-tv-nav-label">Поиск</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
|
@ -2325,13 +2374,13 @@ export default function Cinema({
|
|||
setActiveTab("home");
|
||||
setSearchQuery("");
|
||||
setIsTvNavOpen(false);
|
||||
focusFirstContentCard();
|
||||
setTimeout(() => focusFirstContentCard(), 60);
|
||||
}}
|
||||
data-tv-focusable="true"
|
||||
title="Каталог"
|
||||
>
|
||||
<span className="has-cinema-tv-nav-icon">🎬</span>
|
||||
{isTvNavOpen && <span className="has-cinema-tv-nav-label">Каталог</span>}
|
||||
<span className="has-cinema-tv-nav-icon">🏠</span>
|
||||
<span className="has-cinema-tv-nav-label">Каталог</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
|
@ -2341,13 +2390,13 @@ export default function Cinema({
|
|||
setActiveTab("movies");
|
||||
setSearchQuery("");
|
||||
setIsTvNavOpen(false);
|
||||
focusFirstContentCard();
|
||||
setTimeout(() => focusFirstContentCard(), 60);
|
||||
}}
|
||||
data-tv-focusable="true"
|
||||
title="Фильмы"
|
||||
>
|
||||
<span className="has-cinema-tv-nav-icon">🍿</span>
|
||||
{isTvNavOpen && <span className="has-cinema-tv-nav-label">Фильмы</span>}
|
||||
<span className="has-cinema-tv-nav-icon">🎬</span>
|
||||
<span className="has-cinema-tv-nav-label">Фильмы</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
|
@ -2357,13 +2406,13 @@ export default function Cinema({
|
|||
setActiveTab("series");
|
||||
setSearchQuery("");
|
||||
setIsTvNavOpen(false);
|
||||
focusFirstContentCard();
|
||||
setTimeout(() => focusFirstContentCard(), 60);
|
||||
}}
|
||||
data-tv-focusable="true"
|
||||
title="Сериалы"
|
||||
>
|
||||
<span className="has-cinema-tv-nav-icon">📺</span>
|
||||
{isTvNavOpen && <span className="has-cinema-tv-nav-label">Сериалы</span>}
|
||||
<span className="has-cinema-tv-nav-label">Сериалы</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
|
@ -2373,13 +2422,13 @@ export default function Cinema({
|
|||
setActiveTab("anime");
|
||||
setSearchQuery("");
|
||||
setIsTvNavOpen(false);
|
||||
focusFirstContentCard();
|
||||
setTimeout(() => focusFirstContentCard(), 60);
|
||||
}}
|
||||
data-tv-focusable="true"
|
||||
title="Аниме"
|
||||
>
|
||||
<span className="has-cinema-tv-nav-icon">⛩️</span>
|
||||
{isTvNavOpen && <span className="has-cinema-tv-nav-label">Аниме</span>}
|
||||
<span className="has-cinema-tv-nav-icon">🌸</span>
|
||||
<span className="has-cinema-tv-nav-label">Аниме</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
|
@ -2389,17 +2438,15 @@ export default function Cinema({
|
|||
setActiveTab("bookmarks");
|
||||
setSearchQuery("");
|
||||
setIsTvNavOpen(false);
|
||||
focusFirstContentCard();
|
||||
setTimeout(() => focusFirstContentCard(), 60);
|
||||
}}
|
||||
data-tv-focusable="true"
|
||||
title="Мой список"
|
||||
>
|
||||
<span className="has-cinema-tv-nav-icon">⭐</span>
|
||||
{isTvNavOpen && (
|
||||
<span className="has-cinema-tv-nav-label">
|
||||
Мой список {cinemaState.bookmarks.length > 0 && `(${cinemaState.bookmarks.length})`}
|
||||
</span>
|
||||
)}
|
||||
<span className="has-cinema-tv-nav-label">
|
||||
Мой список {cinemaState.bookmarks.length > 0 && `(${cinemaState.bookmarks.length})`}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{onNavigateTab && (
|
||||
|
|
@ -2414,7 +2461,7 @@ export default function Cinema({
|
|||
title="Панель сервера"
|
||||
>
|
||||
<span className="has-cinema-tv-nav-icon">🖥️</span>
|
||||
{isTvNavOpen && <span className="has-cinema-tv-nav-label">Панель сервера</span>}
|
||||
<span className="has-cinema-tv-nav-label">Панель сервера</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
|
|
@ -2430,21 +2477,19 @@ export default function Cinema({
|
|||
title="Выйти"
|
||||
>
|
||||
<span className="has-cinema-tv-nav-icon">🚪</span>
|
||||
{isTvNavOpen && <span className="has-cinema-tv-nav-label">Выйти</span>}
|
||||
<span className="has-cinema-tv-nav-label">Выйти</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isTvNavOpen && (
|
||||
<div className="has-cinema-tv-nav-footer">
|
||||
<span>▶ Нажмите Вправо для перехода к фильмам</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="has-cinema-tv-nav-footer">
|
||||
<span>▶ Вправо или Назад — закрыть меню</span>
|
||||
</div>
|
||||
</aside>
|
||||
)}
|
||||
|
||||
{/* ========================================================
|
||||
1. TOP NAVIGATION BAR (All categories & search)
|
||||
1. TOP NAVIGATION BAR (For Desktop / Mobile only; hidden in TV Mode)
|
||||
======================================================== */}
|
||||
<header className="has-cinema-header">
|
||||
<nav className="has-cinema-nav-links">
|
||||
|
|
@ -2506,17 +2551,6 @@ export default function Cinema({
|
|||
</nav>
|
||||
|
||||
<div className="has-cinema-header-right">
|
||||
{isTvMode && (
|
||||
<button
|
||||
type="button"
|
||||
className={`has-cinema-tv-menu-pill ${isTvNavOpen ? "active" : ""}`}
|
||||
onClick={() => setIsTvNavOpen((prev) => !prev)}
|
||||
title="Меню навигации (Пульт: ◀ или Влево)"
|
||||
data-tv-focusable="true"
|
||||
>
|
||||
☰ Разделы
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Universal Search Box */}
|
||||
<div className="has-cinema-search-wrapper">
|
||||
|
|
@ -2560,6 +2594,42 @@ export default function Cinema({
|
|||
</div>
|
||||
</header>
|
||||
|
||||
{/* ========================================================
|
||||
TV SEARCH VIEW (When activeTab === "search" in TV Mode)
|
||||
======================================================== */}
|
||||
{isTvMode && activeTab === "search" && (
|
||||
<section className="has-cinema-tv-search-page">
|
||||
<div className="has-cinema-tv-search-box-wrap">
|
||||
<span className="has-cinema-tv-search-lens">🔍</span>
|
||||
<input
|
||||
type="text"
|
||||
className="has-cinema-tv-search-input-field"
|
||||
placeholder="Введите название фильма, сериала или аниме..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
data-tv-focusable="true"
|
||||
autoFocus
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
type="button"
|
||||
className="has-cinema-tv-search-clear-btn"
|
||||
onClick={() => setSearchQuery("")}
|
||||
data-tv-focusable="true"
|
||||
title="Очистить"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{searchQuery.trim().length === 0 && (
|
||||
<div className="has-cinema-tv-search-hint">
|
||||
<span>💡 Введите название с клавиатуры или пульта. Нажмите Влево с края для вызова меню.</span>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* ========================================================
|
||||
SEARCH RESULTS FULL GRID VIEW (When Searching)
|
||||
======================================================== */}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue