Improve Home Screen configuration and add functionality to hide the mobile Category Home rail on scroll

This commit is contained in:
HighImKevin committed 2026-09-25 05:09:33 -04:00
1 parent f8bce09895
commit 281514aae3
6 files changed
+413 -211

No files matched your search

+41 -77
View File
@@ -6,10 +6,6 @@
gap: 1rem;
}
.snowverlay {
display: none;
}
.homePage [data-section-id] h2.sectionTitle,
.homePage [data-section-id] .sectionTitleTextButton > .material-icons {
margin-bottom: 0 !important;
@@ -243,24 +239,6 @@ div#discovery-loading-indicator:not(.show)::after {
font-size: 1.5rem !important;
line-height: 1.8rem !important;
}
/* .homeSectionsContainer [data-section-id*="thematic-"] .card[data-type="Folder"] .cardText {
display: none;
}
[data-card-format="Square"] [data-type="Person"] .cardScalable,
[data-card-format="Square"] [data-type="Actor"] .cardScalable,
[data-card-format="Square"] [data-type="Writer"] .cardScalable,
[data-card-format="Square"] [data-type="Director"] .cardScalable {
border-radius: 50% !important;
overflow: hidden;
}
[data-card-format="Square"] [data-type="Actor"] .cardText {
display: flex;
justify-content: center;
} */
.section0:not([data-page]),
.section1:not([data-page]),
.section2:not([data-page]),
@@ -270,33 +248,7 @@ div#discovery-loading-indicator:not(.show)::after {
.section6:not([data-page]),
.section7:not([data-page]),
.section8:not([data-page]),
.section9:not([data-page]),
.homeSectionsContainer:has([data-page], [data-section-id])
> :is(
.section0:not([data-page]),
.section1:not([data-page]),
.section2:not([data-page]),
.section3:not([data-page]),
.section4:not([data-page]),
.section5:not([data-page]),
.section6:not([data-page]),
.section7:not([data-page]),
.section8:not([data-page]),
.section9:not([data-page])
),
.homeSectionsContainer[data-sections-rendered="true"]
> :is(
.section0:not([data-page]),
.section1:not([data-page]),
.section2:not([data-page]),
.section3:not([data-page]),
.section4:not([data-page]),
.section5:not([data-page]),
.section6:not([data-page]),
.section7:not([data-page]),
.section8:not([data-page]),
.section9:not([data-page])
) {
.section9:not([data-page]) {
display: none !important;
}
@@ -318,30 +270,27 @@ div#discovery-loading-indicator:not(.show)::after {
display: none;
}
body:not(:has(#slides-container)) .homeSectionsContainer .section9 + .custom-scroller-container {
margin-top: 1em;
body[data-seasonal-background="true"]:has(.homePage:not(.hide))
#reactRoot:has(.homePage:not(.hide) #homeTab.is-active)
> .backdropContainer
.backdropImage {
background: none !important;
}
/* .pageTabContent:has(.kefinHomeSectionsContainer[data-kefin-home-screen]) .homeSectionsContainer:not([data-kefin-home-screen]) {
display: none !important;
body[data-seasonal-background="true"]:has(.homePage:not(.hide))
#reactRoot:has(.homePage:not(.hide) #homeTab.is-active)
> .backgroundContainer::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0%;
background: linear-gradient(rgba(0, 0, 0, 0.65), rgba(0, 0, 0, 0.85)), var(--seasonal-background-image);
background-position: center;
background-size: cover;
}
body[data-seasonal-background="true"]:has(.homePage:not(.hide)) #reactRoot:has(.libraryPage:not(.hide) .pageTabContent.is-active[data-index="0"])>.backgroundContainer::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0%;
background: linear-gradient(rgba(0, 0, 0, 0.65), rgba(0, 0, 0, 0.85)), var(--seasonal-background-image);
background-position: center;
background-size: cover;
}
body[data-seasonal-animations="true"]:has(.homePage:not(.hide)) .snowverlay {
display: block;
} */
.homePage[data-discovery-ready] div#discovery-loading-indicator:not(.show) {
min-height: 50px;
}
@@ -402,10 +351,18 @@ body[data-seasonal-animations="true"]:has(.homePage:not(.hide)) .snowverlay {
.kefin-home-chrome:focus-within {
opacity: 1;
}
.kefin-home-chrome[hidden],
#kefin-home-category-rail[hidden] {
.kefin-home-chrome.hide,
#kefin-home-category-rail.hide,
#kefin-home-category-rail.hide .kefin-home-category-rail {
display: none !important;
}
/* Hide home chrome off-home via root page key (no :has; JMP-safe) */
html:not([data-active-page="home"]) #kefin-home-category-rail,
html:not([data-active-page="home"]) #kefin-home-create-section-btn {
display: none !important;
}
.kefin-home-chrome button,
#kefin-home-category-rail button {
pointer-events: auto;
@@ -477,6 +434,14 @@ body[data-seasonal-animations="true"]:has(.homePage:not(.hide)) .snowverlay {
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
border: none !important;
transition:
transform 0.25s ease,
opacity 0.25s ease;
}
#kefin-home-category-rail.is-scroll-hidden .kefin-home-category-rail {
transform: translateY(100%);
pointer-events: none;
opacity: 0;
}
.kefin-home-create-section,
#kefin-home-create-section-btn {
@@ -491,11 +456,6 @@ body[data-seasonal-animations="true"]:has(.homePage:not(.hide)) .snowverlay {
margin-left: auto !important;
}
body:not(:has(.homePage:not(.hide) #homeTab.is-active)) div#kefin-home-category-rail,
body:not(:has(.homePage:not(.hide) #homeTab.is-active)) div#kefin-home-create-section-btn {
display: none;
}
@media (min-width: 900px) {
#kefin-home-category-rail .kefin-home-category-rail,
#kefin-home-category-rail .kefin-home-category-rail::before {
@@ -531,8 +491,12 @@ body:not(:has(#slides-container)):has(.MuiBox-root) .homeSectionsContainer:has([
height: auto !important;
}
body:has(.kefin-home-category-btn.is-active[data-category-id="discovery"]) .sections.homeSectionsContainer::before {
.sections.homeSectionsContainer[data-category="discovery"]::before {
content: "Discover Something To Watch";
padding-left: max(env(safe-area-inset-left), 3.3%);
font-size: 1.5rem;
}
html:not([data-active-page="home"]) .snowverlay {
display: none;
}
+91 -94
View File
@@ -91,11 +91,6 @@
}
state.getDisplayPrefernces = fetchDisplayPreferences;
const performanceTimer = {
loadTimeStart: null,
loadTimeEnd: null,
};
// Cache used to support re-rendering our sections after Jellyfin overwrites the container
const sectionCache = {
renderedSections: [], // Array of section configs + results
@@ -105,14 +100,6 @@
pendingJellyfinSectionObservers: []
};
// Basic performance metrics for measuring initial and recovery renders
const performanceMetrics = {
initialRenderStart: null,
initialRenderEnd: null,
jellyfinDetected: null,
recoveryRenderEnd: null
};
// Create loading indicator element (always last child of the sections container)
function createDiscoveryLoadingIndicator(container) {
// Prefer the provided container, but fall back to the standard sections container
@@ -420,7 +407,10 @@
document.body.dataset.seasonalAnimations = 'true';
// Add the animation script to the head
const animation = seasonalThemes[activeSeasonalThemeGroup.id]?.animation;
const theme = seasonalThemes[activeSeasonalThemeGroup.id] || {};
const animation = theme.animation;
document.body.dataset.seasonalShape = theme.shape || 'snowflake';
document.body.dataset.seasonalAnimationStyle = theme.animationStyle || 'fall';
if (animation) {
const animationScript = document.querySelector(`script[src*="${animation}.js"]`);
if (!animationScript) {
@@ -439,26 +429,16 @@
*/
async function enhanceHomeScreen() {
LOG('Initializing Home Screen v3...');
performanceTimer.loadTimeStart = performance.now();
performanceMetrics.initialRenderStart = performance.now();
mountCreateSectionButton();
// Defer mountCategoryRail until pinned availability is known (avoids first-load tab jump)
syncHomeScreenChromeVisibility();
// Reset cache/metrics for a new initialization cycle
// Reset cache for a new initialization cycle
sectionCache.renderedSections = [];
sectionCache.fragmentCache = null;
sectionCache.isInitialRender = true;
sectionCache.jellyfinHasRendered = false;
performanceMetrics.initialRenderEnd = null;
performanceMetrics.jellyfinDetected = null;
performanceMetrics.recoveryRenderEnd = null;
// Run migration if needed (check for legacy homeScreen config)
if (window.migrateHomeScreenConfig) {
await window.migrateHomeScreenConfig();
}
const container = document.querySelector(HOME_SECTIONS_SELECTOR);
if (!container) {
@@ -501,18 +481,10 @@
return;
}
let performanceStartTime = performance.now();
// Fetch display preferences
//state.userDisplayPreferences = await fetchDisplayPreferences();
let performanceEndTime = performance.now();
let performanceDuration = performanceEndTime - performanceStartTime;
LOG(`Home Screen v3 Fetch display preferences initialization time: ${performanceDuration.toFixed(2)}ms`);
// Load user preferences and apply filtering/ordering
performanceStartTime = performance.now();
const userHomeConfig = window.KefinUserHomeScreenConfig;
let filteredHomeSections;
let homeScreen;
@@ -546,10 +518,6 @@
mountCategoryRail();
syncHomeScreenChromeVisibility();
performanceEndTime = performance.now();
performanceDuration = performanceEndTime - performanceStartTime;
LOG(`Home Screen v3 Load user home screen sections initialization time: ${performanceDuration.toFixed(2)}ms`);
const nextupSectionConfig = filteredHomeSections.find(s => s.id === 'nextUp');
const continueWatchingSectionConfig = filteredHomeSections.find(s => s.id === 'continueWatching');
const continueWatchingAndNextUpSectionConfig = filteredHomeSections.find(s => s.id === 'continueWatchingAndNextUp');
@@ -589,11 +557,6 @@
observeSectionUntilChildrenThenClone(sectionEl, kefinHome);
}
});
performanceMetrics.recoveryRenderEnd = performance.now();
if (performanceMetrics.jellyfinDetected) {
LOG(`Home Screen v3 Jellyfin section mirroring duration: ${(performanceMetrics.recoveryRenderEnd - performanceMetrics.jellyfinDetected).toFixed(2)}ms`);
}
}; */
//let containerForRender = container;
@@ -607,18 +570,9 @@
//containerForRender.dataset.kefinHomeScreen = 'true';
// Initial render directly into Kefin container (no waiting for Jellyfin)
performanceStartTime = performance.now();
LOG('Rendering Standard/Seasonal Sections (initial render)...');
await renderHomeSections(filteredHomeSections, container, false);
performanceEndTime = performance.now();
performanceDuration = performanceEndTime - performanceStartTime;
LOG(`Home Screen v3 Render home sections (initial) initialization time: ${performanceDuration.toFixed(2)}ms`);
performanceMetrics.initialRenderEnd = performanceEndTime;
performanceTimer.loadTimeEnd = performance.now();
LOG(`Home Screen v3 load time initialization: ${(performanceTimer.loadTimeEnd - performanceTimer.loadTimeStart).toFixed(2)}ms`);
LOG('Initializing Discovery Sections...');
setupDiscoveryInteraction(container);
@@ -653,13 +607,7 @@
* Now reads from all 4 groups: HOME, SEASONAL, DISCOVERY, CUSTOM
*/
async function mergeHomeSectionConfigs(defaultSections) {
const performanceStartTime = performance.now();
const kefinSections = await window.KefinHomeScreen.getSections();
const performanceEndTime = performance.now();
const performanceDuration = performanceEndTime - performanceStartTime;
LOG(`Home Screen v3 Get kefin home screen config initialization time: ${performanceDuration.toFixed(2)}ms`);
return kefinSections.enabledHomeSections;
// Return all enabled sections from HOME_SECTION_GROUPS
@@ -1195,14 +1143,7 @@
const sectionPromises = [];
let performanceStartTime = performance.now();
let performanceEndTime;
let performanceDuration;
const performanceTimes = [];
for (const sectionConfig of sortedSections) {
const loopStartTime = performance.now();
if (sectionConfig.enabled === false) {
LOG(`Skipping disabled section: ${sectionConfig.id}`);
continue;
@@ -1223,30 +1164,13 @@
}
sectionPromises.push(loadSectionForRendering(sectionConfig));
const loopEndTime = performance.now();
const loopDuration = loopEndTime - loopStartTime;
performanceTimes.push(loopDuration);
LOG(`Section ${sectionConfig.id} initialization time: ${loopDuration.toFixed(2)}ms`);
}
performanceEndTime = performance.now();
performanceDuration = performanceEndTime - performanceStartTime;
LOG(`Render home sections configs loaded initialization time: ${performanceDuration.toFixed(2)}ms`);
LOG(`Total performance time: ${performanceTimes.reduce((a, b) => a + b, 0).toFixed(2)}ms`);
performanceStartTime = performance.now();
// Resolve all section promises
let sectionResults = await Promise.all(sectionPromises);
sectionResults = sectionResults.filter(result => result !== null);
sectionsToRender.push(...sectionResults);
performanceEndTime = performance.now();
performanceDuration = performanceEndTime - performanceStartTime;
LOG(`Home Screen v3 Resolve all section promises initialization time: ${performanceDuration.toFixed(2)}ms`);
// Cache the rendered sections for potential re-render after Jellyfin overwrites the container
sectionCache.renderedSections = [...sectionsToRender];
}
@@ -1263,7 +1187,6 @@
}
if (!state.isRenderingHome && !kefinSections) {
const renderStartTime = performance.now();
state.isRenderingHome = true;
await window.cardBuilder.renderProgressiveSections(targetContainer, sectionsToRender, {
@@ -1274,10 +1197,6 @@
state.isRenderingHome = false;
const renderEndTime = performance.now();
const renderDuration = renderEndTime - renderStartTime;
LOG(`Home Screen v3 Render progressive sections initialization time: ${renderDuration.toFixed(2)}ms`);
// If MediaBar plugin is in use, call LayoutSync.update() to update the layout
if (typeof LayoutSync !== 'undefined' && LayoutSync && typeof LayoutSync.update === 'function') {
LayoutSync.update();
@@ -1374,7 +1293,7 @@
const isDynamic = !!(sectionConfig.discoveryType || sectionConfig.source === 'Dynamic');
if (isDynamic) {
// Sync stub + lazy resolve; do not await ensureData here
return window.sectionHelper.buildDiscoverySectionPromise(sectionConfig, {
return await window.sectionHelper.buildDiscoverySectionPromise(sectionConfig, {
order: sectionConfig.order,
pairSpotlight: true
});
@@ -1869,6 +1788,70 @@
let homeChromeCategoryMounted = false;
let homeChromeHasPinnedContent = false;
let homeChromeVisibilityObserver = null;
let homeChromeRailScrollBound = false;
let homeChromeRailLastY = 0;
const HOME_CHROME_RAIL_SCROLL_DELTA = 8;
const HOME_CHROME_RAIL_MOBILE_MQ = '(max-width: 899px)';
function isHomeCategoryRailMobile() {
return typeof window.matchMedia === 'function'
? window.matchMedia(HOME_CHROME_RAIL_MOBILE_MQ).matches
: window.innerWidth < 900;
}
function setHomeCategoryRailScrollHidden(hidden) {
const rail = document.getElementById(HOME_CHROME_CATEGORY_RAIL_ID);
if (!rail) return;
rail.classList.toggle('is-scroll-hidden', !!hidden);
}
function clearHomeCategoryRailScrollHidden() {
setHomeCategoryRailScrollHidden(false);
}
function handleHomeCategoryRailScroll() {
const rail = document.getElementById(HOME_CHROME_CATEGORY_RAIL_ID);
if (!rail || rail.classList.contains('hide') || !isHomeCategoryRailMobile()) {
clearHomeCategoryRailScrollHidden();
return;
}
const y = window.pageYOffset || document.documentElement.scrollTop || 0;
if (y <= HOME_CHROME_RAIL_SCROLL_DELTA) {
clearHomeCategoryRailScrollHidden();
homeChromeRailLastY = y;
return;
}
const delta = y - homeChromeRailLastY;
if (Math.abs(delta) < HOME_CHROME_RAIL_SCROLL_DELTA) return;
if (delta > 0) {
setHomeCategoryRailScrollHidden(true);
} else {
setHomeCategoryRailScrollHidden(false);
}
homeChromeRailLastY = y;
}
function syncHomeCategoryRailScrollListener() {
const rail = document.getElementById(HOME_CHROME_CATEGORY_RAIL_ID);
const showFilters = getHomeSettings().showCategoryFilters !== false;
const shouldListen = !!(rail && !rail.classList.contains('hide') && isHomePageVisible() && showFilters && isHomeCategoryRailMobile());
if (shouldListen) {
if (!homeChromeRailScrollBound) {
homeChromeRailLastY = window.pageYOffset || document.documentElement.scrollTop || 0;
window.addEventListener('scroll', handleHomeCategoryRailScroll, { passive: true });
window.addEventListener('resize', syncHomeCategoryRailScrollListener, { passive: true });
homeChromeRailScrollBound = true;
}
if (!isHomeCategoryRailMobile()) clearHomeCategoryRailScrollHidden();
return;
}
if (homeChromeRailScrollBound) {
window.removeEventListener('scroll', handleHomeCategoryRailScroll);
window.removeEventListener('resize', syncHomeCategoryRailScrollListener);
homeChromeRailScrollBound = false;
}
clearHomeCategoryRailScrollHidden();
}
function getHomeSettings() {
return window.KefinTweaksConfig?.homeScreenConfig?.HOME_SETTINGS
@@ -1915,26 +1898,32 @@
}
function isHomePageVisible() {
return !!document.querySelector('.homePage:not(.hide)');
const active = document.documentElement.getAttribute('data-active-page');
if (active !== null) {
return active === 'home';
}
return !!document.querySelector('.homePage:not(.hide) #homeTab.is-active');
}
function syncHomeScreenChromeVisibility() {
const onHome = isHomePageVisible();
const createBtn = document.getElementById(HOME_CHROME_CREATE_ID);
if (createBtn) {
createBtn.hidden = !onHome;
createBtn.classList.toggle('hide', !onHome);
createBtn.setAttribute('aria-hidden', onHome ? 'false' : 'true');
}
const showFilters = getHomeSettings().showCategoryFilters !== false;
const rail = document.getElementById(HOME_CHROME_CATEGORY_RAIL_ID);
if (rail) {
const visible = onHome && showFilters;
rail.hidden = !visible;
rail.classList.toggle('hide', !visible);
rail.setAttribute('aria-hidden', visible ? 'false' : 'true');
if (!visible) clearHomeCategoryRailScrollHidden();
}
if (onHome) {
applyActiveHomeCategory(homeChromeActiveCategory || 'none');
}
syncHomeCategoryRailScrollListener();
}
function ensureHomeChromeVisibilityObserver() {
@@ -1980,7 +1969,8 @@
const wrap = document.createElement('div');
wrap.id = HOME_CHROME_CREATE_ID;
wrap.className = 'kefin-home-chrome kefin-home-create-section';
wrap.hidden = !isHomePageVisible();
wrap.classList.toggle('hide', !isHomePageVisible());
wrap.setAttribute('aria-hidden', isHomePageVisible() ? 'false' : 'true');
wrap.innerHTML = `
<button type="button" class="paper-icon-button-light emby-button kefin-home-create-section-btn" title="Create Section" aria-label="Create Section">
<span class="material-icons" aria-hidden="true">add_circle</span>
@@ -2085,6 +2075,7 @@
ensureCategoryFilterStyles();
homeChromeActiveCategory = 'none';
applyActiveHomeCategory('none');
syncHomeCategoryRailScrollListener();
return;
}
@@ -2104,10 +2095,10 @@
const outer = document.createElement('div');
outer.id = HOME_CHROME_CATEGORY_RAIL_ID;
outer.className = 'sectionTabs';
outer.hidden = !isHomePageVisible();
outer.classList.toggle('hide', !isHomePageVisible());
outer.setAttribute('role', 'toolbar');
outer.setAttribute('aria-label', 'Home categories');
//outer.setAttribute('aria-hidden', outer.hidden ? 'true' : 'false');
outer.setAttribute('aria-hidden', isHomePageVisible() ? 'false' : 'true');
const inner = document.createElement('div');
inner.className = getCategoryRailNativeClasses();
@@ -2140,6 +2131,7 @@
document.body.appendChild(outer);
homeChromeCategoryMounted = true;
applyActiveHomeCategory(desiredCategory);
syncHomeCategoryRailScrollListener();
}
function getMountedRailCategoryIds() {
@@ -2198,6 +2190,10 @@
//initHomeScreenChrome();
if (window.KefinTweaksUtils && typeof window.KefinTweaksUtils.onViewPage === 'function') {
window.KefinTweaksUtils.onViewPage(() => {
syncHomeScreenChromeVisibility();
}, { pages: [] });
window.KefinTweaksUtils.onViewPage((view, element, hash) => {
// Get selected tab from hash
const hashParams = hash.includes('?') ? hash.split('?')[1] : '';
@@ -2205,8 +2201,9 @@
const currentTab = urlParams.get('tab');
const currentTabIndex = currentTab ? parseInt(currentTab, 10) : 0;
// If the tab isn't 0, don't render the home screen
// If the tab isn't 0, don't render the home screen (still sync chrome)
if (currentTabIndex !== 0) {
syncHomeScreenChromeVisibility();
return;
}