Compare commits

...

47 Commits

Author SHA1 Message Date
CodeDevMLH
99ac46a384 Update manifest.json for release v2.0.0.3 [skip ci] 2026-02-28 01:30:55 +00:00
CodeDevMLH
3a2750388b Bump version to 2.0.0.3
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-02-28 02:30:04 +01:00
CodeDevMLH
33e89ec16b Enhance seasonal options in configuration: add new seasons and improve descriptions 2026-02-28 02:29:41 +01:00
CodeDevMLH
9adbe92e7c Update manifest.json for release v2.0.0.2 [skip ci] 2026-02-28 00:56:16 +00:00
CodeDevMLH
103d63f1b1 Bump version to 2.0.0.2 in project and manifest files
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-02-28 01:55:24 +01:00
CodeDevMLH
49bad2e880 updated to latest settings 2026-02-28 01:55:05 +01:00
CodeDevMLH
04616c2ac4 Fix HeartSize property type in PrideOptions to double for improved precision 2026-02-28 01:54:19 +01:00
CodeDevMLH
3b73dd1728 Add comments for birthday image source and modification details [skip ci] 2026-02-28 01:40:16 +01:00
CodeDevMLH
d5df90a6ae Refactor animation delays in seasonal effects for improved performance and consistency [skip ci] 2026-02-28 01:29:49 +01:00
CodeDevMLH
494e475f42 Refactor snowflakes.js and snowflakes.css to streamline snowflake initialization and enhance mobile responsiveness 2026-02-28 01:22:18 +01:00
CodeDevMLH
4703ba48ed Refactor santa.js to improve mobile detection for snowflake count adjustment 2026-02-28 01:21:37 +01:00
CodeDevMLH
93d5686b77 Refactor resurrection.js to remove random symbol functionality and adjust symbol count initialization for mobile responsiveness 2026-02-28 01:21:33 +01:00
CodeDevMLH
71d07aa0f3 Refactor halloween.js and halloween.css to streamline symbol initialization and remove random symbol functionality 2026-02-28 01:20:02 +01:00
CodeDevMLH
c43f031617 Refactor autumn configuration to enhance leaf count settings for mobile and desktop 2026-02-28 01:19:09 +01:00
CodeDevMLH
89ce903e8a Refactor seasonal scripts to enhance mobile detection using matchMedia for responsive behavior 2026-02-28 01:16:01 +01:00
CodeDevMLH
cbf5d73629 Refactor autumn.js and autumn.css to streamline leaf initialization and enhance configuration handling 2026-02-28 01:15:03 +01:00
CodeDevMLH
8be17dae74 Refactor theme options in test-site.html to enhance selection clarity and add new themes [skip ci] 2026-02-28 00:59:36 +01:00
CodeDevMLH
76006dc162 Refactor olympia.js to enhance configuration handling and streamline symbol creation logic 2026-02-28 00:59:22 +01:00
CodeDevMLH
492acb4052 Refactor createMarioDay function to enhance jump logic and coin spawning conditions [skip ci] 2026-02-28 00:58:10 +01:00
CodeDevMLH
68dc9efa4d Refactor oktoberfest.js to enhance configuration handling for symbol counts and durations 2026-02-28 00:52:27 +01:00
CodeDevMLH
7b15ed46c1 Refactor carnival and cherry blossom initialization to streamline object creation and enhance configuration handling 2026-02-28 00:45:33 +01:00
CodeDevMLH
8019ba760f Refactor carnival and cherry blossom options to improve configuration clarity and add mobile settings 2026-02-28 00:45:23 +01:00
CodeDevMLH
9d1a268875 Refactor halloween.js and marioday.js to enhance clarity and maintainability of configuration handling [skip ci] 2026-02-28 00:00:54 +01:00
CodeDevMLH
9e5feafd64 Refactor birthday.js to improve configuration handling and enhance clarity for symbol counts and confetti settings 2026-02-27 23:57:54 +01:00
CodeDevMLH
5283a69bb8 Remove unused Olympic ring asset images to declutter project resources [skip ci] 2026-02-27 23:56:53 +01:00
CodeDevMLH
a4b2d2edd5 Refactor seasonal plugin configurations to improve clarity and consistency
- Updated comments for clarity on enabling/disabling features across various seasonal scripts.
- Changed default value checks to ensure undefined values are handled consistently.
- Enhanced readability by providing descriptive comments for configuration variables.

[skip ci]
2026-02-27 23:56:36 +01:00
CodeDevMLH
d0634e4487 Refactor sports.js to streamline configuration handling, remove unused variables, and enhance code readability 2026-02-27 23:18:07 +01:00
CodeDevMLH
79c4f988f2 Refactor eid.js to define lantern counts for mobile and desktop, ensuring consistent lantern creation across devices 2026-02-27 23:16:10 +01:00
CodeDevMLH
cee1fa6736 Refactor spring.js to adjust object counts for mobile, streamline pollen and creature creation, and update code comments 2026-02-27 23:11:33 +01:00
CodeDevMLH
8510674d58 Refactor space.js to remove unused randomization options, streamline object counts for mobile, and enhance code readability with updated credits 2026-02-27 23:05:52 +01:00
CodeDevMLH
5ee724201b Refactor summer.js to streamline bubble and dust creation, enhance mobile handling, and remove unused randomization options 2026-02-27 23:03:42 +01:00
CodeDevMLH
b85c038df0 Refactor underwater.js to enhance mobile symbol handling and update credits 2026-02-27 22:56:13 +01:00
CodeDevMLH
3d4e04ab0f Change SymbolCountMobile property type from bool to int in seasonal options 2026-02-27 22:23:39 +01:00
CodeDevMLH
b1d1ce79e6 Merge branch 'main' of ssh://git.mahom03-spacecloud.de:44322/CodeDevMLH/Jellyfin-Seasonals-Plugin
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 53s
2026-02-27 21:57:27 +01:00
CodeDevMLH
0b7b506b8d Remove unnecessary comments and streamline DOM observation in seasonal scripts 2026-02-27 21:57:24 +01:00
CodeDevMLH
f3ea84cc80 Refactor seasonal options in PluginConfiguration to streamline and enhance organization 2026-02-27 21:55:04 +01:00
CodeDevMLH
3d9a474aae Update manifest.json for release v2.0.0.1 [skip ci] 2026-02-27 03:27:03 +00:00
CodeDevMLH
db5baa1fd7 Bump version to 2.0.0.1
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 53s
2026-02-27 04:26:12 +01:00
CodeDevMLH
72ad4ee1a4 Add option to enable different symbol durations for spooky theme; fix casing in snowstorm config 2026-02-27 04:25:53 +01:00
CodeDevMLH
bb6c7796d5 Reduce z-index of matrix-container to improve stacking context and prevent overlap issues [skip ci] 2026-02-27 04:08:00 +01:00
CodeDevMLH
bd8088c52b Add checks for container presence in animation functions to prevent errors when elements are removed from the DOM [skip ci] 2026-02-27 04:07:29 +01:00
CodeDevMLH
3c1bd01373 Fix positioning of olympia-symbol and adjust ring CSS for improved alignment [skip ci] 2026-02-27 03:59:19 +01:00
CodeDevMLH
669ac6d3da Fix spider and mouse creation logic to ensure animations only continue if the container is present in the DOM 2026-02-27 03:58:34 +01:00
CodeDevMLH
73f9be91ef Refactor z-index values and optimize CSS properties for various seasonal styles; enhance performance with will-change property [skip ci] 2026-02-27 03:53:05 +01:00
CodeDevMLH
f14785c54a Enhance CSS animations and performance across seasonal styles; add will-change property for smoother transitions and optimize keyframes. [skip ci] 2026-02-27 03:42:23 +01:00
CodeDevMLH
296873f89e Fix rabbit animation transform origin and improve GIF reset logic
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 40s
2026-02-27 01:58:53 +01:00
CodeDevMLH
d6a9ff7176 Fix z-index for pride container and clean up comments in pride.js
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 42s
2026-02-27 01:35:25 +01:00
70 changed files with 1483 additions and 2301 deletions

View File

@@ -18,40 +18,40 @@ public class PluginConfiguration : BasePluginConfiguration
EnableClientSideToggle = true; EnableClientSideToggle = true;
Autumn = new AutumnOptions(); Autumn = new AutumnOptions();
Snowflakes = new SnowflakesOptions(); Birthday = new BirthdayOptions();
Snowfall = new SnowfallOptions(); Carnival = new CarnivalOptions();
Snowstorm = new SnowstormOptions(); CherryBlossom = new CherryBlossomOptions();
Christmas = new ChristmasOptions();
EarthDay = new EarthDayOptions();
Easter = new EasterOptions();
Eid = new EidOptions();
Eurovision = new EurovisionOptions();
FilmNoir = new FilmNoirOptions();
Fireworks = new FireworksOptions(); Fireworks = new FireworksOptions();
Friday13 = new Friday13Options();
Frost = new FrostOptions();
Halloween = new HalloweenOptions(); Halloween = new HalloweenOptions();
Hearts = new HeartsOptions(); Hearts = new HeartsOptions();
Christmas = new ChristmasOptions();
Santa = new SantaOptions();
Easter = new EasterOptions();
Resurrection = new ResurrectionOptions();
Spring = new SpringOptions();
Summer = new SummerOptions();
CherryBlossom = new CherryBlossomOptions();
Carnival = new CarnivalOptions();
Matrix = new MatrixOptions();
Eurovision = new EurovisionOptions();
Storm = new StormOptions();
Pride = new PrideOptions();
EarthDay = new EarthDayOptions();
Rain = new RainOptions();
Frost = new FrostOptions();
FilmNoir = new FilmNoirOptions();
Oscar = new OscarOptions();
MarioDay = new MarioDayOptions(); MarioDay = new MarioDayOptions();
StarWars = new StarWarsOptions(); Matrix = new MatrixOptions();
Oktoberfest = new OktoberfestOptions(); Oktoberfest = new OktoberfestOptions();
Friday13 = new Friday13Options(); Olympia = new OlympiaOptions();
Eid = new EidOptions(); Oscar = new OscarOptions();
Rain = new RainOptions();
Pride = new PrideOptions();
Resurrection = new ResurrectionOptions();
Santa = new SantaOptions();
Snowfall = new SnowfallOptions();
Snowflakes = new SnowflakesOptions();
Snowstorm = new SnowstormOptions();
Space = new SpaceOptions();
Spooky = new SpookyOptions(); Spooky = new SpookyOptions();
Sports = new SportsOptions(); Sports = new SportsOptions();
Olympia = new OlympiaOptions(); Spring = new SpringOptions();
Space = new SpaceOptions(); StarWars = new StarWarsOptions();
Storm = new StormOptions();
Summer = new SummerOptions();
Underwater = new UnderwaterOptions(); Underwater = new UnderwaterOptions();
Birthday = new BirthdayOptions();
} }
/// <summary> /// <summary>
@@ -83,346 +83,301 @@ public class PluginConfiguration : BasePluginConfiguration
/// Gets or sets the Seasonals options. /// Gets or sets the Seasonals options.
/// </summary> /// </summary>
public AutumnOptions Autumn { get; set; } public AutumnOptions Autumn { get; set; }
public SnowflakesOptions Snowflakes { get; set; } public BirthdayOptions Birthday { get; set; }
public SnowfallOptions Snowfall { get; set; } public CarnivalOptions Carnival { get; set; }
public SnowstormOptions Snowstorm { get; set; } public CherryBlossomOptions CherryBlossom { get; set; }
public ChristmasOptions Christmas { get; set; }
public EarthDayOptions EarthDay { get; set; }
public EasterOptions Easter { get; set; }
public EidOptions Eid { get; set; }
public EurovisionOptions Eurovision { get; set; }
public FilmNoirOptions FilmNoir { get; set; }
public FireworksOptions Fireworks { get; set; } public FireworksOptions Fireworks { get; set; }
public Friday13Options Friday13 { get; set; }
public FrostOptions Frost { get; set; }
public HalloweenOptions Halloween { get; set; } public HalloweenOptions Halloween { get; set; }
public HeartsOptions Hearts { get; set; } public HeartsOptions Hearts { get; set; }
public ChristmasOptions Christmas { get; set; }
public SantaOptions Santa { get; set; }
public EasterOptions Easter { get; set; }
public ResurrectionOptions Resurrection { get; set; }
public SpringOptions Spring { get; set; }
public SummerOptions Summer { get; set; }
public CherryBlossomOptions CherryBlossom { get; set; }
public CarnivalOptions Carnival { get; set; }
public MatrixOptions Matrix { get; set; }
public EurovisionOptions Eurovision { get; set; }
public StormOptions Storm { get; set; }
public PrideOptions Pride { get; set; }
public EarthDayOptions EarthDay { get; set; }
public RainOptions Rain { get; set; }
public FrostOptions Frost { get; set; }
public FilmNoirOptions FilmNoir { get; set; }
public OscarOptions Oscar { get; set; }
public MarioDayOptions MarioDay { get; set; } public MarioDayOptions MarioDay { get; set; }
public StarWarsOptions StarWars { get; set; } public MatrixOptions Matrix { get; set; }
public OktoberfestOptions Oktoberfest { get; set; } public OktoberfestOptions Oktoberfest { get; set; }
public Friday13Options Friday13 { get; set; } public OlympiaOptions Olympia { get; set; }
public EidOptions Eid { get; set; } public OscarOptions Oscar { get; set; }
public PrideOptions Pride { get; set; }
public RainOptions Rain { get; set; }
public ResurrectionOptions Resurrection { get; set; }
public SantaOptions Santa { get; set; }
public SnowfallOptions Snowfall { get; set; }
public SnowflakesOptions Snowflakes { get; set; }
public SnowstormOptions Snowstorm { get; set; }
public SpaceOptions Space { get; set; }
public SpookyOptions Spooky { get; set; } public SpookyOptions Spooky { get; set; }
public SportsOptions Sports { get; set; } public SportsOptions Sports { get; set; }
public OlympiaOptions Olympia { get; set; } public SpringOptions Spring { get; set; }
public SpaceOptions Space { get; set; } public StarWarsOptions StarWars { get; set; }
public StormOptions Storm { get; set; }
public SummerOptions Summer { get; set; }
public UnderwaterOptions Underwater { get; set; } public UnderwaterOptions Underwater { get; set; }
public BirthdayOptions Birthday { get; set; }
} }
public class AutumnOptions public class AutumnOptions {
{
public int LeafCount { get; set; } = 25;
public bool EnableAutumn { get; set; } = true; public bool EnableAutumn { get; set; } = true;
public bool EnableRandomLeaves { get; set; } = true; public int LeafCount { get; set; } = 35;
public bool EnableRandomLeavesMobile { get; set; } = false; public int LeafCountMobile { get; set; } = 10;
public bool EnableDifferentDuration { get; set; } = true; public bool EnableDifferentDuration { get; set; } = true;
public bool EnableRotation { get; set; } = false; public bool EnableRotation { get; set; } = false;
} }
public class SnowflakesOptions public class BirthdayOptions {
{ public bool EnableBirthday { get; set; } = true;
public int SnowflakeCount { get; set; } = 25; public int SymbolCount { get; set; } = 12;
public bool EnableSnowflakes { get; set; } = true; public int SymbolCountMobile { get; set; } = 5;
public bool EnableRandomSnowflakes { get; set; } = true; public bool EnableDifferentDuration { get; set; } = true;
public bool EnableRandomSnowflakesMobile { get; set; } = false; public int ConfettiCount { get; set; } = 60;
public bool EnableColoredSnowflakes { get; set; } = true; }
public class CarnivalOptions {
public bool EnableCarnival { get; set; } = true;
public bool EnableDifferentDuration { get; set; } = true;
public bool EnableCarnivalSway { get; set; } = true;
public int ObjectCount { get; set; } = 120;
public int ObjectCountMobile { get; set; } = 60;
}
public class CherryBlossomOptions {
public bool EnableCherryBlossom { get; set; } = true;
public int PetalCount { get; set; } = 25;
public int PetalCountMobile { get; set; } = 15;
public bool EnableDifferentDuration { get; set; } = true; public bool EnableDifferentDuration { get; set; } = true;
} }
public class SnowfallOptions public class ChristmasOptions {
{ public bool EnableChristmas { get; set; } = true;
public int SnowflakesCount { get; set; } = 500; public int SymbolCount { get; set; } = 25;
public int SnowflakesCountMobile { get; set; } = 250; public int SymbolCountMobile { get; set; } = 10;
public double Speed { get; set; } = 3; public bool EnableDifferentDuration { get; set; } = true;
public bool EnableSnowfall { get; set; } = true;
} }
public class SnowstormOptions public class EarthDayOptions {
{ public bool EnableEarthDay { get; set; } = true;
public int SnowflakesCount { get; set; } = 500; public int VineCount { get; set; } = 4;
public int SnowflakesCountMobile { get; set; } = 250;
public double Speed { get; set; } = 6;
public bool EnableSnowstorm { get; set; } = true;
public double HorizontalWind { get; set; } = 4;
public double VerticalVariation { get; set; } = 2;
} }
public class FireworksOptions public class EasterOptions {
{ public bool EnableEaster { get; set; } = true;
public int ParticleCount { get; set; } = 50; public bool EnableBunny { get; set; } = true;
public int LaunchInterval { get; set; } = 3200; public int MinBunnyRestTime { get; set; } = 2000;
public int MaxBunnyRestTime { get; set; } = 5000;
public int EggCount { get; set; } = 15;
}
public class EidOptions {
public bool EnableEid { get; set; } = true;
public int LanternCount { get; set; } = 8;
public int LanternCountMobile { get; set; } = 3;
}
public class EurovisionOptions {
public bool EnableEurovision { get; set; } = true;
public int SymbolCount { get; set; } = 25;
public bool EnableDifferentDuration { get; set; } = true;
public bool EnableColorfulNotes { get; set; } = true;
public string EurovisionColors { get; set; } = "#ff0026ff,#17a6ffff,#32d432ff,#FFD700,#f0821bff,#f826f8ff";
public int EurovisionGlowSize { get; set; } = 2;
}
public class FilmNoirOptions {
public bool EnableFilmNoir { get; set; } = true;
}
public class FireworksOptions {
public bool EnableFireworks { get; set; } = true; public bool EnableFireworks { get; set; } = true;
public bool ScrollFireworks { get; set; } = true; public bool ScrollFireworks { get; set; } = true;
public int ParticleCount { get; set; } = 50;
public int MinFireworks { get; set; } = 3; public int MinFireworks { get; set; } = 3;
public int MaxFireworks { get; set; } = 6; public int MaxFireworks { get; set; } = 6;
public int LaunchInterval { get; set; } = 3200;
} }
public class HalloweenOptions public class Friday13Options {
{ public bool EnableFriday13 { get; set; } = true;
public int SymbolCount { get; set; } = 25; }
public class FrostOptions {
public bool EnableFrost { get; set; } = true;
}
public class HalloweenOptions {
public bool EnableHalloween { get; set; } = true; public bool EnableHalloween { get; set; } = true;
public bool EnableRandomSymbols { get; set; } = true; public int SymbolCount { get; set; } = 25;
public bool EnableRandomSymbolsMobile { get; set; } = false; public int SymbolCountMobile { get; set; } = 10;
public bool EnableDifferentDuration { get; set; } = true; public bool EnableDifferentDuration { get; set; } = true;
public bool EnableSpiders { get; set; } = true; public bool EnableSpiders { get; set; } = true;
public bool EnableMice { get; set; } = true; public bool EnableMice { get; set; } = true;
} }
public class HeartsOptions public class HeartsOptions {
{
public int SymbolCount { get; set; } = 25;
public bool EnableHearts { get; set; } = true; public bool EnableHearts { get; set; } = true;
public bool EnableRandomSymbols { get; set; } = true;
public bool EnableRandomSymbolsMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true;
}
public class ChristmasOptions
{
public int SymbolCount { get; set; } = 25; public int SymbolCount { get; set; } = 25;
public bool EnableChristmas { get; set; } = true; public int SymbolCountMobile { get; set; } = 10;
public bool EnableRandomChristmas { get; set; } = true;
public bool EnableRandomChristmasMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true; public bool EnableDifferentDuration { get; set; } = true;
} }
public class SantaOptions public class MarioDayOptions {
{ public bool EnableMarioDay { get; set; } = true;
public bool LetMarioJump { get; set; } = true;
}
public class MatrixOptions {
public bool EnableMatrix { get; set; } = true;
public int SymbolCount { get; set; } = 25;
public bool EnableMatrixBackground { get; set; } = false;
public string MatrixChars { get; set; } = "0123456789";
}
public class OktoberfestOptions {
public bool EnableOktoberfest { get; set; } = true;
public int SymbolCount { get; set; } = 25;
public int SymbolCountMobile { get; set; } = 10;
public bool EnableDifferentDuration { get; set; } = true;
}
public class OlympiaOptions {
public bool EnableOlympia { get; set; } = true;
public int SymbolCount { get; set; } = 25;
public int SymbolCountMobile { get; set; } = 10;
public bool EnableDifferentDuration { get; set; } = true;
}
public class OscarOptions {
public bool EnableOscar { get; set; } = true;
}
public class PrideOptions {
public bool EnablePride { get; set; } = true;
public int HeartCount { get; set; } = 20;
public double HeartSize { get; set; } = 1.5;
public bool ColorHeader { get; set; } = true;
}
public class RainOptions {
public bool EnableRain { get; set; } = true;
public int RaindropCount { get; set; } = 300;
public int RaindropCountMobile { get; set; } = 150;
public double RainSpeed { get; set; } = 1.0;
}
public class ResurrectionOptions {
public bool EnableResurrection { get; set; } = true;
public int SymbolCount { get; set; } = 12;
public int SymbolCountMobile { get; set; } = 5;
public bool EnableDifferentDuration { get; set; } = true;
}
public class SantaOptions {
public bool EnableSanta { get; set; } = true;
public int SnowflakesCount { get; set; } = 500; public int SnowflakesCount { get; set; } = 500;
public int SnowflakesCountMobile { get; set; } = 250; public int SnowflakesCountMobile { get; set; } = 250;
public double SnowFallSpeed { get; set; } = 3;
public double SantaSpeed { get; set; } = 10; public double SantaSpeed { get; set; } = 10;
public double SantaSpeedMobile { get; set; } = 8; public double SantaSpeedMobile { get; set; } = 8;
public bool EnableSanta { get; set; } = true;
public double SnowFallSpeed { get; set; } = 3;
public double MaxSantaRestTime { get; set; } = 8; public double MaxSantaRestTime { get; set; } = 8;
public double MinSantaRestTime { get; set; } = 3; public double MinSantaRestTime { get; set; } = 3;
public double MaxPresentFallSpeed { get; set; } = 5; public double MaxPresentFallSpeed { get; set; } = 5;
public double MinPresentFallSpeed { get; set; } = 2; public double MinPresentFallSpeed { get; set; } = 2;
} }
public class EasterOptions public class SnowfallOptions {
{ public bool EnableSnowfall { get; set; } = true;
public int EggCount { get; set; } = 20; public int SnowflakesCount { get; set; } = 500;
public bool EnableEaster { get; set; } = true; public int SnowflakesCountMobile { get; set; } = 250;
public bool EnableRandomEaster { get; set; } = true; public double Speed { get; set; } = 3;
public bool EnableRandomEasterMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true;
public bool EnableBunny { get; set; } = true;
public int BunnyDuration { get; set; } = 12000;
public int HopHeight { get; set; } = 12;
public int MinBunnyRestTime { get; set; } = 2000;
public int MaxBunnyRestTime { get; set; } = 5000;
} }
public class ResurrectionOptions public class SnowflakesOptions {
{ public bool EnableSnowflakes { get; set; } = true;
public int SymbolCount { get; set; } = 12; public int SnowflakeCount { get; set; } = 25;
public bool EnableResurrection { get; set; } = true; public int SnowflakeCountMobile { get; set; } = 10;
public bool EnableRandomSymbols { get; set; } = true; public bool EnableColoredSnowflakes { get; set; } = true;
public bool EnableRandomSymbolsMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true; public bool EnableDifferentDuration { get; set; } = true;
} }
public class SpringOptions public class SnowstormOptions {
{ public bool EnableSnowstorm { get; set; } = true;
public int PollenCount { get; set; } = 30; public int SnowflakesCount { get; set; } = 500;
public int SunbeamCount { get; set; } = 5; public int SnowflakesCountMobile { get; set; } = 250;
public int BirdCount { get; set; } = 4; public double Speed { get; set; } = 6;
public int ButterflyCount { get; set; } = 4; public double HorizontalWind { get; set; } = 4;
public int BeeCount { get; set; } = 2; public double VerticalVariation { get; set; } = 2;
public int LadybugCount { get; set; } = 2; }
public bool EnableSpring { get; set; } = true;
public bool EnableSpringSunbeams { get; set; } = true; public class SpaceOptions {
public bool EnableRandomSpring { get; set; } = true; public bool EnableSpace { get; set; } = true;
public bool EnableRandomSpringMobile { get; set; } = false; public int PlanetCount { get; set; } = 6;
public int AstronautCount { get; set; } = 1;
public int SatelliteCount { get; set; } = 4;
public int IssCount { get; set; } = 1;
public int RocketCount { get; set; } = 1;
public bool EnableDifferentDuration { get; set; } = true; public bool EnableDifferentDuration { get; set; } = true;
public int SymbolCountMobile { get; set; } = 2;
} }
public class SummerOptions public class SpookyOptions {
{
public int BubbleCount { get; set; } = 20;
public int DustCount { get; set; } = 50;
public bool EnableSummer { get; set; } = true;
public bool EnableRandomSummer { get; set; } = true;
public bool EnableRandomSummerMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true;
}
public class CarnivalOptions
{
public int ObjectCount { get; set; } = 25;
public bool EnableCarnival { get; set; } = true;
public bool EnableRandomCarnival { get; set; } = true;
public bool EnableRandomCarnivalMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true;
public bool EnableCarnivalSway { get; set; } = true;
}
public class CherryBlossomOptions
{
public int PetalCount { get; set; } = 25;
public bool EnableCherryBlossom { get; set; } = true;
public bool EnableRandomCherryBlossom { get; set; } = true;
public bool EnableRandomCherryBlossomMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true;
}
public class MatrixOptions
{
public int SymbolCount { get; set; } = 25;
public bool EnableMatrix { get; set; } = true;
public bool EnableRandomMatrix { get; set; } = true;
public bool EnableRandomMatrixMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true;
public bool EnableMatrixBackground { get; set; } = false;
public string MatrixChars { get; set; } = "0123456789";
}
public class EurovisionOptions
{
public int SymbolCount { get; set; } = 25;
public bool EnableEurovision { get; set; } = true;
public bool EnableRandomEurovision { get; set; } = true;
public bool EnableRandomEurovisionMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true;
public bool EnableColorfulNotes { get; set; } = true;
public string EurovisionColors { get; set; } = "#ff0026ff,#17a6ffff,#32d432ff,#FFD700,#f0821bff,#f826f8ff";
public int EurovisionGlowSize { get; set; } = 8;
}
public class StormOptions
{
public int RaindropCount { get; set; } = 300;
public int RaindropCountMobile { get; set; } = 150;
public bool EnableStorm { get; set; } = true;
public bool EnableLightning { get; set; } = true;
public double RainSpeed { get; set; } = 1;
}
public class PrideOptions
{
public bool EnablePride { get; set; } = true;
public int HeartCount { get; set; } = 20;
public int HeartSize { get; set; } = 2;
public bool ColorHeader { get; set; } = true;
}
public class EarthDayOptions
{
public bool EnableEarthDay { get; set; } = true;
public int VineCount { get; set; } = 4;
}
public class RainOptions
{
public bool EnableRain { get; set; } = true;
public int RaindropCount { get; set; } = 300;
public int RaindropCountMobile { get; set; } = 150;
public double RainSpeed { get; set; } = 1;
}
public class FrostOptions
{
public bool EnableFrost { get; set; } = true;
}
public class FilmNoirOptions
{
public bool EnableFilmNoir { get; set; } = true;
}
public class OscarOptions
{
public bool EnableOscar { get; set; } = true;
}
public class MarioDayOptions
{
public bool EnableMarioDay { get; set; } = true;
}
public class StarWarsOptions
{
public bool EnableStarWars { get; set; } = true;
}
public class OktoberfestOptions
{
public bool EnableOktoberfest { get; set; } = true;
}
public class Friday13Options
{
public bool EnableFriday13 { get; set; } = true;
}
public class EidOptions
{
public bool EnableEid { get; set; } = true;
}
public class SpookyOptions
{
public bool EnableSpooky { get; set; } = true; public bool EnableSpooky { get; set; } = true;
public int SymbolCount { get; set; } = 25; public int SymbolCount { get; set; } = 25;
public bool EnableDifferentDuration { get; set; } = true;
public bool EnableSpookySway { get; set; } = true; public bool EnableSpookySway { get; set; } = true;
public int SpookySize { get; set; } = 20; public int SpookySize { get; set; } = 20;
public int SpookyGlowSize { get; set; } = 2; public int SpookyGlowSize { get; set; } = 2;
} }
public class SportsOptions public class SportsOptions {
{
public int SymbolCount { get; set; } = 5;
public bool EnableSports { get; set; } = true; public bool EnableSports { get; set; } = true;
public bool EnableRandomSymbols { get; set; } = true; public int SymbolCount { get; set; } = 5;
public bool EnableRandomSymbolsMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true; public bool EnableDifferentDuration { get; set; } = true;
public string TurfColor { get; set; } = "#228b22"; public string TurfColor { get; set; } = "#228b22";
public string SportsBalls { get; set; } = "football,basketball,tennis,volleyball"; public string SportsBalls { get; set; } = "football,basketball,tennis,volleyball";
public bool EnableTrophy { get; set; } = false; public bool EnableTrophy { get; set; } = false;
public string ConfettiColors { get; set; } = "#000000,#FF0000,#FFCC00";
} }
public class OlympiaOptions public class SpringOptions {
{ public bool EnableSpring { get; set; } = true;
public int SymbolCount { get; set; } = 25; public int PollenCount { get; set; } = 30;
public bool EnableOlympia { get; set; } = true; public bool EnableSpringSunbeams { get; set; } = true;
public bool EnableRandomSymbols { get; set; } = true; public int SunbeamCount { get; set; } = 5;
public bool EnableRandomSymbolsMobile { get; set; } = false; public int BirdCount { get; set; } = 3;
public int ButterflyCount { get; set; } = 4;
public int BeeCount { get; set; } = 2;
public int LadybugCount { get; set; } = 2;
public int SymbolCountMobile { get; set; } = 2;
}
public class StarWarsOptions {
public bool EnableStarWars { get; set; } = true;
}
public class StormOptions {
public bool EnableStorm { get; set; } = true;
public int RaindropCount { get; set; } = 300;
public int RaindropCountMobile { get; set; } = 150;
public bool EnableLightning { get; set; } = true;
public double RainSpeed { get; set; } = 1.0;
}
public class SummerOptions {
public bool EnableSummer { get; set; } = true;
public int BubbleCount { get; set; } = 30;
public int DustCount { get; set; } = 50;
public int SymbolCountMobile { get; set; } = 2;
public bool EnableDifferentDuration { get; set; } = true; public bool EnableDifferentDuration { get; set; } = true;
} }
public class SpaceOptions public class UnderwaterOptions {
{
public int PlanetCount { get; set; } = 12;
public int AstronautCount { get; set; } = 5;
public int SatelliteCount { get; set; } = 2;
public int IssCount { get; set; } = 1;
public int RocketCount { get; set; } = 1;
public bool EnableSpace { get; set; } = true;
public bool EnableRandomSymbols { get; set; } = true;
public bool EnableRandomSymbolsMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true;
}
public class UnderwaterOptions
{
public int SymbolCount { get; set; } = 15;
public bool EnableUnderwater { get; set; } = true; public bool EnableUnderwater { get; set; } = true;
public bool EnableRandomSymbols { get; set; } = true; public int SymbolCountMobile { get; set; } = 2;
public bool EnableRandomSymbolsMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true; public bool EnableDifferentDuration { get; set; } = true;
public bool EnableLightRays { get; set; } = true; public bool EnableLightRays { get; set; } = true;
public int SeaweedCount { get; set; } = 30; public int SeaweedCount { get; set; } = 50;
public int CrabCount { get; set; } = 2; public int CrabCount { get; set; } = 2;
public int StarfishCount { get; set; } = 2; public int StarfishCount { get; set; } = 2;
public int ShellCount { get; set; } = 2; public int ShellCount { get; set; } = 2;
@@ -430,15 +385,4 @@ public class UnderwaterOptions
public int SeahorseCount { get; set; } = 3; public int SeahorseCount { get; set; } = 3;
public int JellyfishCount { get; set; } = 3; public int JellyfishCount { get; set; } = 3;
public int TurtleCount { get; set; } = 1; public int TurtleCount { get; set; } = 1;
} }
public class BirthdayOptions
{
public int SymbolCount { get; set; } = 5;
public int ConfettiCount { get; set; } = 60;
public bool EnableBirthday { get; set; } = true;
public bool EnableRandomSymbols { get; set; } = true;
public bool EnableRandomSymbolsMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true;
public bool EnableGarland { get; set; } = true;
}

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@
<!-- <TreatWarningsAsErrors>false</TreatWarningsAsErrors> --> <!-- <TreatWarningsAsErrors>false</TreatWarningsAsErrors> -->
<Title>Jellyfin Seasonals Plugin</Title> <Title>Jellyfin Seasonals Plugin</Title>
<Authors>CodeDevMLH</Authors> <Authors>CodeDevMLH</Authors>
<Version>2.0.0.0</Version> <Version>2.0.0.3</Version>
<RepositoryUrl>https://github.com/CodeDevMLH/Jellyfin-Seasonals</RepositoryUrl> <RepositoryUrl>https://github.com/CodeDevMLH/Jellyfin-Seasonals</RepositoryUrl>
</PropertyGroup> </PropertyGroup>

