diff --git a/README.md b/README.md index e74edc0..e9a977b 100644 --- a/README.md +++ b/README.md @@ -589,6 +589,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file - **[BobHasNoSoul](https://github.com/BobHasNoSoul)**: For the jellyfin-updoot functionality - **[The Jellyfin Community](https://discord.gg/v7P9CAvCKZhttps://discord.gg/v7P9CAvCKZ)**: For sharing knowledge and providing a welcoming environment to ask questions and learn - **[movie-monk-b0t](https://github.com/movie-monk-b0t)**: For the IMDb Top 250 JSON data +- **[joshmcrty](https://github.com/joshmcrty)**: For the [Flurry](https://github.com/joshmcrty/Flurry) jQuery plugin ## Support @@ -608,6 +609,8 @@ This README and the inline comments in kefinTweaks.js and the other scripts is t
### Version History +- **v0.3.5**: Critical bug fix +- **v0.3.4**: Improved handling of Latest/Development branches for KefinTweaks installation. Improved collection caching. - **v0.3.3**: Important fix for a bug preventing the plugin from running on non-admin accounts - **v0.3.2**: Add proper installation and uninstallation support from the Plugins page. New feature: Playlist sorting. - **v0.3.1**: New Home Screen Section feature: Spotlights! Improved Configuration UI. diff --git a/kefinTweaks-default-config.js b/kefinTweaks-default-config.js index 0aef899..904c95b 100644 --- a/kefinTweaks-default-config.js +++ b/kefinTweaks-default-config.js @@ -33,7 +33,9 @@ window.KefinTweaksDefaultConfig = { "sortOrder": "ReleaseDate", "sortOrderDirection": "Descending", "cardFormat": "Poster", - "order": 30 + "order": 30, + "minPremiereDate": "", + "maxPremiereDate": "" }, "episodes": { "name": "Recently Aired Episodes", @@ -42,7 +44,9 @@ window.KefinTweaksDefaultConfig = { "sortOrder": "PremiereDate", "sortOrderDirection": "Descending", "cardFormat": "Backdrop", - "order": 31 + "order": 31, + "minPremiereDate": "", + "maxPremiereDate": "" } }, "trending": { @@ -100,6 +104,7 @@ window.KefinTweaksDefaultConfig = { }, "seasonal": { "enabled": true, + "enableSeasonalAnimations": true, "defaultItemLimit": 16, "defaultSortOrder": "Random", "defaultCardFormat": "Poster", @@ -203,28 +208,42 @@ window.KefinTweaksDefaultConfig = { "endDate": "12-31", "sections": [ { - "id": "christmas-genre", + "id": "seasonal-1-section-0", "enabled": true, "name": "Christmas Movies", - "type": "Genre", - "source": "Christmas", + "type": "Tag", + "source": "christmas", "itemLimit": 16, "sortOrder": "Random", "sortOrderDirection": "Ascending", "cardFormat": "Poster", - "order": 60 + "order": 50, + "spotlight": true, + "discoveryEnabled": false, + "searchTerm": "", + "includeItemTypes": [ + "Movie" + ], + "additionalQueryOptions": [] }, { - "id": "christmas-family", + "id": "seasonal-1-section-1", "enabled": true, - "name": "Family Movies", - "type": "Genre", - "source": "Family", + "name": "Christmas Episodes", + "type": "Parent", + "source": "", "itemLimit": 16, "sortOrder": "Random", "sortOrderDirection": "Ascending", - "cardFormat": "Poster", - "order": 61 + "cardFormat": "Thumb", + "order": 51, + "spotlight": false, + "discoveryEnabled": false, + "searchTerm": "christmas", + "includeItemTypes": [ + "Episode" + ], + "additionalQueryOptions": [] } ] } @@ -354,6 +373,7 @@ window.KefinTweaksDefaultConfig = { } }, "spotlightDiscoveryChance": 0.5, + "renderSpotlightAboveMatching": false, "spotlight": { "autoPlay": true, "interval": 5000, diff --git a/pages/images/christmas.jpg b/pages/images/christmas.jpg new file mode 100644 index 0000000..4c7912e Binary files /dev/null and b/pages/images/christmas.jpg differ diff --git a/scripts/configuration.js b/scripts/configuration.js index a2a4b40..c8bc413 100644 --- a/scripts/configuration.js +++ b/scripts/configuration.js @@ -28,7 +28,26 @@ ]; const SUPPORTED_CUSTOM_SECTION_PARAMS = { + filters: { label: 'Filters', type: 'array', hint: 'IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes' }, + isPlayed: { label: 'Is Played', type: 'boolean' }, + person: { label: 'Person Name', type: 'string' }, + personTypes: { label: 'Person Types', type: 'array', hint: 'Actor, Director, Writer, etc.' }, + years: { label: 'Years', type: 'array', hint: 'Comma-separated years' }, + + // Dates (ISO 8601) + minPremiereDate: { label: 'Min Premiere Date', type: 'date', hint: 'YYYY-MM-DD' }, + maxPremiereDate: { label: 'Max Premiere Date', type: 'date', hint: 'YYYY-MM-DD' }, + //minDateLastSaved: { label: 'Min Date Last Saved', type: 'date', hint: 'YYYY-MM-DD' }, + //minDateLastSavedForUser: { label: 'Min Date Last Saved For User', type: 'date', hint: 'YYYY-MM-DD' }, + + // Numbers + indexNumber: { label: 'Index Number', type: 'number' }, + parentIndexNumber: { label: 'Parent Index Number', type: 'number' }, + minCommunityRating: { label: 'Min Community Rating', type: 'number' }, + minCriticRating: { label: 'Min Critic Rating', type: 'number' }, + // Booleans + isFavorite: { label: 'Is Favorite', type: 'boolean' }, hasThemeSong: { label: 'Has Theme Song', type: 'boolean' }, hasThemeVideo: { label: 'Has Theme Video', type: 'boolean' }, hasSubtitles: { label: 'Has Subtitles', type: 'boolean' }, @@ -39,45 +58,26 @@ is4K: { label: 'Is 4K', type: 'boolean' }, isMissing: { label: 'Is Missing', type: 'boolean' }, isUnaired: { label: 'Is Unaired', type: 'boolean' }, + //is3D: { label: 'Is 3D', type: 'boolean' }, + //isSeries: { label: 'Is Series (Live TV)', type: 'boolean' }, hasOverview: { label: 'Has Overview', type: 'boolean' }, - isFavorite: { label: 'Is Favorite', type: 'boolean' }, - isPlayed: { label: 'Is Played', type: 'boolean' }, hasOfficialRating: { label: 'Has Official Rating', type: 'boolean' }, recursive: { label: 'Recursive', type: 'boolean', default: true }, - is3D: { label: 'Is 3D', type: 'boolean' }, - isSeries: { label: 'Is Series (Live TV)', type: 'boolean' }, // Strings maxOfficialRating: { label: 'Max Official Rating', type: 'string', hint: 'e.g., PG-13, TV-MA' }, minOfficialRating: { label: 'Min Official Rating', type: 'string', hint: 'e.g., PG, TV-14' }, - person: { label: 'Person Name', type: 'string' }, nameStartsWith: { label: 'Name Starts With', type: 'string' }, nameStartsWithOrGreater: { label: 'Name Starts With Or Greater', type: 'string' }, nameLessThan: { label: 'Name Less Than', type: 'string' }, - // Numbers - indexNumber: { label: 'Index Number', type: 'number' }, - parentIndexNumber: { label: 'Parent Index Number', type: 'number' }, - minCommunityRating: { label: 'Min Community Rating', type: 'number' }, - minCriticRating: { label: 'Min Critic Rating', type: 'number' }, - limit: { label: 'Limit', type: 'number' }, - - // Dates (ISO 8601) - minPremiereDate: { label: 'Min Premiere Date', type: 'date', hint: 'YYYY-MM-DD' }, - maxPremiereDate: { label: 'Max Premiere Date', type: 'date', hint: 'YYYY-MM-DD' }, - minDateLastSaved: { label: 'Min Date Last Saved', type: 'date', hint: 'YYYY-MM-DD' }, - minDateLastSavedForUser: { label: 'Min Date Last Saved For User', type: 'date', hint: 'YYYY-MM-DD' }, - // Arrays (Comma-separated strings in UI) locationTypes: { label: 'Location Types', type: 'array', hint: 'FileSystem, Remote, Virtual, Offline' }, excludeLocationTypes: { label: 'Exclude Location Types', type: 'array', hint: 'FileSystem, Remote, Virtual, Offline' }, excludeItemIds: { label: 'Exclude Item IDs', type: 'array', hint: 'Comma-separated GUIDs' }, excludeItemTypes: { label: 'Exclude Item Types', type: 'array', hint: 'Movie, Series, Episode, etc.' }, - filters: { label: 'Filters', type: 'array', hint: 'IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes' }, imageTypes: { label: 'Image Types', type: 'array', hint: 'Primary, Backdrop, Thumb, Logo, etc.' }, officialRatings: { label: 'Official Ratings', type: 'array', hint: 'PG, PG-13, R, etc.' }, - years: { label: 'Years', type: 'array', hint: 'Comma-separated years' }, - personTypes: { label: 'Person Types', type: 'array', hint: 'Actor, Director, Writer, etc.' }, studios: { label: 'Studios', type: 'array', hint: 'Pipe-delimited names' }, artists: { label: 'Artists', type: 'array', hint: 'Pipe-delimited names' }, albums: { label: 'Albums', type: 'array', hint: 'Pipe-delimited names' }, @@ -1041,7 +1041,7 @@ function buildAdditionalOptionsControls(prefix, sectionIndex, sanitizedPrefix) { // Build dropdown options from supported params const dropdownOptions = Object.entries(SUPPORTED_CUSTOM_SECTION_PARAMS) - .sort((a, b) => a[1].label.localeCompare(b[1].label)) + //.sort((a, b) => a[1].label.localeCompare(b[1].label)) .map(([key, meta]) => ``) .join(''); @@ -1667,8 +1667,23 @@
${buildJellyfinCheckbox('homeScreen_recentlyReleased_movies_enabled', (recentlyReleased.movies || {}).enabled !== false, 'Enabled')}
-
+
${createSectionConfiguration('homeScreen_recentlyReleased_movies', recentlyReleased.movies || {}, { includeName: true, defaultName: 'Recently Released Movies', includeSortOrder: false, includeOrder: true, includeIsPlayed: true })} + +
+
+
Min Premiere Date
+ +
Optional. Leave empty for default (30 days ago).
+
+
+
+
+
Max Premiere Date
+ +
Optional. Leave empty for default (today).
+
+
@@ -1684,8 +1699,23 @@
${buildJellyfinCheckbox('homeScreen_recentlyReleased_episodes_enabled', (recentlyReleased.episodes || {}).enabled !== false, 'Enabled')}
-
+
${createSectionConfiguration('homeScreen_recentlyReleased_episodes', recentlyReleased.episodes || {}, { includeName: true, defaultName: 'Recently Aired Episodes', includeSortOrder: false, includeOrder: true, includeIsPlayed: true })} + +
+
+
Min Premiere Date
+ +
Optional. Leave empty for default (7 days ago).
+
+
+
+
+
Max Premiere Date
+ +
Optional. Leave empty for default (today).
+
+
@@ -1783,6 +1813,13 @@
+
+
+
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