+
+
+
Enable Seasonal Animations
+
Enable falling snow/leaves animations for seasonal themes
+ ${buildJellyfinCheckbox('homeScreen_seasonal_enableSeasonalAnimations', seasonal.enableSeasonalAnimations !== false, 'Enabled')}
+
+
${seasonalSectionsHtml}
@@ -4955,14 +4992,18 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
enabled: document.getElementById('homeScreen_recentlyReleased_enabled')?.checked !== false,
movies: {
enabled: document.getElementById('homeScreen_recentlyReleased_movies_enabled')?.checked !== false,
- ...getSectionConfig('homeScreen_recentlyReleased_movies', { includeName: true, defaultName: 'Recently Released Movies', includeSortOrder: false, includeOrder: true })
- },
- episodes: {
- enabled: document.getElementById('homeScreen_recentlyReleased_episodes_enabled')?.checked !== false,
- ...getSectionConfig('homeScreen_recentlyReleased_episodes', { includeName: true, defaultName: 'Recently Aired Episodes', includeSortOrder: false, includeOrder: true })
- }
- },
- trending: {
+ ...getSectionConfig('homeScreen_recentlyReleased_movies', { includeName: true, defaultName: 'Recently Released Movies', includeSortOrder: false, includeOrder: true }),
+ minPremiereDate: document.getElementById('homeScreen_recentlyReleased_movies_minPremiereDate')?.value || '',
+ maxPremiereDate: document.getElementById('homeScreen_recentlyReleased_movies_maxPremiereDate')?.value || ''
+ },
+ episodes: {
+ enabled: document.getElementById('homeScreen_recentlyReleased_episodes_enabled')?.checked !== false,
+ minPremiereDate: document.getElementById('homeScreen_recentlyReleased_episodes_minPremiereDate')?.value || '',
+ maxPremiereDate: document.getElementById('homeScreen_recentlyReleased_episodes_maxPremiereDate')?.value || '',
+ ...getSectionConfig('homeScreen_recentlyReleased_episodes', { includeName: true, defaultName: 'Recently Aired Episodes', includeSortOrder: false, includeOrder: true })
+ }
+ },
+ trending: {
enabled: document.getElementById('homeScreen_trending_enabled')?.checked === true,
...getSectionConfig('homeScreen_trending', { includeName: true, defaultName: 'Trending' })
},
@@ -4989,6 +5030,7 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
},
seasonal: {
enabled: document.getElementById('homeScreen_seasonal_enabled')?.checked !== false,
+ enableSeasonalAnimations: document.getElementById('homeScreen_seasonal_enableSeasonalAnimations')?.checked !== false,
defaultItemLimit: 16,
defaultSortOrder: 'Random',
defaultCardFormat: 'Poster',
diff --git a/scripts/homeScreen.css b/scripts/homeScreen.css
index e3b2b65..16526a5 100644
--- a/scripts/homeScreen.css
+++ b/scripts/homeScreen.css
@@ -70,6 +70,29 @@
background-size: auto 102%;
}
+/* Christmas theme */
+.home-screen.christmas-theme #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.5), rgba(0, 0, 0, 0.5)), url('../pages/images/christmas-2.jpg');
+ background-position: center;
+ background-size: cover;
+}
+
+.flurry-container {
+ display: none;
+}
+
+.home-screen.christmas-theme .flurry-container {
+ display: block;
+ position: fixed !important;
+ z-index: 9999999999 !important;
+}
+
.home-screen h2.sectionTitle {
margin-bottom: .35em;
margin-top: 0;
diff --git a/scripts/homeScreen.js b/scripts/homeScreen.js
index 0142c79..cea4bce 100644
--- a/scripts/homeScreen.js
+++ b/scripts/homeScreen.js
@@ -138,6 +138,11 @@
const currentView = window.KefinTweaksUtils?.getCurrentView();
const isHomePage = currentView === 'home' || currentView === 'home.html';
+ // Get seasonal animation setting
+ const homeScreenConfig = window.KefinTweaksConfig?.homeScreen || {};
+ const seasonalConfig = homeScreenConfig.seasonal || {};
+ const enableSeasonalAnimations = seasonalConfig.enableSeasonalAnimations !== false;
+
// Manage home-screen class
if (isHomePage && body) {
body.classList.add('home-screen');
@@ -156,6 +161,30 @@
body.classList.add('halloween-theme');
} else if (isChristmasPeriod()) {
body.classList.add('christmas-theme');
+
+ if (enableSeasonalAnimations) {
+ // Import the Flurry module and leverage it from jQuery. /scripts/third party/flurry.js by appending it to the head
+ const flurryScriptSrc = (window.KefinTweaksConfig.kefinTweaksRoot || '') + 'scripts/third party/jquery.flurry.min.js';
+
+ // Function to run flurry safely
+ const startFlurry = () => {
+ if (window.jQuery && typeof window.jQuery.fn.flurry === 'function') {
+ window.jQuery("body").flurry();
+ }
+ };
+
+ // Check if script is already present
+ if (!document.querySelector(`script[src="${flurryScriptSrc}"]`)) {
+ const flurryScript = document.createElement('script');
+ flurryScript.src = flurryScriptSrc;
+ flurryScript.onload = startFlurry; // Run after load
+ document.head.appendChild(flurryScript);
+ } else {
+ // Script loaded, try running directly
+ startFlurry();
+ }
+ }
+
} else if (isValentinesPeriod()) {
body.classList.add('valentines-theme');
} else if (isNewYearsPeriod()) {
@@ -587,6 +616,90 @@
WARN('KefinTweaksUtils.saveConfigToJavaScriptInjector not available, config migration not persisted');
}
}
+
+ // --- NEW UPDATE LOGIC ---
+ // Runs even if no major migration was needed, to catch specific updates
+
+ // 1. Ensure Recently Released date fields exist
+ let configChanged = false;
+ // Use the current config (which might have been just migrated)
+ const currentConfig = window.KefinTweaksConfig.homeScreen;
+
+ if (currentConfig.recentlyReleased) {
+ if (currentConfig.recentlyReleased.movies && currentConfig.recentlyReleased.movies.minPremiereDate === undefined) {
+ currentConfig.recentlyReleased.movies.minPremiereDate = "";
+ currentConfig.recentlyReleased.movies.maxPremiereDate = "";
+ configChanged = true;
+ }
+ if (currentConfig.recentlyReleased.episodes && currentConfig.recentlyReleased.episodes.minPremiereDate === undefined) {
+ currentConfig.recentlyReleased.episodes.minPremiereDate = "";
+ currentConfig.recentlyReleased.episodes.maxPremiereDate = "";
+ configChanged = true;
+ }
+ }
+
+ // 2. Update Christmas Seasonal Sections
+ if (currentConfig.seasonal && Array.isArray(currentConfig.seasonal.seasons)) {
+ const christmasSeason = currentConfig.seasonal.seasons.find(s => s.id === 'christmas');
+ if (christmasSeason && Array.isArray(christmasSeason.sections)) {
+
+ // Check for old "Christmas Movies" (genre) and update to new "Christmas Movies" (tag)
+ const movieSectionIndex = christmasSeason.sections.findIndex(s => s.id === 'christmas-genre');
+ if (movieSectionIndex !== -1) {
+ christmasSeason.sections[movieSectionIndex] = {
+ "id": "seasonal-1-section-0",
+ "enabled": true,
+ "name": "Christmas Movies",
+ "type": "Tag",
+ "source": "christmas",
+ "itemLimit": 16,
+ "sortOrder": "Random",
+ "sortOrderDirection": "Ascending",
+ "cardFormat": "Poster",
+ "order": 50,
+ "spotlight": true,
+ "discoveryEnabled": false,
+ "searchTerm": "",
+ "includeItemTypes": ["Movie"],
+ "additionalQueryOptions": []
+ };
+ configChanged = true;
+ }
+
+ // Check for old "Family Movies" (genre) and update to new "Christmas Episodes" (parent/search)
+ const familySectionIndex = christmasSeason.sections.findIndex(s => s.id === 'christmas-family');
+ if (familySectionIndex !== -1) {
+ christmasSeason.sections[familySectionIndex] = {
+ "id": "seasonal-1-section-1",
+ "enabled": true,
+ "name": "Christmas Episodes",
+ "type": "Parent",
+ "source": "",
+ "itemLimit": 16,
+ "sortOrder": "Random",
+ "sortOrderDirection": "Ascending",
+ "cardFormat": "Thumb",
+ "order": 51,
+ "spotlight": false,
+ "discoveryEnabled": false,
+ "searchTerm": "christmas",
+ "includeItemTypes": ["Episode"],
+ "additionalQueryOptions": []
+ };
+ configChanged = true;
+ }
+ }
+ }
+
+ if (configChanged && !needsMigration) {
+ LOG('Applied specific configuration updates (recently released dates / christmas sections)');
+ // Save updated config back to JS Injector plugin
+ if (window.KefinTweaksUtils && window.KefinTweaksUtils.saveConfigToJavaScriptInjector) {
+ window.KefinTweaksUtils.saveConfigToJavaScriptInjector().catch(err => {
+ ERR('Failed to save updated config to JS Injector:', err);
+ });
+ }
+ }
}
// Run migration before reading config
@@ -644,6 +757,7 @@
// Seasonal configuration
const seasonalConfig = homeScreenConfig.seasonal || {};
const enableSeasonal = seasonalConfig.enabled !== false;
+ const enableSeasonalAnimations = seasonalConfig.enableSeasonalAnimations !== false;
const SEASONAL_ITEM_LIMIT = seasonalConfig.defaultItemLimit || 16;
// Discovery sections configuration
@@ -2445,8 +2559,8 @@
Limit: 16,
Fields: 'PremiereDate',
Recursive: true,
- MinPremiereDate: monthAgo,
- MaxPremiereDate: today
+ MinPremiereDate: newMoviesConfig.minPremiereDate || monthAgo,
+ MaxPremiereDate: newMoviesConfig.maxPremiereDate || today
}
const isPlayedFilter = getIsPlayedFilter(newMoviesConfig);
@@ -2454,6 +2568,11 @@
options.Filters = isPlayedFilter;
}
+ // Add UserData to Fields if IsPlayed filter is active or generally useful
+ if (!options.Fields.includes('UserData')) {
+ options.Fields += ',UserData';
+ }
+
const data = await window.apiHelper.getItems(options, true);
return data.Items || [];
}
@@ -2478,8 +2597,8 @@
Recursive: true,
SortBy: 'PremiereDate',
SortOrder: 'Descending',
- MinPremiereDate: minDate,
- MaxPremiereDate: maxDate,
+ MinPremiereDate: newEpisodesConfig.minPremiereDate || minDate,
+ MaxPremiereDate: newEpisodesConfig.maxPremiereDate || maxDate,
Limit: 100,
Fields: 'PremiereDate,SeriesName,ParentIndexNumber,IndexNumber,ProviderIds,Path'
}
@@ -2489,6 +2608,11 @@
options.Filters = isPlayedFilter;
}
+ // Add UserData to Fields if IsPlayed filter is active or generally useful
+ if (!options.Fields.includes('UserData')) {
+ options.Fields += ',UserData';
+ }
+
const data = await window.apiHelper.getItems(options, true);
const episodes = data.Items || [];
diff --git a/scripts/third party/jquery.flurry.min.js b/scripts/third party/jquery.flurry.min.js
new file mode 100644
index 0000000..c59f386
--- /dev/null
+++ b/scripts/third party/jquery.flurry.min.js
@@ -0,0 +1,16 @@
+/**
+ * Flurry jQuery Plugin
+ *
+ * Flurry is an easy-to-use animated snow plugin for jQuery. It takes advantage
+ * of CSS transforms, CSS transitions and requestAnimationFrame to provide
+ * smooth animation for modern browsers. Props to Jonathan Nicol @f6design
+ * for boilerplate code
+ * (see http://jonathannicol.com/blog/2012/05/06/a-jquery-plugin-boilerplate/)
+ *
+ * @link https://github.com/joshmcrty/Flurry
+ * @version 1.2.0
+ * @author Josh McCarty
+ * @copyright 2016 Josh McCarty
+ * @license https://github.com/joshmcrty/Flurry/blob/master/LICENSE GPLv2
+ */
+!function(n,e){!function(){for(var n=0,e=["ms","moz","webkit","o"],a=0;a");f.html(o).css({color:a.blur&&l<(a.large+a.small)/2?"transparent":d,"text-shadow":a.blur&&l<(a.large+a.small)/2?"0 0 1px "+d:"none",display:"inline-block","line-height":1,margin:0,padding:"2px","pointer-events":"none","font-size":l+"px",opacity:a.startTransparency,position:"absolute",top:"-"+1.2*a.large+"px",transform:"translateX("+s+"px) translateY(0px) rotateZ("+a.startRotation+"deg)",transition:"transform "+u/1e3+"s linear, opacity "+u/1e3+"s "+a.opacityEasing,"z-index":a.zIndex}),a.onFlake!==e&&a.onFlake.call(f[0]),f.appendTo(i),!function(){var n=(document.body||document.documentElement).style;return n.transition!==e||n.WebkitTransition!==e||n.MozTransition!==e||n.MsTransition!==e||n.OTransition!==e}?f.animate(h,u,"linear",function(){n(this).remove()}):(f.on("transitionend.flurry",function(e){n(e.target).remove()}),window.requestAnimationFrame(function(){f.css(h)}))}function r(t,r){var o=this,s=t,c=n(t);function l(n){r[n]!==e&&r[n].call(s)}return r=n.extend({height:c.height()>200?200:c.height(),useRelative:!c.is("body")},n.fn[a].defaults,r),n.each(r,function(n,e){parseInt(e)&&(r[n]=parseInt(e))}),function(){!0===r.useRelative&&"static"===c.css("position")&&c.css({position:"relative"});var e,a,t,s,u=n(document.createElement("div")).addClass("flurry-container").css({margin:0,padding:0,position:"absolute",top:0,right:0,left:0,height:r.height,overflow:r.overflow,"pointer-events":"none"}).prependTo(c),m=u.width();n(window).resize(function(){m=u.width()}),o.flakeInterval=(e=function(){i(r,u,m)},a=r.frequency,t=(new Date).getTime(),(s=new Object).value=window.requestAnimationFrame(function n(){(new Date).getTime()-t>=a&&(e.call(),t=(new Date).getTime()),s.value=window.requestAnimationFrame(n)}),s),l("onInit")}(),{option:function(n,e){if(!e)return r[n];r[n]=parseInt(e)||e},destroy:function(){c.each(function(){var e,t=n(this);e=o.flakeInterval,window.cancelAnimationFrame?window.cancelAnimationFrame(e.value):window.webkitCancelAnimationFrame?window.webkitCancelAnimationFrame(e.value):window.webkitCancelRequestAnimationFrame?window.webkitCancelRequestAnimationFrame(e.value):window.mozCancelRequestAnimationFrame?window.mozCancelRequestAnimationFrame(e.value):window.oCancelRequestAnimationFrame?window.oCancelRequestAnimationFrame(e.value):window.msCancelRequestAnimationFrame?window.msCancelRequestAnimationFrame(e.value):clearInterval(e),t.find(".flurry-container").remove(),l("onDestroy"),t.removeData("plugin_"+a)})}}}n.fn[a]=function(t){if("string"==typeof arguments[0]){var i,o=arguments[0],s=Array.prototype.slice.call(arguments,1);return this.each(function(){if(!n.data(this,"plugin_"+a)||"function"!=typeof n.data(this,"plugin_"+a)[o])throw new Error("Method "+o+" does not exist on jQuery."+a);i=n.data(this,"plugin_"+a)[o].apply(this,s)}),i!==e?i:this}if("object"==typeof t||!t)return this.each(function(){n.data(this,"plugin_"+a)||n.data(this,"plugin_"+a,new r(this,t))})},n.fn[a].defaults={onInit:e,onDestroy:e,onFlake:e,character:["❄"],color:"white",frequency:100,speed:3e3,small:8,large:28,wind:40,windVariance:20,rotation:90,rotationVariance:180,startRotation:0,startOpacity:1,endOpacity:0,opacityEasing:"cubic-bezier(1,.3,.6,.74)",blur:!1,overflow:"hidden",zIndex:9999}}(jQuery);
\ No newline at end of file