View File

@@ -22,13 +22,7 @@
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
text-shadow: 0 0 5px #000; text-shadow: 0 0 5px #000;
user-select: none; user-select: none;
-webkit-user-select: none;
cursor: default; cursor: default;
-webkit-animation-name: leaf-fall, leaf-shake;
-webkit-animation-duration: 7s, 4s;
-webkit-animation-timing-function: linear, ease-in-out;
-webkit-animation-iteration-count: infinite, infinite;
-webkit-user-select: none;
animation-name: leaf-fall, leaf-shake; animation-name: leaf-fall, leaf-shake;
animation-duration: 7s, 4s; animation-duration: 7s, 4s;
animation-timing-function: linear, ease-in-out; animation-timing-function: linear, ease-in-out;
@@ -41,15 +35,6 @@
--rotate-end: 0deg !important; --rotate-end: 0deg !important;
} }
@-webkit-keyframes leaf-fall {
0% {
translate: 0 -10vh;
}
100% {
translate: 0 100vh;
}
}
@keyframes leaf-fall { @keyframes leaf-fall {
0% { 0% {
@@ -61,14 +46,6 @@
} }
} }
@-webkit-keyframes leaf-shake {
0%, 100% {
transform: translateX(0) rotate(var(--rotate-start, -20deg));
}
50% {
transform: translateX(80px) rotate(var(--rotate-end, 20deg));
}
}
@keyframes leaf-shake { @keyframes leaf-shake {
0%, 100% { 0%, 100% {
@@ -78,87 +55,3 @@
transform: translateX(80px) rotate(var(--rotate-end, 20deg)); transform: translateX(80px) rotate(var(--rotate-end, 20deg));
} }
} }
.leaf:nth-of-type(0) {
left: 0%;
animation-delay: 0s, 0s;
--rotate-start: -25deg;
--rotate-end: 22deg;
}
.leaf:nth-of-type(1) {
left: 10%;
animation-delay: 1s, 0.5s;
--rotate-start: -32deg;
--rotate-end: 35deg;
}
.leaf:nth-of-type(2) {
left: 20%;
animation-delay: 6s, 1s;
--rotate-start: -28deg;
--rotate-end: 28deg;
}
.leaf:nth-of-type(3) {
left: 30%;
animation-delay: 4s, 1.5s;
--rotate-start: -38deg;
--rotate-end: 32deg;
}
.leaf:nth-of-type(4) {
left: 40%;
animation-delay: 2s, 0.8s;
--rotate-start: -22deg;
--rotate-end: 38deg;
}
.leaf:nth-of-type(5) {
left: 50%;
animation-delay: 8s, 2s;
--rotate-start: -35deg;
--rotate-end: 25deg;
}
.leaf:nth-of-type(6) {
left: 60%;
animation-delay: 6s, 1.2s;
--rotate-start: -40deg;
--rotate-end: 40deg;
}
.leaf:nth-of-type(7) {
left: 70%;
animation-delay: 2.5s, 0.3s;
--rotate-start: -30deg;
--rotate-end: 30deg;
}
.leaf:nth-of-type(8) {
left: 80%;
animation-delay: 1s, 1.8s;
--rotate-start: -26deg;
--rotate-end: 36deg;
}
.leaf:nth-of-type(9) {
left: 90%;
animation-delay: 3s, 0.7s;
--rotate-start: -34deg;
--rotate-end: 24deg;
}
.leaf:nth-of-type(10) {
left: 25%;
animation-delay: 2s, 2.3s;
--rotate-start: -29deg;
--rotate-end: 33deg;
}
.leaf:nth-of-type(11) {
left: 65%;
animation-delay: 4s, 1.4s;
--rotate-start: -37deg;
--rotate-end: 27deg;
}

View File

@@ -1,11 +1,10 @@
const config = window.SeasonalsPluginConfig?.Autumn || {}; const config = window.SeasonalsPluginConfig?.Autumn || {};
const leaves = config.EnableAutumn !== undefined ? config.EnableAutumn : true; // enable/disable leaves const leaves = config.EnableAutumn !== undefined ? config.EnableAutumn : true; // enable/disable autumn
const randomLeaves = config.EnableRandomLeaves !== undefined ? config.EnableRandomLeaves : true; // enable random leaves const enableDiffrentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different durations
const randomLeavesMobile = config.EnableRandomLeavesMobile !== undefined ? config.EnableRandomLeavesMobile : false; // enable random leaves on mobile devices (Warning: High values may affect performance) const enableRotation = config.EnableRotation !== undefined ? config.EnableRotation : false; // enable/disable rotation
const enableDiffrentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different duration for the random leaves const leafCount = config.LeafCount !== undefined ? config.LeafCount : 35; // count of random extra leaves
const enableRotation = config.EnableRotation !== undefined ? config.EnableRotation : false; // enable/disable leaf rotation const leafCountMobile = config.LeafCountMobile !== undefined ? config.LeafCountMobile : 10; // count of random extra leaves on mobile
const leafCount = config.LeafCount || 25; // count of random extra leaves
const images = [ const images = [
"../Seasonals/Resources/autumn_images/acorn1.png", "../Seasonals/Resources/autumn_images/acorn1.png",
@@ -64,11 +63,16 @@ observer.observe(document.body, {
}); });
function addRandomLeaves(count) { function initLeaves(count) {
const autumnContainer = document.querySelector('.autumn-container'); // get the leave container let autumnContainer = document.querySelector('.autumn-container'); // get the leave container
if (!autumnContainer) return; // exit if leave container is not found if (!autumnContainer) {
autumnContainer = document.createElement("div");
autumnContainer.className = "autumn-container";
autumnContainer.setAttribute("aria-hidden", "true");
document.body.appendChild(autumnContainer);
}
console.log('Adding random leaves'); console.log('Adding leaves');
// Array of leave characters // Array of leave characters
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
@@ -87,6 +91,8 @@ function addRandomLeaves(count) {
// set random horizontal position, animation delay and size(uncomment lines to enable) // set random horizontal position, animation delay and size(uncomment lines to enable)
const randomLeft = Math.random() * 100; // position (0% to 100%) const randomLeft = Math.random() * 100; // position (0% to 100%)
const randomAnimationDelay = Math.random() * 12; // delay for fall (0s to 12s) const randomAnimationDelay = Math.random() * 12; // delay for fall (0s to 12s)
// Display directly symbols on full screen (below) or let it build up (above)
// const randomAnimationDelay = -(Math.random() * 16); // delay for fall (-16s to 0s)
const randomAnimationDelay2 = -(Math.random() * 4); // delay for shake+rotate (-4s to 0s) const randomAnimationDelay2 = -(Math.random() * 4); // delay for shake+rotate (-4s to 0s)
// apply styles // apply styles
@@ -115,60 +121,18 @@ function addRandomLeaves(count) {
// add the leave to the container // add the leave to the container
autumnContainer.appendChild(leaveDiv); autumnContainer.appendChild(leaveDiv);
} }
console.log('Random leaves added'); console.log('Leaves added');
} }
// initialize standard leaves // initialize leaves
function initLeaves() {
const container = document.querySelector('.autumn-container') || document.createElement("div");
if (!document.querySelector('.autumn-container')) {
container.className = "autumn-container";
container.setAttribute("aria-hidden", "true");
document.body.appendChild(container);
}
for (let i = 0; i < 12; i++) {
const leafDiv = document.createElement("div");
leafDiv.className = enableRotation ? "leaf" : "leaf no-rotation";
const img = document.createElement("img");
img.src = images[Math.floor(Math.random() * images.length)];
// set random animation duration
if (enableDiffrentDuration) {
const randomAnimationDuration = Math.random() * 10 + 6; // fall duration (6s to 16s)
const randomAnimationDuration2 = Math.random() * 3 + 2; // shake+rotate duration (2s to 5s)
leafDiv.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`;
}
// set random rotation angles for standard leaves too (only if rotation is enabled)
if (enableRotation) {
const randomRotateStart = -(Math.random() * 40 + 20); // -20deg to -60deg
const randomRotateEnd = Math.random() * 40 + 20; // 20deg to 60deg
leafDiv.style.setProperty('--rotate-start', `${randomRotateStart}deg`);
leafDiv.style.setProperty('--rotate-end', `${randomRotateEnd}deg`);
} else {
// No rotation - set to 0 degrees
leafDiv.style.setProperty('--rotate-start', '0deg');
leafDiv.style.setProperty('--rotate-end', '0deg');
}
leafDiv.appendChild(img);
container.appendChild(leafDiv);
}
}
// initialize leaves and add random leaves
function initializeLeaves() { function initializeLeaves() {
if (!leaves) return; // exit if leaves are disabled if (!leaves) return; // exit if leaves are disabled
initLeaves();
toggleAutumn();
const screenWidth = window.innerWidth; // get the screen width to detect mobile devices const isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
if (randomLeaves && (screenWidth > 768 || randomLeavesMobile)) { // add random leaves only on larger screens, unless enabled for mobile devices const count = !isMobile ? leafCount : leafCountMobile;
addRandomLeaves(leafCount);
} initLeaves(count);
toggleAutumn();
} }
initializeLeaves(); initializeLeaves();

View File

@@ -5,12 +5,14 @@
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
pointer-events: none; pointer-events: none;
z-index: 9999; z-index: 10;
overflow: hidden; overflow: hidden;
contain: strict; contain: strict;
contain: layout paint;
} }
.birthday-symbol { .birthday-symbol {
will-change: opacity;
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
@@ -29,7 +31,7 @@
} }
.birthday-inner { .birthday-inner {
pointer-events: auto; /* Allow hover over the actual item */ pointer-events: auto;
cursor: crosshair; cursor: crosshair;
display: inline-block; display: inline-block;
} }
@@ -138,7 +140,7 @@
opacity: 1; opacity: 1;
} }
100% { 100% {
transform: translateY(calc(var(--burst-y) + 150px)); /* Gravity pull downwards */ transform: translateY(calc(var(--burst-y) + 150px));
opacity: 0; opacity: 0;
} }
} }

View File

@@ -1,12 +1,15 @@
const config = window.SeasonalsPluginConfig?.Birthday || {}; const config = window.SeasonalsPluginConfig?.Birthday || {};
const birthday = config.EnableBirthday !== undefined ? config.EnableBirthday : true; const birthday = config.EnableBirthday !== undefined ? config.EnableBirthday : true; // enable/disable birthday symbols
const symbolCount = config.SymbolCount || 5; const symbolCount = config.SymbolCount !== undefined ? config.SymbolCount : 15; // count of balloons
const useRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true; const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different duration for the symbols
const enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false; const symbolCountMobile = config.SymbolCountMobile !== undefined ? config.SymbolCountMobile : 5; // count of mobile balloons
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; const baseConfettiCount = config.ConfettiCount !== undefined ? config.ConfettiCount : 60; // count of confetti
/**
* Base ballon image: https://www.flaticon.com/de/kostenloses-icon/ballon_1512470
* modified by CodeDevMLH
*/
const birthdayImages = [ const birthdayImages = [
'../Seasonals/Resources/birthday_assets/balloon_blue.gif', '../Seasonals/Resources/birthday_assets/balloon_blue.gif',
'../Seasonals/Resources/birthday_assets/balloon_green.gif', '../Seasonals/Resources/birthday_assets/balloon_green.gif',
@@ -142,15 +145,8 @@ function createBirthday() {
// Cake and Garland have been removed // Cake and Garland have been removed
const standardCount = 15;
const totalSymbols = symbolCount + standardCount;
let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches; let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
let finalCount = totalSymbols; let finalCount = isMobile ? symbolCountMobile : symbolCount;
if (isMobile) {
finalCount = enableRandomMobile ? totalSymbols : standardCount;
}
const useRandomDuration = enableDifferentDuration !== false; const useRandomDuration = enableDifferentDuration !== false;
@@ -228,7 +224,19 @@ function createBirthday() {
// Ensure the burst container is appended to the main document body or the birthday container // Ensure the burst container is appended to the main document body or the birthday container
createBalloonPopConfetti(document.body, cx, cy, balloonColors[randomItem]); createBalloonPopConfetti(document.body, cx, cy, balloonColors[randomItem]);
} }
}, { once: true }); });
// Reset the balloon when it reappears at the bottom of the screen
symbol.addEventListener('animationiteration', function(e) {
// Ignore bubbling events from the inner sway animation
if (e.animationName === 'birthday-rise' || e.target === symbol) {
if (innerDiv.classList.contains('popped')) {
innerDiv.classList.remove('popped');
innerDiv.style.animation = '';
innerDiv.style.pointerEvents = 'auto';
}
}
});
} }
const startRot = (Math.random() * 20) - 10; // -10 to +10 spread const startRot = (Math.random() * 20) - 10; // -10 to +10 spread
@@ -242,8 +250,7 @@ function createBirthday() {
} }
// Party Confetti // Party Confetti
const baseConfettiCount = config.ConfettiCount !== undefined ? config.ConfettiCount : 60; const confettiCount = baseConfettiCount;
const confettiCount = isMobile ? Math.floor(baseConfettiCount / 2) : baseConfettiCount;
const allColors = ['#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#808080', '#ffffff', '#000000']; const allColors = ['#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#808080', '#ffffff', '#000000'];
for (let i = 0; i < confettiCount; i++) { for (let i = 0; i < confettiCount; i++) {

View File

@@ -1,11 +1,10 @@
const config = window.SeasonalsPluginConfig?.Carnival || {}; const config = window.SeasonalsPluginConfig?.Carnival || {};
const carnival = config.EnableCarnival !== undefined ? config.EnableCarnival : true; // Enable/disable carnival const carnival = config.EnableCarnival !== undefined ? config.EnableCarnival : true; // enable/disable carnival
const randomCarnival = config.EnableRandomCarnival !== undefined ? config.EnableRandomCarnival : true; // Enable random carnival objects const carnivalCount = config.ObjectCount !== undefined ? config.ObjectCount : 120; // Number of confetti pieces to spawn
const randomCarnivalMobile = config.EnableRandomCarnivalMobile !== undefined ? config.EnableRandomCarnivalMobile : false; // Enable random carnival objects on mobile const carnivalCountMobile = config.ObjectCountMobile !== undefined ? config.ObjectCountMobile : 60; // Number of confetti pieces to spawn on mobile
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // Randomize falling and flutter speeds const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different durations
const enableSway = config.EnableCarnivalSway !== undefined ? config.EnableCarnivalSway : true; // Enable side-to-side sway animation const enableSway = config.EnableCarnivalSway !== undefined ? config.EnableCarnivalSway : true; // enable/disable carnivalsway
const carnivalCount = config.ObjectCount || 120; // Number of confetti pieces to spawn
const confettiColors = [ const confettiColors = [
'#fce18a', '#ff726d', '#b48def', '#f4306d', '#fce18a', '#ff726d', '#b48def', '#f4306d',
@@ -41,7 +40,6 @@ function toggleCarnival() {
} }
} }
// observe changes in the DOM
const observer = new MutationObserver(toggleCarnival); const observer = new MutationObserver(toggleCarnival);
observer.observe(document.body, { observer.observe(document.body, {
childList: true, childList: true,
@@ -149,19 +147,8 @@ function createConfettiPiece(container, isInitial = false) {
container.appendChild(wrapper); container.appendChild(wrapper);
} }
function addRandomCarnivalObjects(count) {
const carnivalContainer = document.querySelector('.carnival-container');
if (!carnivalContainer) return;
console.log('Adding random carnival confetti');
for (let i = 0; i < count; i++) {
createConfettiPiece(carnivalContainer, true);
}
}
// initialize standard carnival objects // initialize standard carnival objects
function initCarnivalObjects() { function initCarnivalObjects(count) {
let container = document.querySelector('.carnival-container'); let container = document.querySelector('.carnival-container');
if (!container) { if (!container) {
container = document.createElement("div"); container = document.createElement("div");
@@ -171,7 +158,7 @@ function initCarnivalObjects() {
} }
// Initial confetti // Initial confetti
for (let i = 0; i < 60; i++) { for (let i = 0; i < count; i++) {
createConfettiPiece(container, true); createConfettiPiece(container, true);
} }
} }
@@ -179,13 +166,12 @@ function initCarnivalObjects() {
// initialize carnival // initialize carnival
function initializeCarnival() { function initializeCarnival() {
if (!carnival) return; if (!carnival) return;
initCarnivalObjects();
toggleCarnival();
const screenWidth = window.innerWidth; const isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
if (randomCarnival && (screenWidth > 768 || randomCarnivalMobile)) { const count = !isMobile ? carnivalCount : carnivalCountMobile;
addRandomCarnivalObjects(carnivalCount);
} initCarnivalObjects(count);
toggleCarnival();
} }
initializeCarnival(); initializeCarnival();

View File

@@ -1,10 +1,9 @@
const config = window.SeasonalsPluginConfig?.CherryBlossom || {}; const config = window.SeasonalsPluginConfig?.CherryBlossom || {};
const cherryBlossom = config.EnableCherryBlossom !== undefined ? config.EnableCherryBlossom : true; const cherryBlossom = config.EnableCherryBlossom !== undefined ? config.EnableCherryBlossom : true; // enable/disable cherryblossom
const petalCount = config.PetalCount || 25; const petalCount = config.PetalCount !== undefined ? config.PetalCount : 25; // count of petal
const randomCherryBlossom = config.EnableRandomCherryBlossom !== undefined ? config.EnableRandomCherryBlossom : true; const petalCountMobile = config.PetalCountMobile !== undefined ? config.PetalCountMobile : 10; // count of petal on mobile
const randomCherryBlossomMobile = config.EnableRandomCherryBlossomMobile !== undefined ? config.EnableRandomCherryBlossomMobile : false; const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different durations
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true;
let msgPrinted = false; let msgPrinted = false;
@@ -67,16 +66,7 @@ function createPetal(container) {
container.appendChild(petal); container.appendChild(petal);
} }
function addRandomObjects() { function initObjects(count) {
const container = document.querySelector('.cherryblossom-container');
if (!container) return;
for (let i = 0; i < petalCount; i++) {
createPetal(container);
}
}
function initObjects() {
let container = document.querySelector('.cherryblossom-container'); let container = document.querySelector('.cherryblossom-container');
if (!container) { if (!container) {
container = document.createElement("div"); container = document.createElement("div");
@@ -86,20 +76,19 @@ function initObjects() {
} }
// Initial batch // Initial batch
for (let i = 0; i < 15; i++) { for (let i = 0; i < count; i++) {
createPetal(container); createPetal(container);
} }
} }
function initializeCherryBlossom() { function initializeCherryBlossom() {
if (!cherryBlossom) return; if (!cherryBlossom) return;
initObjects();
toggleCherryBlossom();
const screenWidth = window.innerWidth; const isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
if (randomCherryBlossom && (screenWidth > 768 || randomCherryBlossomMobile)) { const count = !isMobile ? petalCount : petalCountMobile;
addRandomObjects();
} initObjects(count);
toggleCherryBlossom();
} }
initializeCherryBlossom(); initializeCherryBlossom();

View File

@@ -23,38 +23,13 @@
text-shadow: 0 0 5px #000; text-shadow: 0 0 5px #000;
user-select: none; user-select: none;
cursor: default; cursor: default;
-webkit-user-select: none;
-webkit-animation-name: christmas-fall, christmas-shake;
-webkit-animation-duration: 10s, 3s;
-webkit-animation-timing-function: linear, ease-in-out;
-webkit-animation-iteration-count: infinite, infinite;
animation-name: christmas-fall, christmas-shake; animation-name: christmas-fall, christmas-shake;
animation-duration: 10s, 3s; animation-duration: 10s, 3s;
animation-timing-function: linear, ease-in-out; animation-timing-function: linear, ease-in-out;
animation-iteration-count: infinite, infinite; animation-iteration-count: infinite, infinite;
} }
@-webkit-keyframes christmas-fall {
0% {
translate: 0 -10vh;
}
100% {
translate: 0 110vh;
}
}
@-webkit-keyframes christmas-shake {
0%,
100% {
transform: translateX(0);
}
50% {
transform: translateX(80px);
}
}
@keyframes christmas-fall { @keyframes christmas-fall {
0% { 0% {
@@ -75,64 +50,4 @@
50% { 50% {
transform: translateX(80px); transform: translateX(80px);
} }
}
.christmas:nth-of-type(0) {
left: 0%;
animation-delay: 0s, 0s;
}
.christmas:nth-of-type(1) {
left: 10%;
animation-delay: 1s, 1s;
}
.christmas:nth-of-type(2) {
left: 20%;
animation-delay: 6s, 0.5s;
}
.christmas:nth-of-type(3) {
left: 30%;
animation-delay: 4s, 2s;
}
.christmas:nth-of-type(4) {
left: 40%;
animation-delay: 2s, 2s;
}
.christmas:nth-of-type(5) {
left: 50%;
animation-delay: 8s, 3s;
}
.christmas:nth-of-type(6) {
left: 60%;
animation-delay: 6s, 2s;
}
.christmas:nth-of-type(7) {
left: 70%;
animation-delay: 2.5s, 1s;
}
.christmas:nth-of-type(8) {
left: 80%;
animation-delay: 1s, 0s;
}
.christmas:nth-of-type(9) {
left: 90%;
animation-delay: 3s, 1.5s;
}
.christmas:nth-of-type(10) {
left: 25%;
animation-delay: 2s, 0s;
}
.christmas:nth-of-type(11) {
left: 65%;
animation-delay: 4s, 2.5s;
} }

View File

@@ -1,10 +1,9 @@
const config = window.SeasonalsPluginConfig?.Christmas || {}; const config = window.SeasonalsPluginConfig?.Christmas || {};
const christmas = config.EnableChristmas !== undefined ? config.EnableChristmas : true; // enable/disable christmas const christmas = config.EnableChristmas !== undefined ? config.EnableChristmas : true; // enable/disable christmas
const randomChristmas = config.EnableRandomChristmas !== undefined ? config.EnableRandomChristmas : true; // enable random Christmas const enableDiffrentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different durations
const randomChristmasMobile = config.EnableRandomChristmasMobile !== undefined ? config.EnableRandomChristmasMobile : false; // enable random Christmas on mobile devices (Warning: High values may affect performance) const christmasCount = config.SymbolCount !== undefined ? config.SymbolCount : 25; // count of symbol
const enableDiffrentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different duration for the random Christmas symbols const christmasCountMobile = config.SymbolCountMobile !== undefined ? config.SymbolCountMobile : 10; // count of symbol on mobile
const christmasCount = config.SymbolCount || 25; // count of random extra christmas
// Array of christmas characters // Array of christmas characters
const christmasSymbols = ['❆', '🎁', '❄️', '🎁', '🎅', '🎊', '🎁', '🎉']; const christmasSymbols = ['❆', '🎁', '❄️', '🎁', '🎅', '🎊', '🎁', '🎉'];
@@ -45,11 +44,16 @@ observer.observe(document.body, {
attributes: true attributes: true
}); });
function addRandomChristmas(count) { function initChristmas(count) {
const christmasContainer = document.querySelector('.christmas-container'); // get the christmas container let christmasContainer = document.querySelector('.christmas-container'); // get the christmas container
if (!christmasContainer) return; // exit if christmas container is not found if (!christmasContainer) {
christmasContainer = document.createElement("div");
christmasContainer.className = "christmas-container";
christmasContainer.setAttribute("aria-hidden", "true");
document.body.appendChild(christmasContainer);
}
console.log('Adding random christmas'); console.log('Adding christmas');
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
// create a new christmas element // create a new christmas element
@@ -61,8 +65,8 @@ function addRandomChristmas(count) {
// set random horizontal position, animation delay and size(uncomment lines to enable) // set random horizontal position, animation delay and size(uncomment lines to enable)
const randomLeft = Math.random() * 100; // position (0% to 100%) const randomLeft = Math.random() * 100; // position (0% to 100%)
const randomAnimationDelay = Math.random() * 12 + 8; // delay (8s to 12s) const randomAnimationDelay = -(Math.random() * 16); // delay (-16s to 0s)
const randomAnimationDelay2 = Math.random() * 5 + 3; // delay (0s to 5s) const randomAnimationDelay2 = -(Math.random() * 5); // delay (-5s to 0s)
// apply styles // apply styles
christmasDiv.style.left = `${randomLeft}%`; christmasDiv.style.left = `${randomLeft}%`;
@@ -78,46 +82,18 @@ function addRandomChristmas(count) {
// add the christmas to the container // add the christmas to the container
christmasContainer.appendChild(christmasDiv); christmasContainer.appendChild(christmasDiv);
} }
console.log('Random christmas added'); console.log('Christmas added');
} }
// initialize standard christmas // initialize christmas
function initChristmas() {
const christmasContainer = document.querySelector('.christmas-container') || document.createElement("div");
if (!document.querySelector('.christmas-container')) {
christmasContainer.className = "christmas-container";
christmasContainer.setAttribute("aria-hidden", "true");
document.body.appendChild(christmasContainer);
}
// create the 12 standard christmas
for (let i = 0; i < 12; i++) {
const christmasDiv = document.createElement('div');
christmasDiv.className = 'christmas';
christmasDiv.textContent = christmasSymbols[Math.floor(Math.random() * christmasSymbols.length)];
// set random animation duration
if (enableDiffrentDuration) {
const randomAnimationDuration = Math.random() * 10 + 6; // delay (6s to 10s)
const randomAnimationDuration2 = Math.random() * 5 + 2; // delay (2s to 5s)
christmasDiv.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`;
}
christmasContainer.appendChild(christmasDiv);
}
}
// initialize christmas and add random christmas symbols
function initializeChristmas() { function initializeChristmas() {
if (!christmas) return; // exit if christmas is disabled if (!christmas) return; // exit if christmas is disabled
initChristmas();
toggleChristmas();
const screenWidth = window.innerWidth; // get the screen width to detect mobile devices const isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
if (randomChristmas && (screenWidth > 768 || randomChristmasMobile)) { // add random christmas only on larger screens, unless enabled for mobile devices const count = !isMobile ? christmasCount : christmasCountMobile;
addRandomChristmas(christmasCount);
} initChristmas(count);
toggleChristmas();
} }
initializeChristmas(); initializeChristmas();

View File

@@ -7,9 +7,11 @@
pointer-events: none; pointer-events: none;
z-index: 1000; z-index: 1000;
overflow: hidden; overflow: hidden;
contain: layout paint;
} }
.earthday-meadow { .earthday-meadow {
will-change: transform;
position: absolute; position: absolute;
bottom: 0; bottom: 0;
left: 0; left: 0;
@@ -25,6 +27,7 @@
} }
.earthday-sway { .earthday-sway {
will-change: transform;
transform-origin: bottom center; transform-origin: bottom center;
animation: sway-grass 4s ease-in-out infinite alternate; animation: sway-grass 4s ease-in-out infinite alternate;
} }

View File

@@ -1,7 +1,7 @@
const config = window.SeasonalsPluginConfig?.EarthDay || {}; const config = window.SeasonalsPluginConfig?.EarthDay || {};
const enabled = config.EnableEarthDay !== undefined ? config.EnableEarthDay : true; const enabled = config.EnableEarthDay !== undefined ? config.EnableEarthDay : true; // enable/disable earthday
const vineCount = config.VineCount || 4; const vineCount = config.VineCount !== undefined ? config.VineCount : 4; // count of vine
const flowerColors = ['#FF69B4', '#FFD700', '#87CEFA', '#FF4500', '#BA55D3', '#FFA500', '#FF1493']; const flowerColors = ['#FF69B4', '#FFD700', '#87CEFA', '#FF4500', '#BA55D3', '#FFA500', '#FF1493'];

View File

@@ -9,6 +9,7 @@
z-index: 10000; z-index: 10000;
contain: strict; contain: strict;
overflow: hidden; overflow: hidden;
contain: layout paint;
} }
.easter-grass-container { .easter-grass-container {
@@ -38,6 +39,7 @@
/* sway */ /* sway */
.easter-sway { .easter-sway {
will-change: transform;
transform-origin: bottom center; transform-origin: bottom center;
animation: easter-wind-sway 6s ease-in-out infinite alternate; animation: easter-wind-sway 6s ease-in-out infinite alternate;
} }

View File

@@ -1,18 +1,19 @@
const config = window.SeasonalsPluginConfig?.Easter || {}; const config = window.SeasonalsPluginConfig?.Easter || {};
const easter = config.EnableEaster !== undefined ? config.EnableEaster : true; const easter = config.EnableEaster !== undefined ? config.EnableEaster : true; // enable/disable easter
const enableBunny = config.EnableBunny !== undefined ? config.EnableBunny : true; const enableBunny = config.EnableBunny !== undefined ? config.EnableBunny : true; // enable/disable bunny
const minBunnyRestTime = config.MinBunnyRestTime !== undefined ? config.MinBunnyRestTime : 2000; // timing parameter
const maxBunnyRestTime = config.MaxBunnyRestTime !== undefined ? config.MaxBunnyRestTime : 5000; // timing parameter
const eggCount = config.EggCount !== undefined ? config.EggCount : 15; // count of egg
/* MARK: Bunny movement config */ /* MARK: Bunny movement config */
const jumpDistanceVw = 5; // Distance in vw the bunny covers per jump const jumpDistanceVw = 5; // Distance in vw the bunny covers per jump
const jumpDurationMs = 770; // Time in ms the bunny spends moving during a jump const jumpDurationMs = 770; // Time in ms the bunny spends moving during a jump
const pauseDurationMs = 116.6666; // Time in ms the bunny pauses between jumps const pauseDurationMs = 116.6666; // Time in ms the bunny pauses between jumps
const minBunnyRestTime = config.MinBunnyRestTime || 2000;
const maxBunnyRestTime = config.MaxBunnyRestTime || 5000;
const eggCount = config.EggCount || 15;
const rabbit = "../Seasonals/Resources/easter_images/Osterhase.gif"; const rabbit = "../Seasonals/Resources/easter_images/Osterhase.gif";
// Credit: https://flaticon.com
const easterEggImages = [ const easterEggImages = [
"../Seasonals/Resources/easter_images/egg_1.png", "../Seasonals/Resources/easter_images/egg_1.png",
"../Seasonals/Resources/easter_images/egg_2.png", "../Seasonals/Resources/easter_images/egg_2.png",
@@ -197,6 +198,8 @@ function animateRabbit(rabbit) {
rabbit.style.transition = 'none'; rabbit.style.transition = 'none';
const transformScale = startFromLeft ? 'scaleX(-1)' : ''; const transformScale = startFromLeft ? 'scaleX(-1)' : '';
// Set bounding box center-of-gravity shift when graphic is flipped
rabbit.style.transformOrigin = startFromLeft ? '59% 50%' : '50% 50%';
rabbit.style.transform = `translateX(${currentX}vw) ${transformScale}`; rabbit.style.transform = `translateX(${currentX}vw) ${transformScale}`;
const loopDurationMs = jumpDurationMs + pauseDurationMs; const loopDurationMs = jumpDurationMs + pauseDurationMs;
@@ -211,10 +214,8 @@ function animateRabbit(rabbit) {
if (!startTime) { if (!startTime) {
startTime = timestamp; startTime = timestamp;
// resetting gif, forces the browser to restart the GIF from the first frame (crucial for syncing hops with movement)
const currSrc = rabbit.src.split('?')[0]; const currSrc = rabbit.src.split('?')[0];
rabbit.src = ''; rabbit.src = currSrc + '?t=' + Date.now();
rabbit.src = currSrc;
} }
const elapsed = timestamp - startTime; const elapsed = timestamp - startTime;
@@ -234,7 +235,6 @@ function animateRabbit(rabbit) {
currentX = startX + (completedLoops * jumpDistanceVw + currentLoopDistance) * direction; currentX = startX + (completedLoops * jumpDistanceVw + currentLoopDistance) * direction;
// Update DOM without CSS transitions
rabbit.style.transform = `translateX(${currentX}vw) ${transformScale}`; rabbit.style.transform = `translateX(${currentX}vw) ${transformScale}`;
// Check if finished crossing // Check if finished crossing
@@ -243,6 +243,7 @@ function animateRabbit(rabbit) {
isAnimating = false; isAnimating = false;
rabbitTimeout = setTimeout(() => { rabbitTimeout = setTimeout(() => {
if (!document.body.contains(rabbit)) return;
animateRabbit(document.querySelector('#rabbit')); animateRabbit(document.querySelector('#rabbit'));
}, restTime); }, restTime);
return; return;

View File

@@ -9,6 +9,7 @@
z-index: 10; z-index: 10;
contain: strict; contain: strict;
overflow: hidden; overflow: hidden;
contain: layout paint;
} }
.eid-symbol { .eid-symbol {
@@ -18,12 +19,14 @@
} }
.eid-symbol.floating-star { .eid-symbol.floating-star {
will-change: opacity;
opacity: 0; opacity: 0;
animation: eid-twinkle 4s ease-in-out infinite; animation: eid-twinkle 4s ease-in-out infinite;
mix-blend-mode: screen; mix-blend-mode: screen;
} }
.lantern-rope { .lantern-rope {
will-change: transform;
position: absolute; position: absolute;
top: 0; top: 0;
width: 2px; width: 2px;

View File

@@ -1,5 +1,7 @@
const config = window.SeasonalsPluginConfig?.Eid || {}; const config = window.SeasonalsPluginConfig?.Eid || {};
const eid = config.EnableEid !== undefined ? config.EnableEid : true; const eid = config.EnableEid !== undefined ? config.EnableEid : true; // enable/disable eid
const lanternCount = config.LanternCount !== undefined ? config.LanternCount : 8; // count of lantern
const lanternCountMobile = config.LanternCountMobile !== undefined ? config.LanternCountMobile : 3; // count of lantern on mobile
const eidSymbols = ['🌙', '⭐', '✨']; const eidSymbols = ['🌙', '⭐', '✨'];
@@ -39,11 +41,13 @@ observer.observe(document.body, {
function createEid(container) { function createEid(container) {
const starCount = 20; const starCount = 20;
const lanternCount = Math.floor(Math.random() * 3) + 4; // 4 to 6 lanterns
let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
let activeLanternCount = isMobile ? lanternCountMobile : lanternCount;
// Create evenly spaced lanterns // Create evenly spaced lanterns
const segmentWidth = 100 / lanternCount; const segmentWidth = 100 / activeLanternCount;
for (let i = 0; i < lanternCount; i++) { for (let i = 0; i < activeLanternCount; i++) {
const symbol = document.createElement('div'); const symbol = document.createElement('div');
symbol.className = 'eid-symbol lantern-rope'; symbol.className = 'eid-symbol lantern-rope';

View File

@@ -1,15 +1,14 @@
const config = window.SeasonalsPluginConfig?.Eurovision || {}; const config = window.SeasonalsPluginConfig?.Eurovision || {};
const enabled = config.EnableEurovision !== undefined ? config.EnableEurovision : true; const enabled = config.EnableEurovision !== undefined ? config.EnableEurovision : true; // enable/disable eurovision
const elementCount = config.SymbolCount || 25; const elementCount = config.SymbolCount !== undefined ? config.SymbolCount : 25; // count of notes
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different durations
const enableColorfulNotes = config.EnableColorfulNotes !== undefined ? config.EnableColorfulNotes : true; const enableColorfulNotes = config.EnableColorfulNotes !== undefined ? config.EnableColorfulNotes : true; // enable/disable colorful notes
const eurovisionColorsStr = config.EurovisionColors || '#ff0026ff, #17a6ffff, #32d432ff, #FFD700, #f0821bff, #f826f8ff'; const eurovisionColorsStr = config.EurovisionColors !== undefined ? config.EurovisionColors : '#ff0026ff, #17a6ffff, #32d432ff, #FFD700, #f0821bff, #f826f8ff'; // colors to use
const glowSize = config.EurovisionGlowSize !== undefined ? config.EurovisionGlowSize : 2; const glowSize = config.EurovisionGlowSize !== undefined ? config.EurovisionGlowSize : 2; // size of eurovision glow
let msgPrinted = false; let msgPrinted = false;
// Toggle Function
function toggleEurovision() { function toggleEurovision() {
const container = document.querySelector('.eurovision-container'); const container = document.querySelector('.eurovision-container');
if (!container) return; if (!container) return;

View File

@@ -22,8 +22,9 @@
/* Film grain */ /* Film grain */
.filmnoir-grain { .filmnoir-grain {
will-change: transform, opacity;
position: absolute; position: absolute;
top: -50%; top: 0;
left: -50%; left: -50%;
width: 200%; width: 200%;
height: 200%; height: 200%;
@@ -32,6 +33,7 @@
pointer-events: none; pointer-events: none;
mix-blend-mode: overlay; mix-blend-mode: overlay;
opacity: 0.3; opacity: 0.3;
translate: 0 -50vh;
} }
/* Vignette */ /* Vignette */
@@ -48,6 +50,7 @@
/* Occasional flicker and scratch */ /* Occasional flicker and scratch */
.filmnoir-scratches { .filmnoir-scratches {
will-change: opacity;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;

View File

@@ -1,5 +1,5 @@
const config = window.SeasonalsPluginConfig?.FilmNoir || {}; const config = window.SeasonalsPluginConfig?.FilmNoir || {};
const filmnoir = config.EnableFilmNoir !== undefined ? config.EnableFilmNoir : true; const filmnoir = config.EnableFilmNoir !== undefined ? config.EnableFilmNoir : true; // enable/disable filmnoir
let msgPrinted = false; let msgPrinted = false;

View File

@@ -11,9 +11,10 @@
} }
.rocket-trail { .rocket-trail {
will-change: transform;
position: absolute; position: absolute;
left: var(--trailX); left: var(--trailX);
top: var(--trailStartY); top: 0;
width: 4px; width: 4px;
/* activate the following for rocket trail */ /* activate the following for rocket trail */
@@ -28,6 +29,7 @@
box-shadow: 0 0 8px 2px white;*/ box-shadow: 0 0 8px 2px white;*/
animation: rocket-trail-animation 1s linear forwards; animation: rocket-trail-animation 1s linear forwards;
translate: 0 var(--trailStartY);
} }
@keyframes rocket-trail-animation { @keyframes rocket-trail-animation {
@@ -56,6 +58,7 @@
} }
.firework { .firework {
will-change: transform;
position: absolute; position: absolute;
width: 5px; width: 5px;
height: 5px; height: 5px;

View File

@@ -2,10 +2,10 @@ const config = window.SeasonalsPluginConfig?.Fireworks || {};
const fireworks = config.EnableFireworks !== undefined ? config.EnableFireworks : true; // enable/disable fireworks const fireworks = config.EnableFireworks !== undefined ? config.EnableFireworks : true; // enable/disable fireworks
const scrollFireworks = config.ScrollFireworks !== undefined ? config.ScrollFireworks : true; // enable fireworks to scroll with page content const scrollFireworks = config.ScrollFireworks !== undefined ? config.ScrollFireworks : true; // enable fireworks to scroll with page content
const particlesPerFirework = config.ParticleCount || 50; // count of particles per firework (Warning: High values may affect performance) const particlesPerFirework = config.ParticleCount !== undefined ? config.ParticleCount : 50; // count of particles per firework
const minFireworks = config.MinFireworks || 3; // minimum number of simultaneous fireworks const minFireworks = config.MinFireworks !== undefined ? config.MinFireworks : 3; // minimum number of simultaneous fireworks
const maxFireworks = config.MaxFireworks || 6; // maximum number of simultaneous fireworks const maxFireworks = config.MaxFireworks !== undefined ? config.MaxFireworks : 6; // maximum number of simultaneous fireworks
const intervalOfFireworks = config.LaunchInterval || 3200; // interval for the fireworks in milliseconds const intervalOfFireworks = config.LaunchInterval !== undefined ? config.LaunchInterval : 3200; // interval for the fireworks in milliseconds
// array of color palettes for the fireworks // array of color palettes for the fireworks
const colorPalettes = [ const colorPalettes = [
@@ -162,6 +162,7 @@ function startFireworks() {
} }
fireworksInterval = setInterval(() => { fireworksInterval = setInterval(() => {
if (!document.body.contains(fireworkContainer)) { clearInterval(fireworksInterval); return; }
const randomCount = Math.floor(Math.random() * maxFireworks) + minFireworks; const randomCount = Math.floor(Math.random() * maxFireworks) + minFireworks;
for (let i = 0; i < randomCount; i++) { for (let i = 0; i < randomCount; i++) {
setTimeout(() => { setTimeout(() => {

View File

@@ -1,5 +1,5 @@
const config = window.SeasonalsPluginConfig?.Friday13 || {}; const config = window.SeasonalsPluginConfig?.Friday13 || {};
const friday13 = config.EnableFriday13 !== undefined ? config.EnableFriday13 : true; const friday13 = config.EnableFriday13 !== undefined ? config.EnableFriday13 : true; // enable/disable friday13
let msgPrinted = false; let msgPrinted = false;
@@ -61,12 +61,12 @@ function createFriday13(container) {
cat.parentNode.removeChild(cat); cat.parentNode.removeChild(cat);
} }
// Respawn with random delay between 5 to 25 seconds // Respawn with random delay between 5 to 25 seconds
setTimeout(spawnCat, Math.random() * 20000 + 5000); setTimeout(() => { if (document.body.contains(container)) spawnCat(); }, Math.random() * 20000 + 5000);
}, (catWalkDurationSeconds * 1000) + 500); // Wait for duration + 500ms safety margin }, (catWalkDurationSeconds * 1000) + 500); // Wait for duration + 500ms safety margin
} }
// Initial spawn with random delay // Initial spawn with random delay
setTimeout(spawnCat, Math.random() * 5000); setTimeout(() => { if (document.body.contains(container)) spawnCat(); }, Math.random() * 5000);
} }
function initializeFriday13() { function initializeFriday13() {

View File

@@ -9,16 +9,17 @@
z-index: 10; z-index: 10;
overflow: hidden; overflow: hidden;
contain: strict; contain: strict;
contain: layout paint;
} }
.frost-layer { .frost-layer {
will-change: transform;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none; pointer-events: none;
/* A glowing white-blue gradient from edges */
background: radial-gradient(ellipse at center, transparent 60%, rgba(180, 220, 255, 0.4) 100%); background: radial-gradient(ellipse at center, transparent 60%, rgba(180, 220, 255, 0.4) 100%);
box-shadow: inset 0 0 60px rgba(200, 230, 255, 0.5), inset 0 0 120px rgba(255, 255, 255, 0.3); box-shadow: inset 0 0 60px rgba(200, 230, 255, 0.5), inset 0 0 120px rgba(255, 255, 255, 0.3);
@@ -27,14 +28,13 @@
animation: frost-creep 4s ease-out forwards; animation: frost-creep 4s ease-out forwards;
} }
/* Subtle repeating star/crystal pattern */
.frost-crystals { .frost-crystals {
will-change: transform;
position: absolute; position: absolute;
top: -5%; top: 0;
left: -5%; left: -5%;
width: 110%; width: 110%;
height: 110%; height: 110%;
/* Use multi-layered star patterns for a random, crystalline spread */
background-image: background-image:
url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60"><circle cx="10" cy="10" r="1.5" fill="rgba(255,255,255,0.2)"/><circle cx="40" cy="30" r="1" fill="rgba(255,255,255,0.15)"/><circle cx="20" cy="50" r="2" fill="rgba(255,255,255,0.1)"/><path d="M50 10 L51 15 L56 16 L51 17 L50 22 L49 17 L44 16 L49 15 Z" fill="rgba(255,255,255,0.2)"/></svg>'), url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60"><circle cx="10" cy="10" r="1.5" fill="rgba(255,255,255,0.2)"/><circle cx="40" cy="30" r="1" fill="rgba(255,255,255,0.15)"/><circle cx="20" cy="50" r="2" fill="rgba(255,255,255,0.1)"/><path d="M50 10 L51 15 L56 16 L51 17 L50 22 L49 17 L44 16 L49 15 Z" fill="rgba(255,255,255,0.2)"/></svg>'),
url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40"><circle cx="5" cy="20" r="1" fill="rgba(255,255,255,0.15)"/><circle cx="25" cy="5" r="1.5" fill="rgba(255,255,255,0.1)"/><path d="M20 20 L21 23 L24 24 L21 25 L20 28 L19 25 L16 24 L19 23 Z" fill="rgba(255,255,255,0.15)"/></svg>'), url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40"><circle cx="5" cy="20" r="1" fill="rgba(255,255,255,0.15)"/><circle cx="25" cy="5" r="1.5" fill="rgba(255,255,255,0.1)"/><path d="M20 20 L21 23 L24 24 L21 25 L20 28 L19 25 L16 24 L19 23 Z" fill="rgba(255,255,255,0.15)"/></svg>'),
@@ -43,12 +43,9 @@
background-size: 110px 110px, 60px 60px, 30px 30px; background-size: 110px 110px, 60px 60px, 30px 30px;
background-position: 0 0, 15px 15px, 5px 10px; background-position: 0 0, 15px 15px, 5px 10px;
mix-blend-mode: overlay; mix-blend-mode: overlay;
/* Mask out the center so crystals only appear strongly on the edges */
-webkit-mask-image: radial-gradient(ellipse at center, transparent 50%, black 100%);
mask-image: radial-gradient(ellipse at center, transparent 50%, black 100%); mask-image: radial-gradient(ellipse at center, transparent 50%, black 100%);
animation: frost-shimmer 6s infinite alternate ease-in-out; animation: frost-shimmer 6s infinite alternate ease-in-out;
translate: 0 -5vh;
} }
@keyframes frost-creep { @keyframes frost-creep {

View File

@@ -1,6 +1,6 @@
const config = window.SeasonalsPluginConfig?.Frost || {}; const config = window.SeasonalsPluginConfig?.Frost || {};
const frost = config.EnableFrost !== undefined ? config.EnableFrost : true; const frost = config.EnableFrost !== undefined ? config.EnableFrost : true; // enable/disable frost
let msgPrinted = false; let msgPrinted = false;

View File

@@ -7,25 +7,17 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none; pointer-events: none;
z-index: 10000; z-index: 10;
contain: layout paint; contain: layout paint;
} }
.halloween { .halloween {
will-change: transform;
position: fixed; position: fixed;
bottom: -10%; bottom: -10%;
z-index: 15; z-index: 15;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none; user-select: none;
-webkit-user-select: none;
cursor: default; cursor: default;
-webkit-animation-name: halloween-fall, halloween-shake;
-webkit-animation-duration: 10s, 3s;
-webkit-animation-timing-function: linear, ease-in-out;
-webkit-animation-iteration-count: infinite, infinite;
-webkit-animation-play-state: running, running;
animation-name: halloween-fall, halloween-shake; animation-name: halloween-fall, halloween-shake;
animation-duration: 10s, 3s; animation-duration: 10s, 3s;
animation-timing-function: linear, ease-in-out; animation-timing-function: linear, ease-in-out;
@@ -33,29 +25,7 @@
animation-play-state: running, running animation-play-state: running, running
} }
@-webkit-keyframes halloween-fall {
0% {
bottom: -10%;
}
100% {
bottom: 110%;
}
}
@-webkit-keyframes halloween-shake {
0%,
100% {
-webkit-transform: translateX(0);
transform: translateX(0)
}
50% {
-webkit-transform: translateX(80px);
transform: translateX(80px)
}
}
@keyframes halloween-fall { @keyframes halloween-fall {
0% { 0% {
@@ -79,78 +49,6 @@
} }
} }
.halloween:nth-of-type(0) {
left: 1%;
-webkit-animation-delay: 0s, 0s;
animation-delay: 0s, 0s;
}
.halloween:nth-of-type(1) {
left: 10%;
-webkit-animation-delay: -1s, -1s;
animation-delay: -1s, -1s;
}
.halloween:nth-of-type(2) {
left: 20%;
-webkit-animation-delay: -2s, -2s;
animation-delay: -2s, -2s;
}
.halloween:nth-of-type(3) {
left: 30%;
-webkit-animation-delay: -3s, -3s;
animation-delay: -3s, -3s;
}
.halloween:nth-of-type(4) {
left: 40%;
-webkit-animation-delay: -4s, -4s;
animation-delay: -4s, -4s;
}
.halloween:nth-of-type(5) {
left: 50%;
-webkit-animation-delay: -5s, -5s;
animation-delay: -5s, -5s;
}
.halloween:nth-of-type(6) {
left: 60%;
-webkit-animation-delay: -6s, -6s;
animation-delay: -6s, -6s;
}
.halloween:nth-of-type(7) {
left: 70%;
-webkit-animation-delay: -7s, -7s;
animation-delay: -7s, -7s;
}
.halloween:nth-of-type(8) {
left: 80%;
-webkit-animation-delay: -8s, -8s;
animation-delay: -8s, -8s;
}
.halloween:nth-of-type(9) {
left: 90%;
-webkit-animation-delay: -9s, -9s;
animation-delay: -9s, -9s;
}
.halloween:nth-of-type(10) {
left: 25%;
-webkit-animation-delay: -10s, -10s;
animation-delay: -10s, -10s;
}
.halloween:nth-of-type(11) {
left: 65%;
-webkit-animation-delay: -11s, -11s;
animation-delay: -11s, -11s;
}
/* --- Fog Layer --- */ /* --- Fog Layer --- */
.halloween-fog-layer { .halloween-fog-layer {
position: absolute; position: absolute;
@@ -162,7 +60,6 @@
z-index: 1000; z-index: 1000;
overflow: hidden; overflow: hidden;
mask-image: linear-gradient(to top, black, transparent); mask-image: linear-gradient(to top, black, transparent);
-webkit-mask-image: linear-gradient(to top, black, transparent);
} }
.halloween-fog-blob { .halloween-fog-blob {
position: absolute; position: absolute;
@@ -174,10 +71,12 @@
filter: blur(15px); filter: blur(15px);
} }
.halloween-fog-blob:nth-child(1) { .halloween-fog-blob:nth-child(1) {
will-change: transform;
left: -20vw; left: -20vw;
animation: fog-float1 25s ease-in-out infinite alternate; animation: fog-float1 25s ease-in-out infinite alternate;
} }
.halloween-fog-blob:nth-child(2) { .halloween-fog-blob:nth-child(2) {
will-change: transform;
left: -50vw; left: -50vw;
background: radial-gradient(ellipse at center, rgba(100, 110, 120, 0.3) 0%, transparent 65%); background: radial-gradient(ellipse at center, rgba(100, 110, 120, 0.3) 0%, transparent 65%);
animation: fog-float2 35s ease-in-out infinite alternate; animation: fog-float2 35s ease-in-out infinite alternate;
@@ -196,7 +95,7 @@
/* --- Spiders --- */ /* --- Spiders --- */
.halloween-spider-wrapper { .halloween-spider-wrapper {
position: absolute; position: absolute;
top: -50px; top: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@@ -204,8 +103,10 @@
transform-origin: top; transform-origin: top;
will-change: transform; will-change: transform;
pointer-events: auto; pointer-events: auto;
padding: 20px; /* Increase hit area safely */ padding: 20px; /* Increase hit area */
translate: 0 -50px;
} }
.halloween-thread { .halloween-thread {
width: 30px; /* Wider hit area for mouse interaction */ width: 30px; /* Wider hit area for mouse interaction */
height: 100vh; height: 100vh;
@@ -223,12 +124,12 @@
background: linear-gradient(to bottom, rgba(200, 200, 200, 0.1), rgba(200, 200, 200, 0.6)); background: linear-gradient(to bottom, rgba(200, 200, 200, 0.1), rgba(200, 200, 200, 0.6));
} }
.halloween-spider { .halloween-spider {
will-change: transform;
animation: spider-swing 3s ease-in-out infinite alternate; animation: spider-swing 3s ease-in-out infinite alternate;
transform-origin: top center; transform-origin: top center;
} }
/* MARK: SPIDER SWAY CONFIGURATION */ /* MARK: SPIDER SWAY CONFIGURATION */
/* Adjust degrees in 'rotate(...)' to change how far spider and thread swing in wind. */
@keyframes wind-sway { @keyframes wind-sway {
0% { transform: rotate(0deg); } 0% { transform: rotate(0deg); }
25% { transform: rotate(2deg); } 25% { transform: rotate(2deg); }

View File

@@ -1,12 +1,11 @@
const config = window.SeasonalsPluginConfig?.Halloween || {}; const config = window.SeasonalsPluginConfig?.Halloween || {};
const halloween = config.EnableHalloween !== undefined ? config.EnableHalloween : true; // enable/disable halloween const halloween = config.EnableHalloween !== undefined ? config.EnableHalloween : true; // enable/disable halloween
const randomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true; // enable more random symbols const enableDiffrentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different durations
const randomSymbolsMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false; // enable random symbols on mobile devices (Warning: High values may affect performance) const enableSpiders = config.EnableSpiders !== undefined ? config.EnableSpiders : true; // enable/disable spiders
const enableDiffrentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different duration for the random halloween symbols const enableMice = config.EnableMice !== undefined ? config.EnableMice : true; // enable/disable mice
const enableSpiders = config.EnableSpiders !== undefined ? config.EnableSpiders : true; const halloweenCount = config.SymbolCount !== undefined ? config.SymbolCount : 25; // count of symbols
const enableMice = config.EnableMice !== undefined ? config.EnableMice : true; const halloweenCountMobile = config.SymbolCountMobile !== undefined ? config.SymbolCountMobile : 10; // count of symbols on mobile
const halloweenCount = config.SymbolCount || 25; // count of random extra symbols
const images = [ const images = [
"../Seasonals/Resources/halloween_images/ghost_20x20.png", "../Seasonals/Resources/halloween_images/ghost_20x20.png",
@@ -14,7 +13,7 @@ const images = [
"../Seasonals/Resources/halloween_images/pumpkin_20x20.png", "../Seasonals/Resources/halloween_images/pumpkin_20x20.png",
]; ];
let msgPrinted = false; // flag to prevent multiple console messages let msgPrinted = false;
// function to check and control the halloween // function to check and control the halloween
function toggleHalloween() { function toggleHalloween() {
@@ -42,7 +41,6 @@ function toggleHalloween() {
} }
} }
// observe changes in the DOM
const observer = new MutationObserver(toggleHalloween); const observer = new MutationObserver(toggleHalloween);
observer.observe(document.body, { observer.observe(document.body, {
childList: true, childList: true,
@@ -51,29 +49,31 @@ observer.observe(document.body, {
}); });
function addRandomSymbols(count) { function initHalloween(count) {
const halloweenContainer = document.querySelector('.halloween-container'); // get the halloween container let halloweenContainer = document.querySelector('.halloween-container');
if (!halloweenContainer) return; // exit if halloween container is not found if (!halloweenContainer) {
halloweenContainer = document.createElement("div");
console.log('Adding random halloween symbols'); halloweenContainer.className = "halloween-container";
halloweenContainer.setAttribute("aria-hidden", "true");
document.body.appendChild(halloweenContainer);
}
console.log('Adding halloween symbols');
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
// create a new halloween elements
const halloweenDiv = document.createElement("div"); const halloweenDiv = document.createElement("div");
halloweenDiv.className = "halloween"; halloweenDiv.className = "halloween";
// pick a random halloween symbol
const imageSrc = images[Math.floor(Math.random() * images.length)]; const imageSrc = images[Math.floor(Math.random() * images.length)];
const img = document.createElement("img"); const img = document.createElement("img");
img.src = imageSrc; img.src = imageSrc;
halloweenDiv.appendChild(img); halloweenDiv.appendChild(img);
// set random horizontal position, animation delay and size(uncomment lines to enable)
const randomLeft = Math.random() * 100; // position (0% to 100%) const randomLeft = Math.random() * 100; // position (0% to 100%)
const randomAnimationDelay = Math.random() * 10; // delay (0s to 10s) const randomAnimationDelay = Math.random() * 10; // delay (0s to 10s)
// Display directly symbols on full screen (below) or let it build up (above)
// const randomAnimationDelay = -(Math.random() * 10); // delay (-10s to 0s)
const randomAnimationDelay2 = -(Math.random() * 3); // delay (-3s to 0s) const randomAnimationDelay2 = -(Math.random() * 3); // delay (-3s to 0s)
// apply styles // apply styles
@@ -87,41 +87,9 @@ function addRandomSymbols(count) {
halloweenDiv.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`; halloweenDiv.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`;
} }
// add the halloween to the container
halloweenContainer.appendChild(halloweenDiv); halloweenContainer.appendChild(halloweenDiv);
} }
console.log('Random halloween symbols added'); console.log('Halloween symbols added');
}
// create halloween objects
function createHalloween() {
const container = document.querySelector('.halloween-container') || document.createElement("div");
if (!document.querySelector('.halloween-container')) {
container.className = "halloween-container";
container.setAttribute("aria-hidden", "true");
document.body.appendChild(container);
}
for (let i = 0; i < 4; i++) {
images.forEach(imageSrc => {
const halloweenDiv = document.createElement("div");
halloweenDiv.className = "halloween";
const img = document.createElement("img");
img.src = imageSrc;
// set random animation duration
if (enableDiffrentDuration) {
const randomAnimationDuration = Math.random() * 10 + 6; // delay (6s to 10s)
const randomAnimationDuration2 = Math.random() * 5 + 2; // delay (2s to 5s)
halloweenDiv.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`;
}
halloweenDiv.appendChild(img);
container.appendChild(halloweenDiv);
});
}
} }
// create fog layer // create fog layer
@@ -182,14 +150,18 @@ function createSpider(container) {
setTimeout(() => { setTimeout(() => {
wrapper.remove(); wrapper.remove();
setTimeout(() => createSpider(container), Math.random() * 5000 + 1000); if (document.body.contains(container)) {
setTimeout(() => createSpider(container), Math.random() * 5000 + 1000);
}
}, 500); }, 500);
}); });
wrapper.addEventListener('animationend', () => { wrapper.addEventListener('animationend', () => {
if (isRetreating) return; if (isRetreating) return;
wrapper.remove(); wrapper.remove();
setTimeout(() => createSpider(container), Math.random() * 5000 + 1000); if (document.body.contains(container)) {
setTimeout(() => createSpider(container), Math.random() * 5000 + 1000);
}
}); });
container.appendChild(wrapper); container.appendChild(wrapper);
@@ -223,16 +195,21 @@ function createMouse(container) {
mouse.addEventListener('animationend', () => { mouse.addEventListener('animationend', () => {
mouse.remove(); mouse.remove();
setTimeout(() => createMouse(container), Math.random() * 4000 + 2000); if (document.body.contains(container)) {
setTimeout(() => createMouse(container), Math.random() * 4000 + 2000);
}
}); });
container.appendChild(mouse); container.appendChild(mouse);
} }
// initialize halloween
function initializeHalloween() { function initializeHalloween() {
if (!halloween) return; // exit if halloween is disabled if (!halloween) return;
createHalloween();
const isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
const count = !isMobile ? halloweenCount : halloweenCountMobile;
initHalloween(count);
toggleHalloween(); toggleHalloween();
const container = document.querySelector('.halloween-container'); const container = document.querySelector('.halloween-container');
@@ -254,11 +231,6 @@ function initializeHalloween() {
} }
} }
} }
const screenWidth = window.innerWidth; // get the screen width to detect mobile devices
if (randomSymbols && (screenWidth > 768 || randomSymbolsMobile)) { // add random halloweens only on larger screens, unless enabled for mobile devices
addRandomSymbols(halloweenCount);
}
} }
initializeHalloween(); initializeHalloween();

View File

@@ -12,48 +12,21 @@
} }
.heart { .heart {
will-change: transform;
position: fixed; position: fixed;
bottom: -10%; bottom: -10%;
z-index: 15; z-index: 15;
-webkit-user-select: none;
-moz-user-select: none; -moz-user-select: none;
-ms-user-select: none; -ms-user-select: none;
user-select: none; user-select: none;
-webkit-user-select: none;
cursor: default; cursor: default;
-webkit-animation-name: heart-fall, heart-shake;
-webkit-animation-duration: 14s, 5s;
-webkit-animation-timing-function: linear, ease-in-out;
-webkit-animation-iteration-count: infinite, infinite;
animation-name: heart-fall, heart-shake; animation-name: heart-fall, heart-shake;
animation-duration: 14s, 5s; animation-duration: 14s, 5s;
animation-timing-function: linear, ease-in-out; animation-timing-function: linear, ease-in-out;
animation-iteration-count: infinite, infinite; animation-iteration-count: infinite, infinite;
} }
@-webkit-keyframes heart-fall {
0% {
bottom: -10%;
}
100% {
bottom: 110%;
}
}
@-webkit-keyframes heart-shake {
0%,
100% {
-webkit-transform: translateX(0);
transform: translateX(0)
}
50% {
-webkit-transform: translateX(80px);
transform: translateX(80px)
}
}
@keyframes heart-fall { @keyframes heart-fall {
0% { 0% {
@@ -76,75 +49,3 @@
transform: translateX(80px) transform: translateX(80px)
} }
} }
.heart:nth-of-type(0) {
left: 1%;
-webkit-animation-delay: 0s, 0s;
animation-delay: 0s, 0s
}
.heart:nth-of-type(1) {
left: 10%;
-webkit-animation-delay: 1s, 1s;
animation-delay: 1s, 1s
}
.heart:nth-of-type(2) {
left: 20%;
-webkit-animation-delay: 6s, .5s;
animation-delay: 6s, .5s
}
.heart:nth-of-type(3) {
left: 30%;
-webkit-animation-delay: 4s, 2s;
animation-delay: 4s, 2s
}
.heart:nth-of-type(4) {
left: 40%;
-webkit-animation-delay: 2s, 2s;
animation-delay: 2s, 2s
}
.heart:nth-of-type(5) {
left: 50%;
-webkit-animation-delay: 8s, 3s;
animation-delay: 8s, 3s
}
.heart:nth-of-type(6) {
left: 60%;
-webkit-animation-delay: 6s, 2s;
animation-delay: 6s, 2s
}
.heart:nth-of-type(7) {
left: 70%;
-webkit-animation-delay: 2.5s, 1s;
animation-delay: 2.5s, 1s
}
.heart:nth-of-type(8) {
left: 80%;
-webkit-animation-delay: 1s, 0s;
animation-delay: 1s, 0s
}
.heart:nth-of-type(9) {
left: 90%;
-webkit-animation-delay: 3s, 1.5s;
animation-delay: 3s, 1.5s
}
.heart:nth-of-type(10) {
left: 25%;
-webkit-animation-delay: 2s, 0s;
animation-delay: 2s, 0s
}
.heart:nth-of-type(11) {
left: 65%;
-webkit-animation-delay: 4s, 2.5s;
animation-delay: 4s, 2.5s
}

View File

@@ -1,10 +1,9 @@
const config = window.SeasonalsPluginConfig?.Hearts || {}; const config = window.SeasonalsPluginConfig?.Hearts || {};
const hearts = config.EnableHearts !== undefined ? config.EnableHearts : true; // enable/disable hearts const hearts = config.EnableHearts !== undefined ? config.EnableHearts : true; // enable/disable hearts
const randomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true; // enable more random symbols const enableDiffrentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different durations
const randomSymbolsMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false; // enable random symbols on mobile devices (Warning: High values may affect performance) const heartsCount = config.SymbolCount !== undefined ? config.SymbolCount : 25; // count of symbol
const enableDiffrentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different animation duration for random symbols const heartsCountMobile = config.SymbolCountMobile !== undefined ? config.SymbolCountMobile : 10; // count of symbol on mobile
const heartsCount = config.SymbolCount || 25; // count of random extra symbols
// Array of hearts characters // Array of hearts characters
const heartSymbols = ['❤️', '💕', '💞', '💓', '💗', '💖']; const heartSymbols = ['❤️', '💕', '💞', '💓', '💗', '💖'];
@@ -46,11 +45,16 @@ observer.observe(document.body, {
}); });
function addRandomSymbols(count) { function initHearts(count) {
const heartsContainer = document.querySelector('.hearts-container'); // get the hearts container let heartsContainer = document.querySelector('.hearts-container'); // get the hearts container
if (!heartsContainer) return; // exit if hearts container is not found if (!heartsContainer) {
heartsContainer = document.createElement("div");
heartsContainer.className = "hearts-container";
heartsContainer.setAttribute("aria-hidden", "true");
document.body.appendChild(heartsContainer);
}
console.log('Adding random heart symbols'); console.log('Adding heart symbols');
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
// create a new hearts elements // create a new hearts elements
@@ -63,8 +67,8 @@ function addRandomSymbols(count) {
// set random horizontal position, animation delay and size(uncomment lines to enable) // set random horizontal position, animation delay and size(uncomment lines to enable)
const randomLeft = Math.random() * 100; // position (0% to 100%) const randomLeft = Math.random() * 100; // position (0% to 100%)
const randomAnimationDelay = Math.random() * 14; // delay (0s to 14s) const randomAnimationDelay = -(Math.random() * 16); // delay (-16s to 0s)
const randomAnimationDelay2 = Math.random() * 5; // delay (0s to 5s) const randomAnimationDelay2 = -(Math.random() * 5); // delay (-5s to 0s)
// apply styles // apply styles
heartsDiv.style.left = `${randomLeft}%`; heartsDiv.style.left = `${randomLeft}%`;
@@ -80,46 +84,18 @@ function addRandomSymbols(count) {
// add the hearts to the container // add the hearts to the container
heartsContainer.appendChild(heartsDiv); heartsContainer.appendChild(heartsDiv);
} }
console.log('Random hearts symbols added'); console.log('Heart symbols added');
} }
// create hearts objects
function createHearts() {
const container = document.querySelector('.hearts-container') || document.createElement("div");
if (!document.querySelector('.hearts-container')) {
container.className = "hearts-container";
container.setAttribute("aria-hidden", "true");
document.body.appendChild(container);
}
for (let i = 0; i < 12; i++) {
const heartsDiv = document.createElement("div");
heartsDiv.className = "heart";
heartsDiv.textContent = heartSymbols[i % heartSymbols.length];
// set random animation duration
if (enableDiffrentDuration) {
const randomAnimationDuration = Math.random() * 16 + 12; // delay (12s to 16s)
const randomAnimationDuration2 = Math.random() * 7 + 3; // delay (3s to 7s)
heartsDiv.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`;
}
container.appendChild(heartsDiv);
}
}
// initialize hearts // initialize hearts
function initializeHearts() { function initializeHearts() {
if (!hearts) return; // exit if hearts is disabled if (!hearts) return; // exit if hearts is disabled
createHearts();
toggleHearts();
const screenWidth = window.innerWidth; // get the screen width to detect mobile devices const isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
if (randomSymbols && (screenWidth > 768 || randomSymbolsMobile)) { // add random heartss only on larger screens, unless enabled for mobile devices const count = !isMobile ? heartsCount : heartsCountMobile;
addRandomSymbols(heartsCount);
} initHearts(count);
toggleHearts();
} }
initializeHearts(); initializeHearts();

View File

@@ -29,11 +29,13 @@
} }
.mario-jump { .mario-jump {
will-change: transform;
animation: jump-arc 0.8s ease-in-out; animation: jump-arc 0.8s ease-in-out;
} }
/* 8-bit coin styling */ /* 8-bit coin styling */
.mario-coin { .mario-coin {
will-change: transform;
position: absolute; position: absolute;
width: 32px; width: 32px;
height: 32px; height: 32px;
@@ -47,11 +49,12 @@
.mario-coin::after { .mario-coin::after {
content: ''; content: '';
position: absolute; position: absolute;
top: 6px; top: 0;
left: 10px; left: 10px;
width: 4px; width: 4px;
height: 12px; height: 12px;
background: #daa520; background: #daa520;
translate: 0 6px;
} }
@keyframes mario-run { @keyframes mario-run {

View File

@@ -1,5 +1,9 @@
const config = window.SeasonalsPluginConfig?.MarioDay || {}; const config = window.SeasonalsPluginConfig?.MarioDay || {};
const marioday = config.EnableMarioDay !== undefined ? config.EnableMarioDay : true; const marioday = config.EnableMarioDay !== undefined ? config.EnableMarioDay : true; // enable/disable marioday
const letMarioJump = config.LetMarioJump !== undefined ? config.LetMarioJump : true; // optionally let mario jump occasionally
// Credit: https://gifs.alphacoders.com/gifs/view/2585
const marioImage = '../Seasonals/Resources/mario_assets/mario.gif';
let msgPrinted = false; let msgPrinted = false;
@@ -45,19 +49,38 @@ function createMarioDay(container) {
const mario = document.createElement('img'); const mario = document.createElement('img');
mario.className = 'mario-runner'; mario.className = 'mario-runner';
mario.src = '../Seasonals/Resources/mario_assets/mario.gif'; mario.src = marioImage;
wrapper.appendChild(mario); wrapper.appendChild(mario);
container.appendChild(wrapper); container.appendChild(wrapper);
let jumpCount = 0;
let maxJumpsForThisRun = Math.floor(Math.random() * 3); // 0, 1, or 2
const resetJumpInterval = setInterval(() => {
if (!document.body.contains(container)) { clearInterval(resetJumpInterval); return; }
jumpCount = 0;
maxJumpsForThisRun = Math.floor(Math.random() * 3); // Randomize jumps for the next pass
}, (marioSpeedSeconds / 2) * 1000);
// Periodically throw out an 8-bit coin // Periodically throw out an 8-bit coin
setInterval(() => { const intervalId = setInterval(() => {
if (!document.querySelector('.marioday-container')) return; if (!document.body.contains(container)) { clearInterval(intervalId); return; }
if (container.style.display === 'none') return;
const marioRect = wrapper.getBoundingClientRect();
if (marioRect.left < 0 || marioRect.right > window.innerWidth) return;
if (letMarioJump && !mario.classList.contains('mario-jump') && jumpCount < maxJumpsForThisRun) {
mario.classList.add('mario-jump');
jumpCount++;
setTimeout(() => mario.classList.remove('mario-jump'), 800);
}
const coin = document.createElement('div'); const coin = document.createElement('div');
coin.className = 'mario-coin'; coin.className = 'mario-coin';
// Grab Mario's current screen position to lock the coin's X coordinate // Grab Mario's current screen position to lock the coin's X coordinate
const marioRect = wrapper.getBoundingClientRect();
coin.style.left = `${marioRect.left + 16}px`; coin.style.left = `${marioRect.left + 16}px`;
coin.style.bottom = '35px'; // bottom offset coin.style.bottom = '35px'; // bottom offset

View File

@@ -5,7 +5,7 @@
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
pointer-events: none; pointer-events: none;
z-index: 1000; z-index: 10;
overflow: hidden; overflow: hidden;
contain: layout paint; contain: layout paint;
} }

View File

@@ -1,9 +1,9 @@
const config = window.SeasonalsPluginConfig?.Matrix || {}; const config = window.SeasonalsPluginConfig?.Matrix || {};
const enabled = config.EnableMatrix !== undefined ? config.EnableMatrix : true; const enabled = config.EnableMatrix !== undefined ? config.EnableMatrix : true; // enable/disable matrix
const maxTrails = config.SymbolCount || 25; const maxTrails = config.SymbolCount !== undefined ? config.SymbolCount : 25; // count of max trails on screen
const backgroundMode = config.EnableMatrixBackground !== undefined ? config.EnableMatrixBackground : false; const backgroundMode = config.EnableMatrixBackground !== undefined ? config.EnableMatrixBackground : false; // enable/disable matrix as background
const matrixChars = config.MatrixChars || '0123456789'; const matrixChars = config.MatrixChars !== undefined ? config.MatrixChars : '0123456789'; // characters to use in the matrix rain, default is '0123456789'
let msgPrinted = false; let msgPrinted = false;
let isHidden = false; let isHidden = false;
@@ -136,6 +136,7 @@ function createElements() {
for(let i=0; i<maxTrails; i++) trails.push(new Trail()); for(let i=0; i<maxTrails; i++) trails.push(new Trail());
function loop() { function loop() {
if (!document.body.contains(container)) { clearInterval(window.matrixInterval); return; }
if (isHidden) return; // Pause drawing when hidden if (isHidden) return; // Pause drawing when hidden
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = 'bold ' + fontSize + 'px monospace'; ctx.font = 'bold ' + fontSize + 'px monospace';

View File

@@ -12,13 +12,15 @@
} }
.oktoberfest-symbol { .oktoberfest-symbol {
will-change: transform;
position: absolute; position: absolute;
top: -10%; top: 0;
font-size: 2.2em; font-size: 2.2em;
user-select: none; user-select: none;
animation-name: oktoberfest-fall, oktoberfest-sway; animation-name: oktoberfest-fall, oktoberfest-sway;
animation-timing-function: linear, ease-in-out; animation-timing-function: linear, ease-in-out;
animation-iteration-count: infinite, infinite; animation-iteration-count: infinite, infinite;
translate: 0 -10vh;
} }
@keyframes oktoberfest-fall { @keyframes oktoberfest-fall {

View File

@@ -1,5 +1,8 @@
const config = window.SeasonalsPluginConfig?.Oktoberfest || {}; const config = window.SeasonalsPluginConfig?.Oktoberfest || {};
const oktoberfest = config.EnableOktoberfest !== undefined ? config.EnableOktoberfest : true; const oktoberfest = config.EnableOktoberfest !== undefined ? config.EnableOktoberfest : true; // enable/disable oktoberfest
const symbolCount = config.SymbolCount !== undefined ? config.SymbolCount : 25; // count of symbols
const symbolCountMobile = config.SymbolCountMobile !== undefined ? config.SymbolCountMobile : 10; // count of symbols on mobile
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different durations
const oktoberfestSymbols = ['🥨', '🍺', '🍻', '🥨', '🥨']; const oktoberfestSymbols = ['🥨', '🍺', '🍻', '🥨', '🥨'];
@@ -31,7 +34,6 @@ function toggleOktoberfest() {
} }
} }
// observe changes in the DOM
const observer = new MutationObserver(toggleOktoberfest); const observer = new MutationObserver(toggleOktoberfest);
observer.observe(document.body, { observer.observe(document.body, {
childList: true, childList: true,
@@ -39,8 +41,8 @@ observer.observe(document.body, {
attributes: true attributes: true
}); });
function createOktoberfest(container) { function createOktoberfest(container, count) {
for (let i = 0; i < 20; i++) { for (let i = 0; i < count; i++) {
const symbol = document.createElement('div'); const symbol = document.createElement('div');
symbol.className = 'oktoberfest-symbol'; symbol.className = 'oktoberfest-symbol';
symbol.textContent = oktoberfestSymbols[Math.floor(Math.random() * oktoberfestSymbols.length)]; symbol.textContent = oktoberfestSymbols[Math.floor(Math.random() * oktoberfestSymbols.length)];
@@ -48,7 +50,9 @@ function createOktoberfest(container) {
symbol.style.animationDelay = `${Math.random() * 10}s, ${Math.random() * 5}s`; symbol.style.animationDelay = `${Math.random() * 10}s, ${Math.random() * 5}s`;
const duration1 = Math.random() * 5 + 8; const duration1 = Math.random() * 5 + 8;
const duration2 = Math.random() * 3 + 3; const duration2 = Math.random() * 3 + 3;
symbol.style.animationDuration = `${duration1}s, ${duration2}s`; if (enableDifferentDuration) {
symbol.style.animationDuration = `${duration1}s, ${duration2}s`;
}
container.appendChild(symbol); container.appendChild(symbol);
} }
@@ -62,6 +66,10 @@ function initializeOktoberfest() {
container.setAttribute("aria-hidden", "true"); container.setAttribute("aria-hidden", "true");
document.body.appendChild(container); document.body.appendChild(container);
} }
createOktoberfest(container);
const isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
const count = !isMobile ? symbolCount : symbolCountMobile;
createOktoberfest(container, count);
} }
initializeOktoberfest(); initializeOktoberfest();

View File

@@ -12,10 +12,11 @@
.olympia-symbol { .olympia-symbol {
position: absolute; position: absolute;
top: -10vh; top: 0;
opacity: 0.95; opacity: 0.95;
text-shadow: 0 0 10px rgba(255,255,255,0.2); text-shadow: 0 0 10px rgba(255,255,255,0.2);
z-index: 40; z-index: 40;
translate: 0 -10vh;
} }
.olympia-flame { .olympia-flame {
@@ -34,8 +35,8 @@
.olympia-ring-css::before { .olympia-ring-css::before {
content: ''; content: '';
position: absolute; position: absolute;
top: 50%; left: 50%; top: 0;
transform: translate(-50%, -50%); translate: -50% -50%;
width: 30px; width: 30px;
height: 30px; height: 30px;
border: 5px solid #0081C8; /* Default blue ring */ border: 5px solid #0081C8; /* Default blue ring */
@@ -46,13 +47,15 @@
} }
.olympia-symbol { .olympia-symbol {
position: absolute; position: absolute;
top: -10vh; top: 0;
opacity: 0.95; opacity: 0.95;
text-shadow: 0 0 10px rgba(255,255,255,0.2); text-shadow: 0 0 10px rgba(255,255,255,0.2);
z-index: 40; z-index: 40;
translate: 0 -10vh;
} }
.olympia-inner { .olympia-inner {
will-change: transform;
display: inline-block; display: inline-block;
animation: olympia-sway linear infinite alternate; animation: olympia-sway linear infinite alternate;
} }

View File

@@ -1,10 +1,34 @@
const config = window.SeasonalsPluginConfig?.Olympia || {}; const config = window.SeasonalsPluginConfig?.Olympia || {};
const olympia = config.EnableOlympia !== undefined ? config.EnableOlympia : true; const olympia = config.EnableOlympia !== undefined ? config.EnableOlympia : true; // enable/disable olympia theme
const symbolCount = config.SymbolCount || 25; const symbolCount = config.SymbolCount !== undefined ? config.SymbolCount : 25; // count of floating symbols
const useRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true; const symbolCountMobile = config.SymbolCountMobile !== undefined ? config.SymbolCountMobile : 10; // count of floating symbols on mobile
const enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false; const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different durations
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true;
// Olympic Ring Colors (Carnival Config)
const confettiColors = ['#0081C8', '#FCB131', '#000000', '#00A651', '#EE334E'];
const isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
const confettiCount = isMobile ? 30 : 60;
/**
* Credits:
* https://lottiefiles.com/free-animation/gold-coin-5Spp5kJbLP
* https://lottiefiles.com/free-animation/silver-coin-SIgIP59fII
* https://lottiefiles.com/free-animation/bronze-coin-wWVCJMsOUq
*/
const olympicMedals = [
"../Seasonals/Resources/olympic_assets/gold_coin.gif",
"../Seasonals/Resources/olympic_assets/silver_coin.gif",
"../Seasonals/Resources/olympic_assets/bronze_coin.gif"
]
/**
* Credits:
* https://www.flaticon.com/de/kostenloses-icon/fackel_4683293
* merged with:
* https://lottiefiles.com/free-animation/abstract-flames-lottie-json-animation-oSb0IFoBrj
*/
const olympicTorch = "../Seasonals/Resources/olympic_assets/torch.gif";
let msgPrinted = false; let msgPrinted = false;
@@ -49,27 +73,23 @@ function createOlympia() {
} }
const standardCount = 15; const standardCount = 15;
const totalSymbols = symbolCount + standardCount;
let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches; let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
let finalCount = totalSymbols; let finalCount = isMobile ? symbolCountMobile : symbolCount;
if (isMobile) {
finalCount = enableRandomMobile ? totalSymbols : standardCount;
}
const useRandomDuration = enableDifferentDuration !== false; const useRandomDuration = enableDifferentDuration !== false;
const activeItems = ['gold', 'silver', 'bronze', 'torch', 'rings_blue', 'rings_yellow', 'rings_black', 'rings_green', 'rings_red']; const olympicRings = ['ring_blue.css', 'ring_yellow.css', 'ring_black.css', 'ring_green.css', 'ring_red.css'];
const activeItems = [...olympicMedals, ...olympicRings];
for (let i = 0; i < finalCount; i++) { for (let i = 0; i < finalCount; i++) {
let symbol = document.createElement('div'); let symbol = document.createElement('div');
const randomItem = activeItems[Math.floor(Math.random() * activeItems.length)]; const randomImgUrl = activeItems[Math.floor(Math.random() * activeItems.length)];
const isRing = randomItem.startsWith('rings'); const isRing = randomImgUrl.includes('ring_');
const isMedal = ['gold', 'silver', 'bronze'].includes(randomItem); const isMedal = randomImgUrl.includes('_coin');
symbol.className = `olympia-symbol olympia-${randomItem}`; symbol.className = `olympia-symbol`;
// Create inner div for sway/rotation // Create inner div for sway/rotation
let innerDiv = document.createElement('div'); let innerDiv = document.createElement('div');
@@ -77,19 +97,20 @@ function createOlympia() {
let img = null; let img = null;
if (isRing) { if (isRing) {
const colorName = randomImgUrl.split('ring_')[1].split('.')[0];
const ringColorMap = { const ringColorMap = {
'rings_blue': '#0081C8', 'blue': '#0081C8',
'rings_yellow': '#FCB131', 'yellow': '#FCB131',
'rings_black': '#000000', 'black': '#000000',
'rings_green': '#00A651', 'green': '#00A651',
'rings_red': '#EE334E' 'red': '#EE334E'
}; };
let ringDiv = document.createElement('div'); let ringDiv = document.createElement('div');
ringDiv.className = 'olympia-ring-css'; ringDiv.className = 'olympia-ring-css';
ringDiv.style.setProperty('--ring-color', ringColorMap[randomItem]); ringDiv.style.setProperty('--ring-color', ringColorMap[colorName]);
innerDiv.appendChild(ringDiv); innerDiv.appendChild(ringDiv);
// Add a 3D flip animation for rings and medals // Add a 3D flip animation for rings
const spinReverse = Math.random() > 0.5 ? 'reverse' : 'normal'; const spinReverse = Math.random() > 0.5 ? 'reverse' : 'normal';
innerDiv.style.animation = `olympia-tumble-3d ${Math.random() * 4 + 4}s linear infinite ${spinReverse}`; innerDiv.style.animation = `olympia-tumble-3d ${Math.random() * 4 + 4}s linear infinite ${spinReverse}`;
@@ -99,13 +120,7 @@ function createOlympia() {
innerDiv.style.setProperty('--rot-z', (Math.random() * 2 - 1).toFixed(2)); innerDiv.style.setProperty('--rot-z', (Math.random() * 2 - 1).toFixed(2));
} else { } else {
img = document.createElement('img'); img = document.createElement('img');
let imgName = randomItem; img.src = randomImgUrl;
if (isMedal) {
imgName = `${randomItem}_coin.gif`;
} else {
imgName = `${randomItem}.png`;
}
img.src = `../Seasonals/Resources/olympic_assets/${imgName}`;
img.onerror = function() { img.onerror = function() {
symbol.remove(); symbol.remove();
}; };
@@ -181,10 +196,6 @@ function createOlympia() {
createTorch(true); createTorch(true);
createTorch(false); createTorch(false);
// Olympic Ring Colors (Carnival Config)
const confettiColors = ['#0081C8', '#FCB131', '#000000', '#00A651', '#EE334E'];
const confettiCount = isMobile ? 30 : 60;
for (let i = 0; i < confettiCount; i++) { for (let i = 0; i < confettiCount; i++) {
let wrapper = document.createElement('div'); let wrapper = document.createElement('div');

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -6,7 +6,7 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none; pointer-events: none;
z-index: 10; /* Behind popups but over background */ z-index: 10;
contain: strict; contain: strict;
overflow: hidden; overflow: hidden;
} }
@@ -30,8 +30,9 @@
} }
.oscar-spotlight { .oscar-spotlight {
will-change: transform;
position: absolute; position: absolute;
top: -10vh; top: 0;
/* MARK: SPOTLIGHT WIDTH CONFIGURATION */ /* MARK: SPOTLIGHT WIDTH CONFIGURATION */
/* To adjust bottom width (spread), change 'width' property (e.g., 20vw for narrow, 40vw for wide). */ /* To adjust bottom width (spread), change 'width' property (e.g., 20vw for narrow, 40vw for wide). */
/* To adjust top width (origin), modify first two percentages in 'clip-path' (e.g., 48% 0, 52% 0 for a very thin start). */ /* To adjust top width (origin), modify first two percentages in 'clip-path' (e.g., 48% 0, 52% 0 for a very thin start). */
@@ -42,9 +43,11 @@
transform-origin: top center; transform-origin: top center;
animation: spotlight-sweep 12s infinite alternate ease-in-out; animation: spotlight-sweep 12s infinite alternate ease-in-out;
mix-blend-mode: screen; mix-blend-mode: screen;
translate: 0 -10vh;
} }
.oscar-flash { .oscar-flash {
will-change: transform;
position: absolute; position: absolute;
width: 10px; width: 10px;
height: 10px; height: 10px;

View File

@@ -1,5 +1,5 @@
const config = window.SeasonalsPluginConfig?.Oscar || {}; const config = window.SeasonalsPluginConfig?.Oscar || {};
const oscar = config.EnableOscar !== undefined ? config.EnableOscar : true; const oscar = config.EnableOscar !== undefined ? config.EnableOscar : true; // enable/disable oscar
let msgPrinted = false; let msgPrinted = false;
@@ -56,9 +56,9 @@ function createOscar(container) {
container.appendChild(carpet); container.appendChild(carpet);
container.appendChild(spotlights); container.appendChild(spotlights);
// Paparazzi flashes with randomized intervals
function flashLoop() { function flashLoop() {
if (!document.querySelector('.oscar-container')) { if (!document.body.contains(container)) return; // Kill the loop if container is removed
if (container.style.display === 'none') {
setTimeout(flashLoop, 1000); // Check again later if hidden setTimeout(flashLoop, 1000); // Check again later if hidden
return; return;
} }

View File

@@ -5,7 +5,7 @@
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
pointer-events: none; pointer-events: none;
z-index: 9999; z-index: 10;
overflow: hidden; overflow: hidden;
contain: layout paint; contain: layout paint;
} }

View File

@@ -1,15 +1,12 @@
// 1. Read Configuration
const config = window.SeasonalsPluginConfig?.Pride || {}; const config = window.SeasonalsPluginConfig?.Pride || {};
const enabled = config.EnablePride !== undefined ? config.EnablePride : true; const enabled = config.EnablePride !== undefined ? config.EnablePride : true; // enable/disable pride
const elementCount = config.HeartCount || 20; const elementCount = config.HeartCount !== undefined ? config.HeartCount : 20; // count of heart
const heartSize = config.HeartSize || 1.5; const heartSize = config.HeartSize !== undefined ? config.HeartSize : 1.5; // size of hearts
const colorHeader = config.ColorHeader !== undefined ? config.ColorHeader : true; const colorHeader = config.ColorHeader !== undefined ? config.ColorHeader : true; // optionally color the header with pride colors
let msgPrinted = false; let msgPrinted = false;
// 2. Toggle Function
// Hides the effect when a video player, trailer (in full width mode), dashboard, or user menu is active.
function togglePride() { function togglePride() {
const container = document.querySelector('.pride-container'); const container = document.querySelector('.pride-container');
if (!container) return; if (!container) return;
@@ -34,8 +31,6 @@ function togglePride() {
} }
} }
// 3. MutationObserver
// Watches the DOM for changes so the effect can auto-hide/show.
const observer = new MutationObserver(togglePride); const observer = new MutationObserver(togglePride);
observer.observe(document.body, { observer.observe(document.body, {
childList: true, childList: true,
@@ -43,8 +38,6 @@ observer.observe(document.body, {
attributes: true attributes: true
}); });
// 4. Element Creation
// Create and append your animated elements to the container.
function createElements() { function createElements() {
const container = document.querySelector('.pride-container') || document.createElement('div'); const container = document.querySelector('.pride-container') || document.createElement('div');
@@ -82,7 +75,6 @@ function createElements() {
} }
} }
// 5. Initialization
function initializePride() { function initializePride() {
if (!enabled) return; if (!enabled) return;
createElements(); createElements();

View File

@@ -1,9 +1,9 @@
const config = window.SeasonalsPluginConfig?.Rain || {}; const config = window.SeasonalsPluginConfig?.Rain || {};
const enabled = config.EnableRain !== undefined ? config.EnableRain : true; const enabled = config.EnableRain !== undefined ? config.EnableRain : true; // enable/disable rain
const isMobile = window.innerWidth <= 768; const isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
const elementCount = isMobile ? (config.RaindropCountMobile || 150) : (config.RaindropCount || 300); const elementCount = isMobile ? (config.RaindropCountMobile || 150) : (config.RaindropCount || 300); // count of raindrops
const rainSpeed = config.RainSpeed || 1.0; const rainSpeed = config.RainSpeed !== undefined ? config.RainSpeed : 1.0; // speed of rain
let msgPrinted = false; let msgPrinted = false;

View File

@@ -1,68 +1,66 @@
.resurrection-container { .resurrection-container {
display: block; display: block;
position: fixed; position: fixed;
overflow: hidden; overflow: hidden;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none; pointer-events: none;
z-index: 10; z-index: 10;
contain: layout paint; contain: layout paint;
} }
.resurrection-symbol { .resurrection-symbol {
position: fixed; position: fixed;
z-index: 15; z-index: 15;
top: 0; top: 0;
translate: 0 -15vh; translate: 0 -15vh;
user-select: none; user-select: none;
-webkit-user-select: none; cursor: default;
cursor: default; animation-name: resurrection-fall;
animation-name: resurrection-fall; animation-timing-function: linear;
animation-timing-function: linear; animation-iteration-count: infinite;
animation-iteration-count: infinite; will-change: transform;
will-change: transform; }
}
.resurrection-sway-wrapper {
.resurrection-sway-wrapper { will-change: transform;
will-change: transform; animation-name: resurrection-sway;
animation-name: resurrection-sway; animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out; animation-iteration-count: infinite;
animation-iteration-count: infinite; }
}
.resurrection-symbol img {
.resurrection-symbol img { z-index: 15;
z-index: 15; height: auto;
height: auto; width: 56px;
width: 56px; opacity: 0.95;
opacity: 0.95; filter: drop-shadow(0 0 8px rgba(255, 215, 130, 0.5));
filter: drop-shadow(0 0 8px rgba(255, 215, 130, 0.5)); }
}
@media (max-width: 768px) {
@media (max-width: 768px) { .resurrection-symbol img {
.resurrection-symbol img { width: 42px;
width: 42px; }
} }
}
@keyframes resurrection-fall {
@keyframes resurrection-fall { 0% {
0% { transform: translate3d(0, -15vh, 0);
transform: translate3d(0, -15vh, 0); }
}
100% {
100% { transform: translate3d(0, 105vh, 0);
transform: translate3d(0, 105vh, 0); }
} }
}
@keyframes resurrection-sway {
@keyframes resurrection-sway { 0%,
0%, 100% {
100% { transform: translateX(0);
transform: translateX(0); }
}
50% {
50% { transform: translateX(65px);
transform: translateX(65px); }
}
}

View File

@@ -1,10 +1,9 @@
const config = window.SeasonalsPluginConfig?.Resurrection || {}; const config = window.SeasonalsPluginConfig?.Resurrection || {};
const enableResurrection = config.EnableResurrection !== undefined ? config.EnableResurrection : true; const enableResurrection = config.EnableResurrection !== undefined ? config.EnableResurrection : true; // enable/disable resurrection
const enableRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true; const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different durations
const enableRandomSymbolsMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false; const symbolCount = config.SymbolCount !== undefined ? config.SymbolCount : 12; // count of symbols
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; const symbolCountMobile = config.SymbolCountMobile !== undefined ? config.SymbolCountMobile : 5; // count of symbols on mobile
const symbolCount = config.SymbolCount || 12;
let animationEnabled = true; let animationEnabled = true;
let statusLogged = false; let statusLogged = false;
@@ -78,20 +77,17 @@ function createSymbol(imageSrc, leftPercent, delaySeconds) {
function addSymbols(count) { function addSymbols(count) {
const container = document.querySelector('.resurrection-container'); const container = document.querySelector('.resurrection-container');
if (!container || !enableRandomSymbols) return; if (!container) return;
const isDesktop = window.innerWidth > 768;
if (!isDesktop && !enableRandomSymbolsMobile) return;
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const imageSrc = images[Math.floor(Math.random() * images.length)]; const imageSrc = images[Math.floor(Math.random() * images.length)];
const left = Math.random() * 100; const left = Math.random() * 100;
const delay = Math.random() * 12; const delay = -(Math.random() * 12);
container.appendChild(createSymbol(imageSrc, left, delay)); container.appendChild(createSymbol(imageSrc, left, delay));
} }
} }
function initResurrection() { function initResurrection(count) {
let container = document.querySelector('.resurrection-container'); let container = document.querySelector('.resurrection-container');
if (!container) { if (!container) {
container = document.createElement('div'); container = document.createElement('div');
@@ -103,17 +99,21 @@ function initResurrection() {
// Place one of each of the 8 provided resurrection images first. // Place one of each of the 8 provided resurrection images first.
images.forEach((imageSrc, index) => { images.forEach((imageSrc, index) => {
const left = (index + 1) * (100 / (images.length + 1)); const left = (index + 1) * (100 / (images.length + 1));
const delay = Math.random() * 8; const delay = -(Math.random() * 8);
container.appendChild(createSymbol(imageSrc, left, delay)); container.appendChild(createSymbol(imageSrc, left, delay));
}); });
const extraCount = Math.max(symbolCount - images.length, 0); const extraCount = Math.max(count - images.length, 0);
addSymbols(extraCount); addSymbols(extraCount);
} }
function initializeResurrection() { function initializeResurrection() {
if (!enableResurrection) return; if (!enableResurrection) return;
initResurrection();
const isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
const count = !isMobile ? symbolCount : symbolCountMobile;
initResurrection(count);
toggleResurrection(); toggleResurrection();
} }

View File

@@ -1,15 +1,15 @@
const config = window.SeasonalsPluginConfig?.Santa || {}; const config = window.SeasonalsPluginConfig?.Santa || {};
const santaIsFlying = config.EnableSanta !== undefined ? config.EnableSanta : true; // enable/disable santa const santaIsFlying = config.EnableSanta !== undefined ? config.EnableSanta : true; // enable/disable santa
let snowflakesCount = config.SnowflakesCount || 500; // count of snowflakes (recommended values: 300-600) let snowflakesCount = config.SnowflakesCount !== undefined ? config.SnowflakesCount : 500; // count of snowflakes
const snowflakesCountMobile = config.SnowflakesCountMobile || 250; // count of snowflakes on mobile devices (Warning: High values may affect performance) const snowflakesCountMobile = config.SnowflakesCountMobile !== undefined ? config.SnowflakesCountMobile : 250; // count of snowflakes on mobile
const snowFallSpeed = config.SnowFallSpeed || 3; // speed of snowfall (recommended values: 0-5) const snowFallSpeed = config.SnowFallSpeed !== undefined ? config.SnowFallSpeed : 3; // speed of snowfall
const santaSpeed = config.SantaSpeed || 10; // speed of santa in seconds (recommended values: 5-15) const santaSpeed = config.SantaSpeed !== undefined ? config.SantaSpeed : 10; // speed of santa in seconds
const santaSpeedMobile = config.SantaSpeedMobile || 8; // speed of santa on mobile devices in seconds const santaSpeedMobile = config.SantaSpeedMobile !== undefined ? config.SantaSpeedMobile : 8; // speed of santa on mobile devices in seconds
const maxSantaRestTime = config.MaxSantaRestTime || 8; // maximum time santa rests in seconds const maxSantaRestTime = config.MaxSantaRestTime !== undefined ? config.MaxSantaRestTime : 8; // maximum time santa rests in seconds
const minSantaRestTime = config.MinSantaRestTime || 3; // minimum time santa rests in seconds const minSantaRestTime = config.MinSantaRestTime !== undefined ? config.MinSantaRestTime : 3; // minimum time santa rests in seconds
const maxPresentFallSpeed = config.MaxPresentFallSpeed || 5; // maximum speed of falling presents in seconds const maxPresentFallSpeed = config.MaxPresentFallSpeed !== undefined ? config.MaxPresentFallSpeed : 5; // maximum speed of falling presents in seconds
const minPresentFallSpeed = config.MinPresentFallSpeed || 2; // minimum speed of falling presents in seconds const minPresentFallSpeed = config.MinPresentFallSpeed !== undefined ? config.MinPresentFallSpeed : 2; // minimum speed of falling presents in seconds
// credits: flaticon.com // credits: flaticon.com
const presentImages = [ const presentImages = [
@@ -238,7 +238,7 @@ function animateSanta() {
function startAnimation() { function startAnimation() {
const santaHeight = santa.offsetHeight; const santaHeight = santa.offsetHeight;
if (santaHeight === 0) { if (santaHeight === 0) {
setTimeout(startAnimation, 100); setTimeout(() => { if (document.body.contains(santa)) startAnimation(); }, 100);
return; return;
} }
// console.log('Santa height: ', santaHeight); // console.log('Santa height: ', santaHeight);
@@ -283,7 +283,7 @@ function animateSanta() {
animationFrameIdSanta = requestAnimationFrame(move); animationFrameIdSanta = requestAnimationFrame(move);
} else { } else {
const pause = Math.random() * ((maxSantaRestTime - minSantaRestTime) * 1000) + minSantaRestTime * 1000; const pause = Math.random() * ((maxSantaRestTime - minSantaRestTime) * 1000) + minSantaRestTime * 1000;
setTimeout(animateSanta, pause); setTimeout(() => { if (document.body.contains(santa)) animateSanta(); }, pause);
} }
} }
@@ -310,8 +310,8 @@ function initializeSanta() {
} }
const container = document.querySelector('.santa-container'); const container = document.querySelector('.santa-container');
if (container) { if (container) {
const screenWidth = window.innerWidth; // get the screen width to detect mobile devices const isMobile = window.matchMedia("only screen and (max-width: 768px)").matches; // check if mobile device
if (screenWidth < 768) { // lower count of snowflakes on mobile devices if (isMobile) { // lower count of snowflakes on mobile devices
isMobile = true; isMobile = true;
console.log('Mobile device detected. Reducing snowflakes count.'); console.log('Mobile device detected. Reducing snowflakes count.');
snowflakesCount = snowflakesCountMobile; snowflakesCount = snowflakesCountMobile;

View File

@@ -1,9 +1,9 @@
const config = window.SeasonalsPluginConfig?.Snowfall || {}; const config = window.SeasonalsPluginConfig?.Snowfall || {};
const snowfall = config.EnableSnowfall !== undefined ? config.EnableSnowfall : true; // enable/disable snowfall const snowfall = config.EnableSnowfall !== undefined ? config.EnableSnowfall : true; // enable/disable snowfall
let snowflakesCount = config.SnowflakesCount || 500; // count of snowflakes (recommended values: 300-600) let snowflakesCount = config.SnowflakesCount !== undefined ? config.SnowflakesCount : 500; // count of snowflakes
const snowflakesCountMobile = config.SnowflakesCountMobile || 250; // count of snowflakes on mobile devices (Warning: High values may affect performance) const snowflakesCountMobile = config.SnowflakesCountMobile !== undefined ? config.SnowflakesCountMobile : 250; // count of snowflakes on mobile
const snowFallSpeed = config.Speed || 3; // speed of snowfall (recommended values: 0-5) const snowFallSpeed = config.Speed !== undefined ? config.Speed : 3; // speed of snowfall
let msgPrinted = false; // flag to prevent multiple console messages let msgPrinted = false; // flag to prevent multiple console messages
@@ -181,8 +181,8 @@ function initializeSnowfall() {
} }
const container = document.querySelector('.snowfall-container'); const container = document.querySelector('.snowfall-container');
if (container) { if (container) {
const screenWidth = window.innerWidth; // get the screen width to detect mobile devices const isMobile = window.matchMedia("only screen and (max-width: 768px)").matches; // check if mobile device
if (screenWidth < 768) { // lower count of snowflakes on mobile devices if (isMobile) { // lower count of snowflakes on mobile devices
console.log('Mobile device detected. Reducing snowflakes count.'); console.log('Mobile device detected. Reducing snowflakes count.');
snowflakesCount = snowflakesCountMobile; snowflakesCount = snowflakesCountMobile;
} }

View File

@@ -22,39 +22,14 @@
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
text-shadow: 0 0 5px #000; text-shadow: 0 0 5px #000;
user-select: none; user-select: none;
-webkit-user-select: none;
cursor: default; cursor: default;
-webkit-animation-name: heart-fall, heart-shake;
-webkit-animation-duration: 12s, 3s;
-webkit-animation-timing-function: linear, ease-in-out;
-webkit-animation-iteration-count: infinite, infinite;
animation-name: snowflakes-fall, snowflakes-shake; animation-name: snowflakes-fall, snowflakes-shake;
animation-duration: 12s, 3s; animation-duration: 12s, 3s;
animation-timing-function: linear, ease-in-out; animation-timing-function: linear, ease-in-out;
animation-iteration-count: infinite, infinite; animation-iteration-count: infinite, infinite;
} }
@-webkit-keyframes snowflakes-fall {
0% {
translate: 0 -10vh;
}
100% {
translate: 0 110vh;
}
}
@-webkit-keyframes snowflakes-shake {
0%,
100% {
transform: translateX(0);
}
50% {
transform: translateX(80px);
}
}
@keyframes snowflakes-fall { @keyframes snowflakes-fall {
0% { 0% {
@@ -76,64 +51,4 @@
50% { 50% {
transform: translateX(80px); transform: translateX(80px);
} }
}
.snowflake:nth-of-type(0) {
left: 0%;
animation-delay: 0s, 0s;
}
.snowflake:nth-of-type(1) {
left: 10%;
animation-delay: 1s, 1s;
}
.snowflake:nth-of-type(2) {
left: 20%;
animation-delay: 6s, 0.5s;
}
.snowflake:nth-of-type(3) {
left: 30%;
animation-delay: 4s, 2s;
}
.snowflake:nth-of-type(4) {
left: 40%;
animation-delay: 2s, 2s;
}
.snowflake:nth-of-type(5) {
left: 50%;
animation-delay: 8s, 3s;
}
.snowflake:nth-of-type(6) {
left: 60%;
animation-delay: 6s, 2s;
}
.snowflake:nth-of-type(7) {
left: 70%;
animation-delay: 2.5s, 1s;
}
.snowflake:nth-of-type(8) {
left: 80%;
animation-delay: 1s, 0s;
}
.snowflake:nth-of-type(9) {
left: 90%;
animation-delay: 3s, 1.5s;
}
.snowflake:nth-of-type(10) {
left: 25%;
animation-delay: 2s, 0s;
}
.snowflake:nth-of-type(11) {
left: 65%;
animation-delay: 4s, 2.5s;
} }

View File

@@ -1,11 +1,10 @@
const config = window.SeasonalsPluginConfig?.Snowflakes || {}; const config = window.SeasonalsPluginConfig?.Snowflakes || {};
const snowflakes = config.EnableSnowflakes !== undefined ? config.EnableSnowflakes : true; // enable/disable snowflakes const snowflakes = config.EnableSnowflakes !== undefined ? config.EnableSnowflakes : true; // enable/disable snowflakes
const randomSnowflakes = config.EnableRandomSnowflakes !== undefined ? config.EnableRandomSnowflakes : true; // enable random Snowflakes const snowflakeCount = config.SnowflakeCount !== undefined ? config.SnowflakeCount : 25; // count of snowflakes
const randomSnowflakesMobile = config.EnableRandomSnowflakesMobile !== undefined ? config.EnableRandomSnowflakesMobile : false; // enable random Snowflakes on mobile devices const snowflakeCountMobile = config.SnowflakeCountMobile !== undefined ? config.SnowflakeCountMobile : 10; // count of snowflakes on mobile
const enableColoredSnowflakes = config.EnableColoredSnowflakes !== undefined ? config.EnableColoredSnowflakes : true; // enable colored snowflakes const enableColoredSnowflakes = config.EnableColoredSnowflakes !== undefined ? config.EnableColoredSnowflakes : true; // enable/disable colored snowflakes
const enableDiffrentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different animation duration const enableDiffrentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different durations
const snowflakeCount = config.SnowflakeCount || 25; // count of random extra snowflakes
const snowflakeSymbols = ['❅', '❆']; // some snowflake symbols const snowflakeSymbols = ['❅', '❆']; // some snowflake symbols
const snowflakeSymbolsMobile = ['❅', '❆', '❄']; // some snowflake symbols mobile version const snowflakeSymbolsMobile = ['❅', '❆', '❄']; // some snowflake symbols mobile version
@@ -46,11 +45,16 @@ observer.observe(document.body, {
attributes: true attributes: true
}); });
function addRandomSnowflakes(count) { function initSnowflakes(count) {
const snowflakeContainer = document.querySelector('.snowflakes'); // get the snowflake container let snowflakeContainer = document.querySelector('.snowflakes'); // get the snowflake container
if (!snowflakeContainer) return; // exit if snowflake container is not found if (!snowflakeContainer) {
snowflakeContainer = document.createElement("div");
snowflakeContainer.className = "snowflakes";
snowflakeContainer.setAttribute("aria-hidden", "true");
document.body.appendChild(snowflakeContainer);
}
console.log('Adding random snowflakes'); console.log('Adding snowflakes');
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
// create a new snowflake element // create a new snowflake element
@@ -66,7 +70,7 @@ function addRandomSnowflakes(count) {
// set random horizontal position, animation delay and size(uncomment lines to enable) // set random horizontal position, animation delay and size(uncomment lines to enable)
const randomLeft = Math.random() * 100; // position (0% to 100%) const randomLeft = Math.random() * 100; // position (0% to 100%)
const randomAnimationDelay = Math.random() * 8; // delay (0s to 8s) const randomAnimationDelay = -(Math.random() * 14); // delay (-14s to 0s)
const randomAnimationDelay2 = -(Math.random() * 5); // delay (-5s to 0s) const randomAnimationDelay2 = -(Math.random() * 5); // delay (-5s to 0s)
// apply styles // apply styles
@@ -83,49 +87,18 @@ function addRandomSnowflakes(count) {
// add the snowflake to the container // add the snowflake to the container
snowflakeContainer.appendChild(snowflake); snowflakeContainer.appendChild(snowflake);
} }
console.log('Random snowflakes added'); console.log('Snowflakes added');
} }
// initialize standard snowflakes // initialize snowflakes
function initSnowflakes() {
const snowflakesContainer = document.querySelector('.snowflakes') || document.createElement("div");
if (!document.querySelector('.snowflakes')) {
snowflakesContainer.className = "snowflakes";
snowflakesContainer.setAttribute("aria-hidden", "true");
document.body.appendChild(snowflakesContainer);
}
// Array of snowflake characters
const snowflakeSymbols = ['❅', '❆'];
// create the 12 standard snowflakes
for (let i = 0; i < 12; i++) {
const snowflake = document.createElement('div');
snowflake.className = 'snowflake';
snowflake.textContent = snowflakeSymbols[i % 2]; // change between ❅ and ❆
// set random animation duration
if (enableDiffrentDuration) {
const randomAnimationDuration = Math.random() * 14 + 10; // delay (10s to 14s)
const randomAnimationDuration2 = Math.random() * 5 + 3; // delay (3s to 5s)
snowflake.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`;
}
snowflakesContainer.appendChild(snowflake);
}
}
// initialize snowflakes and add random snowflakes
function initializeSnowflakes() { function initializeSnowflakes() {
if (!snowflakes) return; // exit if snowflakes are disabled if (!snowflakes) return; // exit if snowflakes are disabled
initSnowflakes();
toggleSnowflakes();
const screenWidth = window.innerWidth; // get the screen width to detect mobile devices const isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
if (randomSnowflakes && (screenWidth > 768 || randomSnowflakesMobile)) { // add random snowflakes only on larger screens, unless enabled for mobile devices const count = !isMobile ? snowflakeCount : snowflakeCountMobile;
addRandomSnowflakes(snowflakeCount);
} initSnowflakes(count);
toggleSnowflakes();
} }
initializeSnowflakes(); initializeSnowflakes();

View File

@@ -1,11 +1,11 @@
const config = window.SeasonalsPluginConfig?.Snowstorm || {}; const config = window.SeasonalsPluginConfig?.Snowstorm || {};
const snowstorm = config.enableSnowstorm !== undefined ? config.EnableSnowstorm : true; // enable/disable snowstorm const snowstorm = config.EnableSnowstorm !== undefined ? config.EnableSnowstorm : true; // enable/disable snowstorm
let snowflakesCount = config.SnowflakesCount || 500; // count of snowflakes (recommended values: 300-600) let snowflakesCount = config.SnowflakesCount !== undefined ? config.SnowflakesCount : 500; // count of snowflakes
const snowflakesCountMobile = config.SnowflakesCountMobile || 250; // count of snowflakes on mobile devices (Warning: High values may affect performance) const snowflakesCountMobile = config.SnowflakesCountMobile !== undefined ? config.SnowflakesCountMobile : 250; // count of snowflakes on mobile
const snowFallSpeed = config.Speed || 6; // speed of snowfall (recommended values: 4-8) const snowFallSpeed = config.Speed !== undefined ? config.Speed : 6; // speed of snowstorm
const horizontalWind = config.HorizontalWind || 4; // horizontal wind speed (recommended value: 4) const horizontalWind = config.HorizontalWind !== undefined ? config.HorizontalWind : 4; // horizontal wind strength
const verticalVariation = config.VerticalVariation || 2; // vertical variation (recommended value: 2) const verticalVariation = config.VerticalVariation !== undefined ? config.VerticalVariation : 2; // vertical variation
let msgPrinted = false; // flag to prevent multiple console messages let msgPrinted = false; // flag to prevent multiple console messages
@@ -184,8 +184,8 @@ function initializeSnowstorm() {
} }
const container = document.querySelector('.snowstorm-container'); const container = document.querySelector('.snowstorm-container');
if (container) { if (container) {
const screenWidth = window.innerWidth; // get the screen width to detect mobile devices const isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
if (screenWidth < 768) { // lower count of snowflakes on mobile devices if (isMobile) { // lower count of snowflakes on mobile devices
console.log('Mobile device detected. Reducing snowflakes count.'); console.log('Mobile device detected. Reducing snowflakes count.');
snowflakesCount = snowflakesCountMobile; snowflakesCount = snowflakesCountMobile;
} }

View File

@@ -5,12 +5,13 @@
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
pointer-events: none; pointer-events: none;
z-index: 9999; z-index: 10;
overflow: hidden; overflow: hidden;
contain: strict; contain: strict;
} }
.space-bg-glow { .space-bg-glow {
will-change: transform;
position: absolute; position: absolute;
top: 0; left: 0; width: 100vw; height: 100vh; top: 0; left: 0; width: 100vw; height: 100vh;
background: radial-gradient(circle at 70% 30%, rgba(138, 43, 226, 0.15), transparent 60%), background: radial-gradient(circle at 70% 30%, rgba(138, 43, 226, 0.15), transparent 60%),
@@ -33,6 +34,7 @@
} }
.space-shooting-star { .space-shooting-star {
will-change: opacity;
position: absolute; position: absolute;
width: 250px; width: 250px;
height: 3px; height: 3px;
@@ -61,11 +63,11 @@
} }
.space-symbol img { .space-symbol img {
will-change: transform;
width: 6vh; width: 6vh;
height: auto; height: auto;
max-width: 60px; max-width: 60px;
object-fit: contain; object-fit: contain;
/* Add a slow spin to images */
animation: space-slow-spin var(--rot-dur, 20s) linear infinite; animation: space-slow-spin var(--rot-dur, 20s) linear infinite;
} }

View File

@@ -1,18 +1,20 @@
const config = window.SeasonalsPluginConfig?.Space || {}; const config = window.SeasonalsPluginConfig?.Space || {};
const space = config.EnableSpace !== undefined ? config.EnableSpace : true; const space = config.EnableSpace !== undefined ? config.EnableSpace : true; // enable/disable space
const planetCountConf = config.PlanetCount !== undefined ? config.PlanetCount : 6; const planetCountConf = config.PlanetCount !== undefined ? config.PlanetCount : 6; // count of planets
const astronautCountConf = config.AstronautCount !== undefined ? config.AstronautCount : 1; const astronautCountConf = config.AstronautCount !== undefined ? config.AstronautCount : 1; // count of astronaut
const satelliteCountConf = config.SatelliteCount !== undefined ? config.SatelliteCount : 4; const satelliteCountConf = config.SatelliteCount !== undefined ? config.SatelliteCount : 4; // count of satellite
const issCountConf = config.IssCount !== undefined ? config.IssCount : 1; const issCountConf = config.IssCount !== undefined ? config.IssCount : 1; // count of iss
const rocketCountConf = config.RocketCount !== undefined ? config.RocketCount : 1; const rocketCountConf = config.RocketCount !== undefined ? config.RocketCount : 1; // count of rocket/space shuttle
const useRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true; const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different durations
const enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false; const symbolCountMobile = config.SymbolCountMobile !== undefined ? config.SymbolCountMobile : 2; // Devisor to reduce number of objects on mobile
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true;
// Credit: https://lottiefiles.com/free-animation/astronaut-63lcWG4Xnh
const astronautImages = [ const astronautImages = [
"../Seasonals/Resources/space_assets/astronaut_1.gif" "../Seasonals/Resources/space_assets/astronaut_1.gif"
]; ];
// Credits: https://flaticon.com
const planetImages = [ const planetImages = [
"../Seasonals/Resources/space_assets/planet_1.png", "../Seasonals/Resources/space_assets/planet_1.png",
"../Seasonals/Resources/space_assets/planet_2.png", "../Seasonals/Resources/space_assets/planet_2.png",
@@ -24,13 +26,21 @@ const planetImages = [
"../Seasonals/Resources/space_assets/planet_8.png", "../Seasonals/Resources/space_assets/planet_8.png",
"../Seasonals/Resources/space_assets/planet_9.png" "../Seasonals/Resources/space_assets/planet_9.png"
]; ];
// Credits: https://lottiefiles.com/free-animation/s-satellite-vfnNE8AALo
const satelliteImages = [ const satelliteImages = [
"../Seasonals/Resources/space_assets/Satellite_1.gif", "../Seasonals/Resources/space_assets/Satellite_1.gif",
"../Seasonals/Resources/space_assets/Satellite_2.gif" "../Seasonals/Resources/space_assets/Satellite_2.gif"
]; ];
// Credit: https://pixabay.com/de/illustrations/raumstation-raum-struktur-8023777/
const issImage = "../Seasonals/Resources/space_assets/iss.png"; const issImage = "../Seasonals/Resources/space_assets/iss.png";
/**
* Credits:
* https://lottiefiles.com/free-animation/rocket-MYUQ3UFq3k
* https://pixabay.com/de/vectors/space-shuttle-atlantis-nasa-156012/
*/
const rocketImages = [ const rocketImages = [
"../Seasonals/Resources/space_assets/rocket.gif", "../Seasonals/Resources/space_assets/rocket.gif",
"../Seasonals/Resources/space_assets/space-shuttle.png" "../Seasonals/Resources/space_assets/space-shuttle.png"
@@ -78,26 +88,19 @@ function createSpace() {
document.body.appendChild(container); document.body.appendChild(container);
} }
const standardPlanetCount = 4; // const standardPlanetCount = 4;
const standardAstronautCount = 1; // const standardAstronautCount = 1;
const standardSatelliteCount = 2; // const standardSatelliteCount = 2;
const standardIssCount = 1; // const standardIssCount = 1;
const standardRocketCount = 1; // const standardRocketCount = 1;
let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches; let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
let pCount = planetCountConf; let divisor = isMobile ? Math.max(1, symbolCountMobile) : 1;
let aCount = astronautCountConf; let pCount = Math.floor(planetCountConf / divisor);
let sCount = satelliteCountConf; let aCount = Math.floor(astronautCountConf / divisor);
let iCount = issCountConf; let sCount = Math.floor(satelliteCountConf / divisor);
let rCount = rocketCountConf; let iCount = Math.floor(issCountConf / divisor);
let rCount = Math.floor(rocketCountConf / divisor);
if (isMobile && !enableRandomMobile) {
pCount = standardPlanetCount;
aCount = standardAstronautCount;
sCount = standardSatelliteCount;
iCount = standardIssCount;
rCount = standardRocketCount;
}
// Add Nebula Glow // Add Nebula Glow
const bgGlow = document.createElement('div'); const bgGlow = document.createElement('div');
@@ -252,7 +255,8 @@ function createSpace() {
// Swap to a random image from the pool every time it completes an orbit (disappears) // Swap to a random image from the pool every time it completes an orbit (disappears)
if (imageArr.length > 1) { if (imageArr.length > 1) {
// The animation delay pushes the initial cycle, so we use setInterval matched to duration // The animation delay pushes the initial cycle, so we use setInterval matched to duration
setInterval(() => { const intervalId = setInterval(() => {
if (!document.body.contains(container)) { clearInterval(intervalId); return; }
// Update only if currently out of bounds to avoid popping // Update only if currently out of bounds to avoid popping
const rect = symbol.getBoundingClientRect(); const rect = symbol.getBoundingClientRect();
if (rect.right < 0 || rect.left > window.innerWidth) { if (rect.right < 0 || rect.left > window.innerWidth) {

View File

@@ -8,6 +8,7 @@
height: 100%; height: 100%;
pointer-events: none; pointer-events: none;
z-index: 10; z-index: 10;
contain: layout paint;
} }
.spooky { .spooky {
@@ -16,19 +17,8 @@
will-change: transform; will-change: transform;
translate: 0 120vh; translate: 0 120vh;
z-index: 15; z-index: 15;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none; user-select: none;
-webkit-user-select: none;
cursor: default; cursor: default;
-webkit-animation-name: spooky-float;
-webkit-animation-duration: 10s;
-webkit-animation-timing-function: linear;
-webkit-animation-iteration-count: infinite;
-webkit-animation-play-state: running;
animation-name: spooky-float; animation-name: spooky-float;
animation-duration: 10s; animation-duration: 10s;
animation-timing-function: linear; animation-timing-function: linear;
@@ -40,13 +30,6 @@
width: 30px; width: 30px;
height: auto; height: auto;
will-change: transform; will-change: transform;
-webkit-animation-name: spooky-shake;
-webkit-animation-duration: 3s;
-webkit-animation-timing-function: ease-in-out;
-webkit-animation-iteration-count: infinite;
-webkit-animation-play-state: running;
animation-name: spooky-shake; animation-name: spooky-shake;
animation-duration: 3s; animation-duration: 3s;
animation-timing-function: ease-in-out; animation-timing-function: ease-in-out;
@@ -59,22 +42,6 @@
width: 100%; width: 100%;
} }
@-webkit-keyframes spooky-float {
0% {
translate: 0 120vh;
opacity: 0;
}
10% {
opacity: 0.8;
}
90% {
opacity: 0.8;
}
100% {
translate: 0 -150px;
opacity: 0;
}
}
@keyframes spooky-float { @keyframes spooky-float {
0% { 0% {
@@ -93,14 +60,6 @@
} }
} }
@-webkit-keyframes spooky-shake {
0%, 100% {
transform: translateX(0) scale(1) rotate(15deg);
}
50% {
transform: translateX(80px) scale(1.2) rotate(-15deg);
}
}
@keyframes spooky-shake { @keyframes spooky-shake {
0%, 100% { 0%, 100% {
@@ -111,7 +70,6 @@
} }
} }
/* Base predefined starting offsets (if not overridden by js) */
.spooky:nth-of-type(0) { left: 1%; } .spooky:nth-of-type(0) { left: 1%; }
.spooky:nth-of-type(1) { left: 10%; } .spooky:nth-of-type(1) { left: 10%; }
.spooky:nth-of-type(2) { left: 20%; } .spooky:nth-of-type(2) { left: 20%; }

View File

@@ -1,11 +1,17 @@
const config = window.SeasonalsPluginConfig?.Spooky || {}; const config = window.SeasonalsPluginConfig?.Spooky || {};
const spooky = config.EnableSpooky !== undefined ? config.EnableSpooky : true; // enable/disable const spooky = config.EnableSpooky !== undefined ? config.EnableSpooky : true; // enable/disable spooky
const spookyCount = config.SymbolCount || 25; // count of random extra symbols const spookyCount = config.SymbolCount !== undefined ? config.SymbolCount : 25; // count of symbols
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different durations
const enableSpookySway = config.EnableSpookySway !== undefined ? config.EnableSpookySway : true; const enableSpookySway = config.EnableSpookySway !== undefined ? config.EnableSpookySway : true; // enable/disable spooky sway
const spookySize = config.SpookySize || 20; const spookySize = config.SpookySize !== undefined ? config.SpookySize : 20; // size of elements
const spookyGlowSize = config.SpookyGlowSize !== undefined ? config.SpookyGlowSize : 2; const spookyGlowSize = config.SpookyGlowSize !== undefined ? config.SpookyGlowSize : 2; // size of element glow
const spookyImages = [
"../Seasonals/Resources/halloween_images/ghost_20x20.png",
"../Seasonals/Resources/halloween_images/bat_20x20.png",
"../Seasonals/Resources/halloween_images/pumpkin_20x20.png",
];
let msgPrinted = false; let msgPrinted = false;
@@ -20,13 +26,13 @@ function toggleSpooky() {
const hasUserMenu = document.querySelector('#app-user-menu'); const hasUserMenu = document.querySelector('#app-user-menu');
if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) { if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) {
spookyContainer.style.display = 'none'; // hide spooky spookyContainer.style.display = 'none';
if (!msgPrinted) { if (!msgPrinted) {
console.log('Spooky Theme hidden'); console.log('Spooky Theme hidden');
msgPrinted = true; msgPrinted = true;
} }
} else { } else {
spookyContainer.style.display = 'block'; // show spooky spookyContainer.style.display = 'block';
if (msgPrinted) { if (msgPrinted) {
console.log('Spooky Theme visible'); console.log('Spooky Theme visible');
msgPrinted = false; msgPrinted = false;
@@ -34,7 +40,6 @@ function toggleSpooky() {
} }
} }
// observe changes in the DOM
const observer = new MutationObserver(toggleSpooky); const observer = new MutationObserver(toggleSpooky);
observer.observe(document.body, { observer.observe(document.body, {
childList: true, childList: true,
@@ -42,13 +47,7 @@ observer.observe(document.body, {
attributes: true attributes: true
}); });
const spookyImages = [
"../Seasonals/Resources/halloween_images/ghost_20x20.png",
"../Seasonals/Resources/halloween_images/bat_20x20.png",
"../Seasonals/Resources/halloween_images/pumpkin_20x20.png",
];
// create spooky objects
function createSpooky() { function createSpooky() {
const container = document.querySelector('.spooky-container') || document.createElement("div"); const container = document.querySelector('.spooky-container') || document.createElement("div");
@@ -95,7 +94,6 @@ function createSpooky() {
}); });
} }
// Add configured extra symbols
for (let i = 0; i < spookyCount; i++) { for (let i = 0; i < spookyCount; i++) {
const spookyOuter = document.createElement("div"); const spookyOuter = document.createElement("div");
spookyOuter.className = "spooky"; spookyOuter.className = "spooky";

View File

@@ -5,16 +5,17 @@
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
pointer-events: none; pointer-events: none;
z-index: 9999; z-index: 10;
overflow: hidden; overflow: hidden;
contain: strict; contain: strict;
} }
.sports-symbol { .sports-symbol {
position: absolute; position: absolute;
top: -10vh; top: 0;
opacity: 0.9; opacity: 0.9;
z-index: 40; z-index: 40;
translate: 0 -10vh;
} }
.sports-inner { .sports-inner {
@@ -29,13 +30,15 @@
} }
.sports-confetti { .sports-confetti {
will-change: transform, opacity;
position: absolute; position: absolute;
top: -5vh; top: 0;
width: 10px; width: 10px;
height: 15px; height: 15px;
opacity: 0.8; opacity: 0.8;
animation: sports-confetti-fall linear infinite; animation: sports-confetti-fall linear infinite;
border-radius: 2px; border-radius: 2px;
translate: 0 -5vh;
} }
.sports-confetti.circle { .sports-confetti.circle {

View File

@@ -1,29 +1,30 @@
const config = window.SeasonalsPluginConfig?.Sports || {}; const config = window.SeasonalsPluginConfig?.Sports || {};
const sports = config.EnableSports !== undefined ? config.EnableSports : true; const sports = config.EnableSports !== undefined ? config.EnableSports : true; // enable/disable sports
const symbolCount = config.SymbolCount || 5; const symbolCount = config.SymbolCount !== undefined ? config.SymbolCount : 5; // count of balls per category
const useRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true; const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different durations
const enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false; const enableTrophy = config.EnableTrophy !== undefined ? config.EnableTrophy : false; // enable/disable trophy
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; const rawSportsBalls = config.SportsBalls !== undefined ? config.SportsBalls : 'football,basketball,tennis,volleyball'; // set sports categories to spawn
const enableTrophy = config.EnableTrophy !== undefined ? config.EnableTrophy : false; const turfColorHex = config.TurfColor !== undefined ? config.TurfColor : '#228b22'; // color of turf
const confettiColors = config.ConfettiColors ? config.ConfettiColors.split(',') : ['#000000', '#FF0000', '#FFCC00']; // Add Country Colored confetti
// Pre-declare and manage image assets // Pre-declare and manage image assets
// Credits: https://flaticon.com
const SPORTS_ASSETS = { const SPORTS_ASSETS = {
badminton: ['badminton_1', 'badminton_2'], badminton: ['badminton_1', 'badminton_2'],
baseball: ['baseball_1', 'baseball_2'], baseball: ['baseball_1', 'baseball_2'],
basketball: ['basketball_1', 'basketball_2'], basketball: ['basketball_1', 'basketball_2'],
billiard: Array.from({length: 14}, (_, i) => `billiard_ball_${i + 1}`), billiard: Array.from({length: 14}, (_, i) => `billiard_ball_${i + 1}`),
bowling: ['bowling_1', 'bowling_2'], bowling: ['bowling_1', 'bowling_2'],
football: Array.from({length: 5}, (_, i) => `football_${i + 1}`), football: Array.from({length: 5}, (_, i) => `football_${i + 1}`),
golf: ['golf_ball_1', 'golf_ball_2'], golf: ['golf_ball_1', 'golf_ball_2'],
rugby: ['rugby_ball_1', 'rugby_ball_2'], rugby: ['rugby_ball_1', 'rugby_ball_2'],
table_tennis: ['table_tennis_ball_1', 'table_tennis_ball_2'], table_tennis: ['table_tennis_ball_1', 'table_tennis_ball_2'],
tennis: ['tennis_ball_1', 'tennis_ball_2'], tennis: ['tennis_ball_1', 'tennis_ball_2'],
volleyball: ['volleyball_1', 'volleyball_2'], volleyball: ['volleyball_1', 'volleyball_2'],
waterball: ['waterball_1', 'waterball_2'] waterball: ['waterball_1', 'waterball_2']
}; };
const turfColorHex = config.TurfColor || '#228b22';
let msgPrinted = false; let msgPrinted = false;
@@ -67,11 +68,10 @@ function createSports() {
document.body.appendChild(container); document.body.appendChild(container);
} }
// Parse turf color config
// Create a turf/grass overlay at the bottom using the provided hex // Create a turf/grass overlay at the bottom using the provided hex
const turf = document.createElement('div'); const turf = document.createElement('div');
turf.className = 'sports-turf'; turf.className = 'sports-turf';
// Using hex with transparency (e.g., 4D = 30%, CC = 80%) // Using hex with transparency
turf.style.background = `linear-gradient(180deg, transparent 0%, ${turfColorHex}4D 30%, ${turfColorHex}CC 100%)`; turf.style.background = `linear-gradient(180deg, transparent 0%, ${turfColorHex}4D 30%, ${turfColorHex}CC 100%)`;
container.appendChild(turf); container.appendChild(turf);
@@ -85,7 +85,6 @@ function createSports() {
const useRandomDuration = enableDifferentDuration !== false; const useRandomDuration = enableDifferentDuration !== false;
// Map standard sports balls to spawn based on category configuration // Map standard sports balls to spawn based on category configuration
const rawSportsBalls = config.SportsBalls || 'football,basketball,tennis,volleyball';
const chosenCategories = rawSportsBalls.split(',').map(s => s.trim()).filter(s => s !== ''); const chosenCategories = rawSportsBalls.split(',').map(s => s.trim()).filter(s => s !== '');
const createBall = (randomItem) => { const createBall = (randomItem) => {
@@ -140,7 +139,7 @@ function createSports() {
// Create falling sports balls // Create falling sports balls
chosenCategories.forEach(category => { chosenCategories.forEach(category => {
let variants = SPORTS_ASSETS[category]; let variants = SPORTS_ASSETS[category];
if (!variants) variants = [category]; // Legacy fallback if (!variants) variants = [category];
for (let i = 0; i < ballsPerCategory; i++) { for (let i = 0; i < ballsPerCategory; i++) {
// Pick a random variant // Pick a random variant
@@ -162,7 +161,6 @@ function createSports() {
let trophyImg = document.createElement('img'); let trophyImg = document.createElement('img');
trophyImg.src = `../Seasonals/Resources/sport_assets/trophy.gif`; trophyImg.src = `../Seasonals/Resources/sport_assets/trophy.gif`;
// Randomly scale trophy slightly larger
trophyImg.style.transform = `scale(${Math.random() * 0.5 + 0.8})`; trophyImg.style.transform = `scale(${Math.random() * 0.5 + 0.8})`;
trophyImg.onerror = function() { trophyImg.onerror = function() {
this.style.display = 'none'; this.style.display = 'none';
@@ -203,16 +201,14 @@ function createSports() {
}, arcDuration * 1000 + 500); }, arcDuration * 1000 + 500);
// Schedule the next trophy // Schedule the next trophy
setTimeout(launchTrophy, Math.random() * 20000 + 10000); // Wait 10-30s until next trophy setTimeout(() => { if (document.body.contains(container)) launchTrophy(); }, Math.random() * 20000 + 10000); // Wait 10-30s until next trophy
} }
// Launch initial trophy after a short delay // Launch initial trophy after a short delay
if (enableTrophy) { if (enableTrophy) {
setTimeout(launchTrophy, Math.random() * 5000 + 2000); setTimeout(() => { if (document.body.contains(container)) launchTrophy(); }, Math.random() * 5000 + 2000);
} }
// Add Germany Colored confetti (Black, Red, Gold)
const confettiColors = ['#000000', '#FF0000', '#FFCC00'];
const confettiCount = isMobile ? 30 : 60; const confettiCount = isMobile ? 30 : 60;
for (let i = 0; i < confettiCount; i++) { for (let i = 0; i < confettiCount; i++) {
@@ -257,8 +253,6 @@ function createSports() {
} }
} }
/* Removed legacy fallback logic */
function initializeSports() { function initializeSports() {
if (!sports) return; if (!sports) return;
createSports(); createSports();

View File

@@ -29,13 +29,14 @@
/* Sunbeams */ /* Sunbeams */
.spring-sunbeam { .spring-sunbeam {
position: fixed; position: fixed;
top: -50%; top: 0;
height: 200%; height: 200%;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(255, 255, 200, 0.08) 50%, rgba(255, 255, 255, 0)); background: linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(255, 255, 200, 0.08) 50%, rgba(255, 255, 255, 0));
z-index: 5; z-index: 5;
transform-origin: top center; transform-origin: top center;
pointer-events: none; pointer-events: none;
opacity: 0; opacity: 0;
translate: 0 -50vh;
} }
/* Grass Container (Wrapper) */ /* Grass Container (Wrapper) */
@@ -72,6 +73,7 @@
/* SVG Meadow Layer */ /* SVG Meadow Layer */
.spring-meadow-layer { .spring-meadow-layer {
will-change: transform;
position: absolute; position: absolute;
bottom: 0; bottom: 0;
left: 0; left: 0;
@@ -94,6 +96,7 @@
} }
.spring-sway { .spring-sway {
will-change: transform;
transform-origin: bottom center; transform-origin: bottom center;
animation: spring-meadow-sway 4s ease-in-out infinite alternate; animation: spring-meadow-sway 4s ease-in-out infinite alternate;
} }

View File

@@ -1,14 +1,14 @@
const config = window.SeasonalsPluginConfig?.Spring || {}; const config = window.SeasonalsPluginConfig?.Spring || {};
const spring = config.EnableSpring !== undefined ? config.EnableSpring : true; // Enable/disable spring const spring = config.EnableSpring !== undefined ? config.EnableSpring : true; // enable/disable spring
const pollenCount = config.PollenCount || 30; // Number of pollen particles const pollenCount = config.PollenCount !== undefined ? config.PollenCount : 30; // count of pollen
const sunbeamCount = config.SunbeamCount || 5; // Number of sunbeams const enableSunbeams = config.EnableSpringSunbeams !== undefined ? config.EnableSpringSunbeams : true; // enable/disable sunbeams
const enableSunbeams = config.EnableSpringSunbeams !== undefined ? config.EnableSpringSunbeams : true; // Enable/disable sunbeams const sunbeamCount = config.SunbeamCount !== undefined ? config.SunbeamCount : 5; // count of sunbeams
const birdCount = config.BirdCount !== undefined ? config.BirdCount : 3; // Number of birds const birdCount = config.BirdCount !== undefined ? config.BirdCount : 3; // count of birds
const butterflyCount = config.ButterflyCount !== undefined ? config.ButterflyCount : 4; // Number of butterflies const butterflyCount = config.ButterflyCount !== undefined ? config.ButterflyCount : 4; // count of butterflies
const beeCount = config.BeeCount !== undefined ? config.BeeCount : 2; // Number of bees const beeCount = config.BeeCount !== undefined ? config.BeeCount : 2; // count of bees
const ladybugCount = config.LadybugCount !== undefined ? config.LadybugCount : 2; // Number of ladybugs const ladybugCount = config.LadybugCount !== undefined ? config.LadybugCount : 2; // count of ladybugs
const randomSpring = config.EnableRandomSpring !== undefined ? config.EnableRandomSpring : true; // Enable random spring objects const symbolCountMobile = config.SymbolCountMobile !== undefined ? config.SymbolCountMobile : 2; // Devisor to reduce number of objects on mobile
// Credit: https://lottiefiles.com/free-animation/birds-flying-V7O0L8jkOg // Credit: https://lottiefiles.com/free-animation/birds-flying-V7O0L8jkOg
const birdImages = [ const birdImages = [
@@ -23,7 +23,10 @@ const butterflyImages = [
'../Seasonals/Resources/spring_assets/Butterfly_2.gif' '../Seasonals/Resources/spring_assets/Butterfly_2.gif'
]; ];
// Credit: https://lottiefiles.com/free-animation/loading-flying-beee-WcTfIccdJZ
const beeImage = '../Seasonals/Resources/spring_assets/Bee.gif'; const beeImage = '../Seasonals/Resources/spring_assets/Bee.gif';
// Credit: https://pixabay.com/gifs/ladybug-insect-nature-fly-wings-5068/
const ladybugImage = '../Seasonals/Resources/spring_assets/ladybug.gif'; const ladybugImage = '../Seasonals/Resources/spring_assets/ladybug.gif';
let msgPrinted = false; let msgPrinted = false;
@@ -230,28 +233,35 @@ function initializeSpring() {
const container = document.querySelector('.spring-container'); const container = document.querySelector('.spring-container');
if (container) { if (container) {
if (randomSpring) { let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
// Add Pollen let divisor = isMobile ? Math.max(1, symbolCountMobile) : 1;
for (let i = 0; i < pollenCount; i++) {
createPollen(container);
}
// Add Birds let adjPollen = Math.floor(pollenCount / divisor);
for (let i = 0; i < birdCount; i++) { let adjBird = Math.floor(birdCount / divisor);
setTimeout(() => createBird(container), Math.random() * 1000); // 0-1s desync let adjButterfly = Math.floor(butterflyCount / divisor);
} let adjBee = Math.floor(beeCount / divisor);
// Add Butterflies let adjLadybug = Math.floor(ladybugCount / divisor);
for (let i = 0; i < butterflyCount; i++) {
setTimeout(() => createButterfly(container), Math.random() * 1000); // 0-1s desync // Add Pollen
} for (let i = 0; i < adjPollen; i++) {
// Add Bees createPollen(container);
for (let i = 0; i < beeCount; i++) { }
setTimeout(() => createBee(container), Math.random() * 1000); // 0-1s desync
} // Add Birds
// Add Ladybugs for (let i = 0; i < adjBird; i++) {
for (let i = 0; i < ladybugCount; i++) { setTimeout(() => createBird(container), Math.random() * 1000); // 0-1s desync
setTimeout(() => createLadybugGif(container), Math.random() * 1000); // 0-1s desync }
} // Add Butterflies
for (let i = 0; i < adjButterfly; i++) {
setTimeout(() => createButterfly(container), Math.random() * 1000); // 0-1s desync
}
// Add Bees
for (let i = 0; i < adjBee; i++) {
setTimeout(() => createBee(container), Math.random() * 1000); // 0-1s desync
}
// Add Ladybugs
for (let i = 0; i < adjLadybug; i++) {
setTimeout(() => createLadybugGif(container), Math.random() * 1000); // 0-1s desync
} }
} }
} }
@@ -295,7 +305,7 @@ function createBird(container) {
wrapper.addEventListener('animationend', (e) => { wrapper.addEventListener('animationend', (e) => {
if (e.animationName.includes('fly-')) { if (e.animationName.includes('fly-')) {
wrapper.remove(); wrapper.remove();
createBird(container); if (document.body.contains(container)) createBird(container);
} }
}); });
@@ -338,7 +348,7 @@ function createButterfly(container) {
wrapper.addEventListener('animationend', (e) => { wrapper.addEventListener('animationend', (e) => {
if (e.animationName.includes('fly-')) { if (e.animationName.includes('fly-')) {
wrapper.remove(); wrapper.remove();
createButterfly(container); if (document.body.contains(container)) createButterfly(container);
} }
}); });
@@ -385,7 +395,7 @@ function createBee(container) {
wrapper.addEventListener('animationend', (e) => { wrapper.addEventListener('animationend', (e) => {
if (e.animationName.includes('fly-')) { if (e.animationName.includes('fly-')) {
wrapper.remove(); wrapper.remove();
createBee(container); if (document.body.contains(container)) createBee(container);
} }
}); });
@@ -428,7 +438,7 @@ function createLadybugGif(container) {
wrapper.addEventListener('animationend', (e) => { wrapper.addEventListener('animationend', (e) => {
if (e.animationName.includes('walk-')) { if (e.animationName.includes('walk-')) {
wrapper.remove(); wrapper.remove();
createLadybugGif(container); if (document.body.contains(container)) createLadybugGif(container);
} }
}); });

View File

@@ -13,10 +13,11 @@
.starwars-center { .starwars-center {
position: absolute; position: absolute;
top: 50%; top: 0;
left: 50%; left: 50%;
width: 0; width: 0;
height: 0; height: 0;
translate: 0 50vh;
} }
.starwars-streak { .starwars-streak {

View File

@@ -1,5 +1,5 @@
const config = window.SeasonalsPluginConfig?.StarWars || {}; const config = window.SeasonalsPluginConfig?.StarWars || {};
const starwars = config.EnableStarWars !== undefined ? config.EnableStarWars : true; const starwars = config.EnableStarWars !== undefined ? config.EnableStarWars : true; // enable/disable starwars
let msgPrinted = false; let msgPrinted = false;

View File

@@ -1,10 +1,10 @@
const config = window.SeasonalsPluginConfig?.Storm || {}; const config = window.SeasonalsPluginConfig?.Storm || {};
const enabled = config.EnableStorm !== undefined ? config.EnableStorm : true; const enabled = config.EnableStorm !== undefined ? config.EnableStorm : true; // enable/disable storm
const isMobile = window.innerWidth <= 768; const isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
const elementCount = isMobile ? (config.RaindropCountMobile || 150) : (config.RaindropCount || 300); const elementCount = isMobile ? (config.RaindropCountMobile || 150) : (config.RaindropCount || 300); // count of raindrops
const enableLightning = config.EnableLightning !== undefined ? config.EnableLightning : true; const enableLightning = config.EnableLightning !== undefined ? config.EnableLightning : true; // enable/disable lightning
const rainSpeed = config.RainSpeed || 1.0; const rainSpeed = config.RainSpeed !== undefined ? config.RainSpeed : 1.0; // speed of rain
let msgPrinted = false; let msgPrinted = false;

View File

@@ -1,11 +1,10 @@
const config = window.SeasonalsPluginConfig?.Summer || {}; const config = window.SeasonalsPluginConfig?.Summer || {};
const summer = config.EnableSummer !== undefined ? config.EnableSummer : true; // Enable/disable summer theme const summer = config.EnableSummer !== undefined ? config.EnableSummer : true; // enable/disable summer
const bubbleCount = config.BubbleCount || 30; // Number of bubbles const bubbleCount = config.BubbleCount !== undefined ? config.BubbleCount : 30; // count of bubbles
const dustCount = config.DustCount || 50; // Number of dust particles const dustCount = config.DustCount !== undefined ? config.DustCount : 50; // count of dust particles
const randomSummer = config.EnableRandomSummer !== undefined ? config.EnableRandomSummer : true; // Enable random generating objects const symbolCountMobile = config.SymbolCountMobile !== undefined ? config.SymbolCountMobile : 2; // Devisor to reduce number of objects on mobile
const randomSummerMobile = config.EnableRandomSummerMobile !== undefined ? config.EnableRandomSummerMobile : false; // Enable random generating objects on mobile const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different durations
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // Randomize animation duration of bubbles and dust
let msgPrinted = false; let msgPrinted = false;
@@ -39,34 +38,23 @@ observer.observe(document.body, {
subtree: true, subtree: true,
attributes: true attributes: true
}); });
function createBubble(container, isDust = false) {
function createBubble(container) {
const bubble = document.createElement('div'); const bubble = document.createElement('div');
if (isDust) { bubble.classList.add('summer-bubble');
bubble.classList.add('summer-dust');
} else {
bubble.classList.add('summer-bubble');
}
// Random horizontal position // Random horizontal position
const randomLeft = Math.random() * 100; const randomLeft = Math.random() * 100;
bubble.style.left = `${randomLeft}%`; bubble.style.left = `${randomLeft}%`;
// Random size // MARK: BUBBLE SIZE
if (!isDust) { const size = Math.random() * 20 + 10; // 10-30px bubbles
// MARK: BUBBLE SIZE bubble.style.width = `${size}px`;
const size = Math.random() * 20 + 10; // 10-30px bubbles bubble.style.height = `${size}px`;
bubble.style.width = `${size}px`;
bubble.style.height = `${size}px`;
} else {
// MARK: DUST SIZE
const size = Math.random() * 3 + 1; // 1-4px dust
bubble.style.width = `${size}px`;
bubble.style.height = `${size}px`;
}
// Animation properties // Animation properties
const duration = isDust ? (Math.random() * 20 + 10) : (Math.random() * 10 + 5); // Dust is slower const duration = (Math.random() * 10 + 5);
const delay = Math.random() * 10; const delay = Math.random() * 10;
if (enableDifferentDuration) { if (enableDifferentDuration) {
@@ -77,19 +65,30 @@ function createBubble(container, isDust = false) {
container.appendChild(bubble); container.appendChild(bubble);
} }
function addRandomSummerObjects() { function createDust(container) {
const container = document.querySelector('.summer-container'); const dust = document.createElement('div');
if (!container) return;
dust.classList.add('summer-dust');
// Random horizontal position
const randomLeft = Math.random() * 100;
dust.style.left = `${randomLeft}%`;
// MARK: DUST SIZE
const size = Math.random() * 3 + 1; // 1-4px dust
dust.style.width = `${size}px`;
dust.style.height = `${size}px`;
// Animation properties
const duration = (Math.random() * 20 + 10); // Dust is slower
const delay = Math.random() * 10;
// Add bubbles if (enableDifferentDuration) {
for (let i = 0; i < bubbleCount; i++) { dust.style.animationDuration = `${duration}s`;
createBubble(container, false);
}
// Add some dust particles
for (let i = 0; i < dustCount; i++) {
createBubble(container, true);
} }
dust.style.animationDelay = `${delay}s`;
container.appendChild(dust);
} }
function initSummerObjects() { function initSummerObjects() {
@@ -101,38 +100,18 @@ function initSummerObjects() {
document.body.appendChild(container); document.body.appendChild(container);
} }
// Initial bubbles/dust let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
for (let i = 0; i < 10; i++) { let limitBubbles = isMobile ? Math.floor(bubbleCount / Math.max(1, symbolCountMobile)) : bubbleCount;
const bubble = document.createElement('div'); let limitDust = isMobile ? Math.floor(dustCount / Math.max(1, symbolCountMobile)) : dustCount;
const isDust = Math.random() > 0.5;
if (isDust) { // Initial bubbles
bubble.classList.add('summer-dust'); for (let i = 0; i < limitBubbles; i++) {
} else { createBubble(container);
bubble.classList.add('summer-bubble'); }
}
// Initial dust
const randomLeft = Math.random() * 100; for (let i = 0; i < limitDust; i++) {
bubble.style.left = `${randomLeft}%`; createDust(container);
if (!isDust) {
// MARK: BUBBLE SIZE
const size = Math.random() * 20 + 10;
bubble.style.width = `${size}px`;
bubble.style.height = `${size}px`;
} else {
// MARK: DUST SIZE
const size = Math.random() * 3 + 1;
bubble.style.width = `${size}px`;
bubble.style.height = `${size}px`;
}
const duration = isDust ? (Math.random() * 20 + 10) : (Math.random() * 10 + 5);
if (enableDifferentDuration) {
bubble.style.animationDuration = `${duration}s`;
}
bubble.style.animationDelay = `-${Math.random() * 10}s`;
container.appendChild(bubble);
} }
} }
@@ -140,11 +119,6 @@ function initializeSummer() {
if (!summer) return; if (!summer) return;
initSummerObjects(); initSummerObjects();
toggleSummer(); toggleSummer();
const screenWidth = window.innerWidth;
if (randomSummer && (screenWidth > 768 || randomSummerMobile)) {
addRandomSummerObjects();
}
} }
initializeSummer(); initializeSummer();

View File

@@ -235,41 +235,41 @@
<label for="theme-select">Theme:</label> <label for="theme-select">Theme:</label>
<select id="theme-select"> <select id="theme-select">
<option value="" selected disabled>— Select a theme —</option> <option value="" selected disabled>— Select a theme —</option>
<option value="autumn">Autumn</option>
<option value="birthday">Birthday</option>
<option value="carnival">Carnival (Confetti)</option>
<option value="cherryblossom">Cherryblossom</option>
<option value="christmas">Christmas</option>
<option value="earthday">Earth Day</option>
<option value="easter">Easter</option>
<option value="eid">Eid al-Fitr</option>
<option value="eurovision">Eurovision</option>
<option value="filmnoir">Film-Noir</option>
<option value="fireworks">Fireworks</option>
<option value="frost">Frost / Ice</option>
<option value="friday13">Friday the 13th</option>
<option value="halloween">Halloween</option>
<option value="hearts">Hearts</option>
<option value="marioday">Mario Day</option>
<option value="matrix">Matrix</option>
<option value="oktoberfest">Oktoberfest</option>
<option value="olympia">Olympia</option>
<option value="oscar">Oscar Awards</option>
<option value="pride">Pride</option>
<option value="rain">Rain</option>
<option value="resurrection">Resurrection</option>
<option value="santa">Santa</option>
<option value="snowfall">Snowfall</option> <option value="snowfall">Snowfall</option>
<option value="snowflakes">Snowflakes</option> <option value="snowflakes">Snowflakes</option>
<option value="snowstorm">Snowstorm</option> <option value="snowstorm">Snowstorm</option>
<option value="fireworks">Fireworks</option>
<option value="halloween">Halloween</option>
<option value="spooky">Spooky</option>
<option value="hearts">Hearts</option>
<option value="christmas">Christmas</option>
<option value="santa">Santa</option>
<option value="autumn">Autumn</option>
<option value="easter">Easter</option>
<option value="resurrection">Resurrection</option>
<option value="spring">Spring</option>
<option value="summer">Summer (Bubbles)</option>
<option value="carnival">Carnival (Confetti)</option>
<option value="cherryblossom">Cherryblossom</option>
<option value="earthday">Earth Day</option>
<option value="eurovision">Eurovision</option>
<option value="matrix">Matrix</option>
<option value="pride">Pride</option>
<option value="rain">Rain</option>
<option value="storm">Storm (⚠Epilepsy Warning⚠)</option>
<option value="frost">Frost / Ice</option>
<option value="filmnoir">Film-Noir</option>
<option value="oscar">Oscar Awards</option>
<option value="marioday">Mario Day</option>
<option value="starwars">Star Wars Day</option>
<option value="oktoberfest">Oktoberfest</option>
<option value="friday13">Friday the 13th</option>
<option value="eid">Eid al-Fitr</option>
<option value="sports">Sports</option>
<option value="olympia">Olympia</option>
<option value="space">Space / Sci-Fi</option> <option value="space">Space / Sci-Fi</option>
<option value="spooky">Spooky</option>
<option value="sports">Sports</option>
<option value="spring">Spring</option>
<option value="starwars">Star Wars Day</option>
<option value="storm">Storm (⚠Epilepsy Warning⚠)</option>
<option value="summer">Summer (Bubbles)</option>
<option value="underwater">Underwater</option> <option value="underwater">Underwater</option>
<option value="birthday">Birthday</option>
<option value="custom">⚙ Custom (Local Files)</option> <option value="custom">⚙ Custom (Local Files)</option>
</select> </select>
@@ -341,41 +341,41 @@
// ── Built-in theme map (local file paths for testing) ── // ── Built-in theme map (local file paths for testing) ──
const themes = { const themes = {
snowfall: { css: 'snowfall.css', js: 'snowfall.js', container: 'snowfall-container' },
snowflakes: { css: 'snowflakes.css', js: 'snowflakes.js', container: 'snowflakes' },
snowstorm: { css: 'snowstorm.css', js: 'snowstorm.js', container: 'snowstorm-container' },
fireworks: { css: 'fireworks.css', js: 'fireworks.js', container: 'fireworks' },
halloween: { css: 'halloween.css', js: 'halloween.js', container: 'halloween-container' },
spooky: { css: 'spooky.css', js: 'spooky.js', container: 'spooky-container' },
hearts: { css: 'hearts.css', js: 'hearts.js', container: 'hearts-container' },
christmas: { css: 'christmas.css', js: 'christmas.js', container: 'christmas-container' },
santa: { css: 'santa.css', js: 'santa.js', container: 'santa-container' },
autumn: { css: 'autumn.css', js: 'autumn.js', container: 'autumn-container' }, autumn: { css: 'autumn.css', js: 'autumn.js', container: 'autumn-container' },
easter: { css: 'easter.css', js: 'easter.js', container: 'easter-container' }, birthday: { css: 'birthday.css', js: 'birthday.js', container: 'birthday-container' },
resurrection: { css: 'resurrection.css', js: 'resurrection.js', container: 'resurrection-container' },
spring: { css: 'spring.css', js: 'spring.js', container: 'spring-container' },
summer: { css: 'summer.css', js: 'summer.js', container: 'summer-container' },
carnival: { css: 'carnival.css', js: 'carnival.js', container: 'carnival-container' }, carnival: { css: 'carnival.css', js: 'carnival.js', container: 'carnival-container' },
cherryblossom: { css: 'cherryblossom.css', js: 'cherryblossom.js', container: 'cherryblossom-container' }, cherryblossom:{ css: 'cherryblossom.css', js: 'cherryblossom.js',container: 'cherryblossom-container' },
christmas: { css: 'christmas.css', js: 'christmas.js', container: 'christmas-container' },
earthday: { css: 'earthday.css', js: 'earthday.js', container: 'earthday-container' }, earthday: { css: 'earthday.css', js: 'earthday.js', container: 'earthday-container' },
easter: { css: 'easter.css', js: 'easter.js', container: 'easter-container' },
eid: { css: 'eid.css', js: 'eid.js', container: 'eid-container' },
eurovision: { css: 'eurovision.css', js: 'eurovision.js', container: 'eurovision-container' }, eurovision: { css: 'eurovision.css', js: 'eurovision.js', container: 'eurovision-container' },
matrix: { css: 'matrix.css', js: 'matrix.js', container: 'matrix-container' }, filmnoir: { css: 'filmnoir.css', js: 'filmnoir.js', container: 'filmnoir-container' },
fireworks: { css: 'fireworks.css', js: 'fireworks.js', container: 'fireworks' },
frost: { css: 'frost.css', js: 'frost.js', container: 'frost-container' },
friday13: { css: 'friday13.css', js: 'friday13.js', container: 'friday13-container' },
halloween: { css: 'halloween.css', js: 'halloween.js', container: 'halloween-container' },
hearts: { css: 'hearts.css', js: 'hearts.js', container: 'hearts-container' },
marioday: { css: 'marioday.css', js: 'marioday.js', container: 'marioday-container' },
matrix: { css: 'matrix.css', js: 'matrix.js', container: 'matrix-container' },
oktoberfest: { css: 'oktoberfest.css', js: 'oktoberfest.js', container: 'oktoberfest-container' },
olympia: { css: 'olympia.css', js: 'olympia.js', container: 'olympia-container' },
oscar: { css: 'oscar.css', js: 'oscar.js', container: 'oscar-container' },
pride: { css: 'pride.css', js: 'pride.js', container: 'pride-container' }, pride: { css: 'pride.css', js: 'pride.js', container: 'pride-container' },
rain: { css: 'rain.css', js: 'rain.js', container: 'rain-container' }, rain: { css: 'rain.css', js: 'rain.js', container: 'rain-container' },
storm: { css: 'storm.css', js: 'storm.js', container: 'storm-container' }, resurrection: { css: 'resurrection.css', js: 'resurrection.js', container: 'resurrection-container' },
frost: { css: 'frost.css', js: 'frost.js', container: 'frost-container' }, santa: { css: 'santa.css', js: 'santa.js', container: 'santa-container' },
filmnoir: { css: 'filmnoir.css', js: 'filmnoir.js', container: 'filmnoir-container' }, snowfall: { css: 'snowfall.css', js: 'snowfall.js', container: 'snowfall-container' },
oscar: { css: 'oscar.css', js: 'oscar.js', container: 'oscar-container' }, snowflakes: { css: 'snowflakes.css', js: 'snowflakes.js', container: 'snowflakes' },
marioday: { css: 'marioday.css', js: 'marioday.js', container: 'marioday-container' }, snowstorm: { css: 'snowstorm.css', js: 'snowstorm.js', container: 'snowstorm-container' },
starwars: { css: 'starwars.css', js: 'starwars.js', container: 'starwars-container' },
oktoberfest: { css: 'oktoberfest.css', js: 'oktoberfest.js', container: 'oktoberfest-container' },
friday13: { css: 'friday13.css', js: 'friday13.js', container: 'friday13-container' },
eid: { css: 'eid.css', js: 'eid.js', container: 'eid-container' },
sports: { css: 'sports.css', js: 'sports.js', container: 'sports-container' },
olympia: { css: 'olympia.css', js: 'olympia.js', container: 'olympia-container' },
space: { css: 'space.css', js: 'space.js', container: 'space-container' }, space: { css: 'space.css', js: 'space.js', container: 'space-container' },
underwater: { css: 'underwater.css', js: 'underwater.js', container: 'underwater-container' }, spooky: { css: 'spooky.css', js: 'spooky.js', container: 'spooky-container' },
birthday: { css: 'birthday.css', js: 'birthday.js', container: 'birthday-container' } sports: { css: 'sports.css', js: 'sports.js', container: 'sports-container' },
spring: { css: 'spring.css', js: 'spring.js', container: 'spring-container' },
starwars: { css: 'starwars.css', js: 'starwars.js', container: 'starwars-container' },
storm: { css: 'storm.css', js: 'storm.js', container: 'storm-container' },
summer: { css: 'summer.css', js: 'summer.js', container: 'summer-container' },
underwater: { css: 'underwater.css', js: 'underwater.js', container: 'underwater-container' }
}; };
const select = document.getElementById('theme-select'); const select = document.getElementById('theme-select');

View File

@@ -35,6 +35,7 @@
} }
.underwater-seaweed { .underwater-seaweed {
will-change: transform, opacity;
position: absolute; position: absolute;
bottom: -1vh; bottom: -1vh;
font-size: 4rem; font-size: 4rem;
@@ -52,6 +53,7 @@
} }
.underwater-bubble { .underwater-bubble {
will-change: transform;
position: absolute; position: absolute;
bottom: -5vh; bottom: -5vh;
border-radius: 50%; border-radius: 50%;
@@ -73,13 +75,13 @@
} }
@keyframes underwater-traverse-up { @keyframes underwater-traverse-up {
0% { top: 120vh; } 0% { top: 0; translate: 0 120vh; }
100% { top: -20vh; } 100% { top: 0; translate: 0 -20vh; }
} }
@keyframes underwater-traverse-down { @keyframes underwater-traverse-down {
0% { top: -20vh; } 0% { top: 0; translate: 0 -20vh; }
100% { top: 120vh; } 100% { top: 0; translate: 0 120vh; }
} }
@keyframes underwater-sway-y { @keyframes underwater-sway-y {
@@ -101,8 +103,9 @@
} }
.underwater-god-rays { .underwater-god-rays {
will-change: transform;
position: absolute; position: absolute;
top: -50vh; top: 0;
left: -50vw; left: -50vw;
width: 200vw; width: 200vw;
height: 200vh; height: 200vh;
@@ -119,6 +122,7 @@
transform-origin: top center; transform-origin: top center;
mix-blend-mode: overlay; mix-blend-mode: overlay;
filter: blur(5px); filter: blur(5px);
translate: 0 -50vh;
} }
@keyframes god-rays-sway { @keyframes god-rays-sway {

View File

@@ -1,43 +1,63 @@
const config = window.SeasonalsPluginConfig?.Underwater || {}; const config = window.SeasonalsPluginConfig?.Underwater || {};
const underwater = config.EnableUnderwater !== undefined ? config.EnableUnderwater : true; const underwater = config.EnableUnderwater !== undefined ? config.EnableUnderwater : true; // enable/disable underwater
const symbolCount = config.SymbolCount || 15; const symbolCountMobile = config.SymbolCountMobile !== undefined ? config.SymbolCountMobile : 2; // Devisor to reduce number of objects on mobile
const useRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true; const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different durations
const enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false; const enableLightRays = config.EnableLightRays !== undefined ? config.EnableLightRays : true; // enable/disable lightrays
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; const seaweedCount = config.SeaweedCount !== undefined ? config.SeaweedCount : 50; // count of seaweed
const enableLightRays = config.EnableLightRays !== undefined ? config.EnableLightRays : true;
const seaweedCount = config.SeaweedCount !== undefined ? config.SeaweedCount : 50;
// Entity counts configured // Entity counts configured
const fishCount = config.FishCount !== undefined ? config.FishCount : 15; const fishCount = config.FishCount !== undefined ? config.FishCount : 15; // count of fish
const seahorseCount = config.SeahorseCount !== undefined ? config.SeahorseCount : 3; const seahorseCount = config.SeahorseCount !== undefined ? config.SeahorseCount : 3; // count of seahorse
const jellyfishCount = config.JellyfishCount !== undefined ? config.JellyfishCount : 3; const jellyfishCount = config.JellyfishCount !== undefined ? config.JellyfishCount : 3; // count of jellyfish
const turtleCount = config.TurtleCount !== undefined ? config.TurtleCount : 1; const turtleCount = config.TurtleCount !== undefined ? config.TurtleCount : 1; // count of turtle
const crabCount = config.CrabCount !== undefined ? config.CrabCount : 2; const crabCount = config.CrabCount !== undefined ? config.CrabCount : 2; // count of crab
const starfishCount = config.StarfishCount !== undefined ? config.StarfishCount : 2; const starfishCount = config.StarfishCount !== undefined ? config.StarfishCount : 2; // count of starfish
const shellCount = config.ShellCount !== undefined ? config.ShellCount : 2; const shellCount = config.ShellCount !== undefined ? config.ShellCount : 2; // count of shell
// credits: https://lottiefiles.com/free-animation/seaweed-E6Go0HdkqY
const seaweeds = [ const seaweeds = [
"../Seasonals/Resources/underwater_assets/seaweed_1.gif", "../Seasonals/Resources/underwater_assets/seaweed_1.gif",
"../Seasonals/Resources/underwater_assets/seaweed_2.gif" "../Seasonals/Resources/underwater_assets/seaweed_2.gif"
]; ];
// Statics for bottom /**
* Credits:
* https://www.animierte-gifs.net/img-animiertes-krebs-bild-0041-59970.htm
* https://www.animierte-gifs.net/img-animiertes-krebs-bild-0002-59931.htm
*
*/
const crabImages = [ const crabImages = [
"../Seasonals/Resources/underwater_assets/crab_1.gif", "../Seasonals/Resources/underwater_assets/crab_1.gif",
"../Seasonals/Resources/underwater_assets/crab_2.gif", "../Seasonals/Resources/underwater_assets/crab_2.gif",
"../Seasonals/Resources/underwater_assets/crab_3.gif" "../Seasonals/Resources/underwater_assets/crab_3.gif"
]; ];
/**
* Credits:
* https://lottiefiles.com/free-animation/dancing-starfish-tJ9ZFu9Zq0
* https://www.animierte-gifs.net/img-animiertes-fische-bild-0003-50003.htm
*/
const starfishImages = [ const starfishImages = [
"../Seasonals/Resources/underwater_assets/starfish_1.gif", "../Seasonals/Resources/underwater_assets/starfish_1.gif",
"../Seasonals/Resources/underwater_assets/starfish_2.gif" "../Seasonals/Resources/underwater_assets/starfish_2.gif"
]; ];
// Credit: https://www.animierte-gifs.net/img-animiertes-muschel-bild-0021-108539.htm
const shellImages = [ const shellImages = [
"../Seasonals/Resources/underwater_assets/shell_1.gif" "../Seasonals/Resources/underwater_assets/shell_1.gif"
]; ];
/**
* Credits:
* https://lottiefiles.com/free-animation/0101uwt-tt-01-4fA4Lm9gtN
* https://www.animierte-gifs.net/img-animiertes-fische-bild-0473-50473.htm
* https://www.animierte-gifs.net/img-animiertes-fische-bild-0290-50290.htm
* https://www.animierte-gifs.net/img-animiertes-fische-bild-0049-50049.htm
* https://www.animierte-gifs.net/img-animiertes-fische-bild-0403-50403.htm
*
* https://flaticon.com
*/
const fishImages = [ const fishImages = [
"../Seasonals/Resources/underwater_assets/fish_1.gif", "../Seasonals/Resources/underwater_assets/fish_1.gif",
"../Seasonals/Resources/underwater_assets/fish_2.gif", "../Seasonals/Resources/underwater_assets/fish_2.gif",
@@ -55,15 +75,22 @@ const fishImages = [
"../Seasonals/Resources/underwater_assets/fish_15.png" "../Seasonals/Resources/underwater_assets/fish_15.png"
]; ];
/**
* Credits:
* https://www.animierte-gifs.net/img-animiertes-fische-bild-0221-50221.htm
* https://www.animierte-gifs.net/img-animiertes-fische-bild-0217-50217.htm
*/
const seahorsesImages = [ const seahorsesImages = [
"../Seasonals/Resources/underwater_assets/seahorse_1.gif", "../Seasonals/Resources/underwater_assets/seahorse_1.gif",
"../Seasonals/Resources/underwater_assets/seahorse_2.gif" "../Seasonals/Resources/underwater_assets/seahorse_2.gif"
]; ];
// credit: https://lottiefiles.com/free-animation/sea-turtle-s0sbHIWS2F
const turtleImages = [ const turtleImages = [
"../Seasonals/Resources/underwater_assets/turtle.gif" "../Seasonals/Resources/underwater_assets/turtle.gif"
]; ];
// credits: https://lottiefiles.com/free-animation/jellyfish-wPwyF8EeSQ
const jellyfishImages = [ const jellyfishImages = [
"../Seasonals/Resources/underwater_assets/jellyfish_1.gif", "../Seasonals/Resources/underwater_assets/jellyfish_1.gif",
"../Seasonals/Resources/underwater_assets/jellyfish_2.gif" "../Seasonals/Resources/underwater_assets/jellyfish_2.gif"
@@ -137,16 +164,15 @@ function createUnderwater() {
const useRandomDuration = enableDifferentDuration !== false; const useRandomDuration = enableDifferentDuration !== false;
let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches; let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
// Seaweed swaying at the bottom (evenly distributed based on count) // Seaweed swaying at the bottom (evenly distributed based on count)
const activeSeaweedCount = Math.max(1, seaweedCount); const activeSeaweedCount = isMobile ? Math.max(1, Math.floor(seaweedCount / Math.max(1, symbolCountMobile))) : Math.max(1, seaweedCount);
const seaweedSpacing = 95 / activeSeaweedCount; const seaweedSpacing = 95 / activeSeaweedCount;
for (let i = 0; i < seaweedCount; i++) { for (let i = 0; i < activeSeaweedCount; i++) {
let seaweed = document.createElement('div'); let seaweed = document.createElement('div');
seaweed.className = 'underwater-seaweed'; seaweed.className = 'underwater-seaweed';
seaweed.style.position = 'absolute'; seaweed.style.position = 'absolute';
// MARK: Distance from the bottom edge for the seaweed // Distance from the bottom edge for the seaweed
seaweed.style.bottom = '-18px'; seaweed.style.bottom = '-18px';
let offset = (Math.random() * seaweedSpacing) - (seaweedSpacing / 2); let offset = (Math.random() * seaweedSpacing) - (seaweedSpacing / 2);
@@ -163,7 +189,7 @@ function createUnderwater() {
seaweed.style.transform = `scale(${scale}) ${flip}`; seaweed.style.transform = `scale(${scale}) ${flip}`;
seaweed.style.zIndex = depth < 0.5 ? '15' : '30'; seaweed.style.zIndex = depth < 0.5 ? '15' : '30';
// Mix Emojis and GIFs // Mix Emojis and GIFs for seaweed
if (Math.random() > 0.4) { if (Math.random() > 0.4) {
let img = document.createElement('img'); let img = document.createElement('img');
img.src = seaweeds[Math.floor(Math.random() * seaweeds.length)]; img.src = seaweeds[Math.floor(Math.random() * seaweeds.length)];
@@ -182,9 +208,8 @@ function createUnderwater() {
// Static Bottom Creatures logic // Static Bottom Creatures logic
function spawnStatic(imageArray, maxCount, baseSize) { function spawnStatic(imageArray, maxCount, baseSize) {
// Evaluate an actual count between 1 and maxCount if random symbols are enabled let spawnLimit = isMobile ? Math.floor(maxCount / Math.max(1, symbolCountMobile)) : maxCount;
const actualCount = (useRandomSymbols && maxCount > 0) ? Math.floor(Math.random() * maxCount) + 1 : maxCount; for (let i = 0; i < spawnLimit; i++) {
for (let i = 0; i < actualCount; i++) {
let creature = document.createElement('div'); let creature = document.createElement('div');
creature.className = 'underwater-static-bottom'; creature.className = 'underwater-static-bottom';
creature.style.position = 'absolute'; creature.style.position = 'absolute';
@@ -216,13 +241,9 @@ function createUnderwater() {
// Swimmers logic // Swimmers logic
function spawnSwimmerLoop(imageArray, maxCount, baseSize, typeName) { function spawnSwimmerLoop(imageArray, maxCount, baseSize, typeName) {
if (maxCount <= 0) return; if (maxCount <= 0) return;
let spawnLimit = isMobile ? (enableRandomMobile ? maxCount : Math.floor(maxCount / 2)) : maxCount; let spawnLimit = isMobile ? Math.floor(maxCount / Math.max(1, symbolCountMobile)) : maxCount;
// Randomize the actual amount spawned up to the limit for (let i = 0; i < spawnLimit; i++) {
const actualCount = (useRandomSymbols && spawnLimit > 0) ? Math.floor(Math.random() * spawnLimit) + 1 : spawnLimit;
for (let i = 0; i < actualCount; i++) {
// Spawn immediately but use negative delay to distribute them across the screen!
spawnSingleSwimmer(imageArray, baseSize, typeName); spawnSingleSwimmer(imageArray, baseSize, typeName);
} }
} }

View File

@@ -9,12 +9,12 @@
"imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/raw/branch/main/logo.png", "imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/raw/branch/main/logo.png",
"versions": [ "versions": [
{ {
"version": "2.0.0.0", "version": "2.0.0.3",
"changelog": "- feat: add many themes\n- fix: improve performance", "changelog": "- feat: add many themes\n- fix: improve performance",
"targetAbi": "10.11.0.0", "targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v2.0.0.0/Jellyfin.Plugin.Seasonals.zip", "sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v2.0.0.3/Jellyfin.Plugin.Seasonals.zip",
"checksum": "11a55b3af5536ad6b3d281746e61f8e6", "checksum": "ef29196932c544d88119421dddb58afb",
"timestamp": "2026-02-27T00:20:15Z" "timestamp": "2026-02-28T01:30:54Z"
}, },
{ {
"version": "1.7.2.0", "version": "1.7.2.0",