Compare commits

...

24 Commits

Author SHA1 Message Date
CodeDevMLH
5c10583601 Update manifest.json for release v1.7.2.0 [skip ci] 2026-02-23 00:34:14 +00:00
CodeDevMLH
20dcf08bda Bump version to 1.7.2.0
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 53s
2026-02-23 01:33:23 +01:00
CodeDevMLH
e4b3a132b1 Add seasonal effects for Pi Day, Pride, Rain, and Storm; enhance existing styles
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 45s
- Introduced new CSS and JS files for Pi Day, Pride, Rain, and Storm effects.
- Updated existing seasonal styles (e.g., Halloween, Hearts, Resurrection) to improve performance with 'contain: layout paint'.
- Enhanced animations for seasonal effects, including adjustments to keyframes and element creation logic.
- Added configuration options for new effects in the main seasonals.js file.
- Updated test-site.html to include new seasonal options in the dropdown.
2026-02-23 01:31:52 +01:00
CodeDevMLH
63ec6d5e52 Update disabled options descriptions in seasonal configuration [skip ci] 2026-02-21 16:06:24 +01:00
CodeDevMLH
ec89f2d48d Update manifest.json for release v1.7.1.5 [skip ci] 2026-02-21 14:28:31 +00:00
CodeDevMLH
61b21de566 Bump version to 1.7.1.5
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 56s
2026-02-21 15:27:36 +01:00
CodeDevMLH
590f2c3606 Add Cherry Blossom option and update Resurrection description in seasonal options 2026-02-21 15:27:24 +01:00
CodeDevMLH
fdadc00a0c Update manifest.json for release v1.7.1.4 [skip ci] 2026-02-21 14:24:25 +00:00
CodeDevMLH
2ab88fd5ac Bump version to 1.7.1.4
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 53s
2026-02-21 15:23:35 +01:00
CodeDevMLH
9a41c0a2ce Adjust sunbeam and butterfly positioning for improved visuals 2026-02-21 15:23:29 +01:00
CodeDevMLH
816f58cf02 Update SpringOptions configuration and HTML for seasonal options
Some checks failed
Auto Release Plugin / build-and-release (push) Has been cancelled
2026-02-21 15:11:29 +01:00
CodeDevMLH
5be9a60eed Update manifest.json for release v1.7.1.3 [skip ci] 2026-02-21 13:50:20 +00:00
CodeDevMLH
133808105e Bump version to 1.7.1.3
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 56s
2026-02-21 14:49:26 +01:00
CodeDevMLH
c631aca44f Add Cherry Blossom seasonal options to configuration 2026-02-21 14:49:08 +01:00
CodeDevMLH
241450d132 Increase default counts for pollen and butterflies in SpringOptions configuration 2026-02-21 14:44:09 +01:00
CodeDevMLH
d50d71bde1 Update manifest.json for release v1.7.1.2 [skip ci] 2026-02-21 03:33:25 +00:00
CodeDevMLH
262dd98519 Bump version to 1.7.1.2
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 1m42s
2026-02-21 04:31:42 +01:00
CodeDevMLH
b45ec73a67 Enhance spring configuration options by adding counts for birds, butterflies, bees, and ladybugs; update UI labels and descriptions for clarity. 2026-02-21 04:31:22 +01:00
CodeDevMLH
4e8a37540f Refactor spring and carnival animations, enhance configuration options, and improve asset management 2026-02-21 04:31:15 +01:00
CodeDevMLH
cde5201991 Add Cherry Blossom option to theme selector and configuration 2026-02-21 04:31:04 +01:00
CodeDevMLH
b2420b8eb4 Add Rotkehlchen GIF asset for spring animations 2026-02-21 04:30:54 +01:00
CodeDevMLH
dacec7d03c Add new spring-themed GIF assets for animations 2026-02-21 04:30:47 +01:00
CodeDevMLH
65f8261fb7 Add Cherry Blossom feature with configuration options and animations [skip ci] 2026-02-20 01:09:23 +01:00
CodeDevMLH
78872e7f96 Remove petal and ladybug functionality from spring animation 2026-02-20 00:55:54 +01:00
45 changed files with 2175 additions and 336 deletions

View File

@@ -32,6 +32,12 @@ public class PluginConfiguration : BasePluginConfiguration
Summer = new SummerOptions(); Summer = new SummerOptions();
CherryBlossom = new CherryBlossomOptions(); CherryBlossom = new CherryBlossomOptions();
Carnival = new CarnivalOptions(); Carnival = new CarnivalOptions();
PiDay = new PiDayOptions();
Eurovision = new EurovisionOptions();
Storm = new StormOptions();
Pride = new PrideOptions();
EarthDay = new EarthDayOptions();
Rain = new RainOptions();
} }
/// <summary> /// <summary>
@@ -57,7 +63,7 @@ public class PluginConfiguration : BasePluginConfiguration
/// <summary> /// <summary>
/// Gets or sets the seasonal rules configuration as JSON. /// Gets or sets the seasonal rules configuration as JSON.
/// </summary> /// </summary>
public string SeasonalRules { get; set; } = "[{\"Name\":\"New Year Fireworks\",\"StartDay\":28,\"StartMonth\":12,\"EndDay\":5,\"EndMonth\":1,\"Theme\":\"fireworks\"},{\"Name\":\"Carnival\",\"StartDay\":19,\"StartMonth\":2,\"EndDay\":28,\"EndMonth\":2,\"Theme\":\"carnival\"},{\"Name\":\"Valentine's Day\",\"StartDay\":10,\"StartMonth\":2,\"EndDay\":18,\"EndMonth\":2,\"Theme\":\"hearts\"},{\"Name\":\"Spring\",\"StartDay\":1,\"StartMonth\":3,\"EndDay\":31,\"EndMonth\":5,\"Theme\":\"spring\"},{\"Name\":\"Summer\",\"StartDay\":1,\"StartMonth\":6,\"EndDay\":31,\"EndMonth\":8,\"Theme\":\"summer\"},{\"Name\":\"Santa\",\"StartDay\":22,\"StartMonth\":12,\"EndDay\":27,\"EndMonth\":12,\"Theme\":\"santa\"},{\"Name\":\"Snowflakes (December)\",\"StartDay\":1,\"StartMonth\":12,\"EndDay\":31,\"EndMonth\":12,\"Theme\":\"snowflakes\"},{\"Name\":\"Snowfall (January)\",\"StartDay\":1,\"StartMonth\":1,\"EndDay\":31,\"EndMonth\":1,\"Theme\":\"snowfall\"},{\"Name\":\"Snowfall (February)\",\"StartDay\":1,\"StartMonth\":2,\"EndDay\":29,\"EndMonth\":2,\"Theme\":\"snowfall\"},{\"Name\":\"Easter\",\"StartDay\":25,\"StartMonth\":3,\"EndDay\":25,\"EndMonth\":4,\"Theme\":\"easter\"},{\"Name\":\"Halloween\",\"StartDay\":24,\"StartMonth\":10,\"EndDay\":5,\"EndMonth\":11,\"Theme\":\"halloween\"},{\"Name\":\"Autumn\",\"StartDay\":1,\"StartMonth\":9,\"EndDay\":30,\"EndMonth\":11,\"Theme\":\"autumn\"}]"; public string SeasonalRules { get; set; } = "[{\"Name\":\"New Year Fireworks\",\"StartDay\":28,\"StartMonth\":12,\"EndDay\":5,\"EndMonth\":1,\"Theme\":\"fireworks\"},{\"Name\":\"Carnival\",\"StartDay\":19,\"StartMonth\":2,\"EndDay\":28,\"EndMonth\":2,\"Theme\":\"carnival\"},{\"Name\":\"Valentine's Day\",\"StartDay\":10,\"StartMonth\":2,\"EndDay\":18,\"EndMonth\":2,\"Theme\":\"hearts\"},{\"Name\":\"Spring\",\"StartDay\":1,\"StartMonth\":3,\"EndDay\":31,\"EndMonth\":5,\"Theme\":\"spring\"},{\"Name\":\"Summer\",\"StartDay\":1,\"StartMonth\":6,\"EndDay\":31,\"EndMonth\":8,\"Theme\":\"summer\"},{\"Name\":\"Santa\",\"StartDay\":22,\"StartMonth\":12,\"EndDay\":27,\"EndMonth\":12,\"Theme\":\"santa\"},{\"Name\":\"Snowflakes (December)\",\"StartDay\":1,\"StartMonth\":12,\"EndDay\":31,\"EndMonth\":12,\"Theme\":\"snowflakes\"},{\"Name\":\"Snowfall (January)\",\"StartDay\":1,\"StartMonth\":1,\"EndDay\":31,\"EndMonth\":1,\"Theme\":\"snowfall\"},{\"Name\":\"Snowfall (February)\",\"StartDay\":1,\"StartMonth\":2,\"EndDay\":29,\"EndMonth\":2,\"Theme\":\"snowfall\"},{\"Name\":\"Easter\",\"StartDay\":25,\"StartMonth\":3,\"EndDay\":25,\"EndMonth\":4,\"Theme\":\"easter\"},{\"Name\":\"Halloween\",\"StartDay\":24,\"StartMonth\":10,\"EndDay\":5,\"EndMonth\":11,\"Theme\":\"halloween\"},{\"Name\":\"Autumn\",\"StartDay\":1,\"StartMonth\":9,\"EndDay\":30,\"EndMonth\":11,\"Theme\":\"autumn\"},{\"Name\":\"Cherry Blossom\",\"StartDay\":1,\"StartMonth\":4,\"EndDay\":30,\"EndMonth\":4,\"Theme\":\"cherryblossom\"}]";
/// <summary> /// <summary>
/// Gets or sets the Seasonals options. /// Gets or sets the Seasonals options.
@@ -77,6 +83,12 @@ public class PluginConfiguration : BasePluginConfiguration
public SummerOptions Summer { get; set; } public SummerOptions Summer { get; set; }
public CherryBlossomOptions CherryBlossom { get; set; } public CherryBlossomOptions CherryBlossom { get; set; }
public CarnivalOptions Carnival { get; set; } public CarnivalOptions Carnival { get; set; }
public PiDayOptions PiDay { 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 class AutumnOptions public class AutumnOptions
@@ -193,10 +205,14 @@ public class ResurrectionOptions
public class SpringOptions public class SpringOptions
{ {
public int PollenCount { get; set; } = 15; public int PollenCount { get; set; } = 30;
public int LadybugCount { get; set; } = 5;
public int SunbeamCount { get; set; } = 5; public int SunbeamCount { get; set; } = 5;
public int BirdCount { get; set; } = 4;
public int ButterflyCount { get; set; } = 4;
public int BeeCount { get; set; } = 2;
public int LadybugCount { get; set; } = 2;
public bool EnableSpring { get; set; } = true; public bool EnableSpring { get; set; } = true;
public bool EnableSpringSunbeams { get; set; } = true;
public bool EnableRandomSpring { get; set; } = true; public bool EnableRandomSpring { get; set; } = true;
public bool EnableRandomSpringMobile { get; set; } = false; public bool EnableRandomSpringMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true; public bool EnableDifferentDuration { get; set; } = true;
@@ -219,6 +235,7 @@ public class CarnivalOptions
public bool EnableRandomCarnival { get; set; } = true; public bool EnableRandomCarnival { get; set; } = true;
public bool EnableRandomCarnivalMobile { get; set; } = false; public bool EnableRandomCarnivalMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true; public bool EnableDifferentDuration { get; set; } = true;
public bool EnableCarnivalSway { get; set; } = true;
} }
public class CherryBlossomOptions public class CherryBlossomOptions
@@ -229,3 +246,55 @@ public class CherryBlossomOptions
public bool EnableRandomCherryBlossomMobile { get; set; } = false; public bool EnableRandomCherryBlossomMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true; public bool EnableDifferentDuration { get; set; } = true;
} }
public class PiDayOptions
{
public int SymbolCount { get; set; } = 50;
public bool EnablePiDay { get; set; } = true;
public bool EnableRandomPiDay { get; set; } = true;
public bool EnableRandomPiDayMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true;
}
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;
}

View File

@@ -65,14 +65,22 @@
<option value="santa">Santa (flying santa & snowfall)</option> <option value="santa">Santa (flying santa & snowfall)</option>
<option value="autumn">Autumn (falling leaves)</option> <option value="autumn">Autumn (falling leaves)</option>
<option value="easter">Easter</option> <option value="easter">Easter</option>
<option value="resurrection">Resurrection</option>
<option value="summer">Summer (Bubbles)</option> <option value="summer">Summer (Bubbles)</option>
<option value="spring">Spring</option> <option value="spring">Spring</option>
<option value="carnival">Carnival (Confetti)</option> <option value="carnival">Carnival (Confetti)</option>
<option value="championships" disabled>European/World Championships (not implemented yet. Please commit ideas in a issue or PR)</option> <option value="cherryblossom">Cherry Blossom</option>
<option value="patrick" disabled>St. Patrick's Day (not implemented yet. Please commit ideas in a issue or PR)</option> <option value="resurrection">Resurrection by Bioflash257</option>
<option value="thanksgiving" disabled>Thanksgiving (not implemented yet. Please commit ideas in a issue or PR)</option> <option value="championships" disabled>European/World Championships (not implemented yet. Please commit ideas/implementation in a issue or PR)</option>
<option value="pride" disabled>Pride (not implemented yet. Please commit ideas in a issue or PR)</option> <option value="patrick" disabled>St. Patrick's Day (not implemented yet. Please commit ideas/implementation in a issue or PR)</option>
<option value="thanksgiving" disabled>Thanksgiving (not implemented yet. Please commit ideas/implementation in a issue or PR)</option>
<option value="earthday">Earth Day (Growing Vines)</option>
<option value="eurovision">Eurovision (Dancing Notes)</option>
<option value="oscar" disabled>Oscar Awards (not implemented yet. Please commit ideas/implementation in a issue or PR)</option>
<option value="piday">Pi-Day (Matrix Rain)</option>
<option value="pride">Pride (Rainbow Border)</option>
<option value="rain">Rain (Pure Rain)</option>
<option value="storm">Storm (Heavy Rain & Lightning (Epilepsy Warning!))</option>
<option value="sugarfeast" disabled>Sugar Feast (Eid al-Fitr, Ramadan) (not implemented yet. Please commit ideas/implementation in a issue or PR)</option>
</select> </select>
<div class="fieldDescription">The season to display if automation is disabled or no "Auto Selection" rule matches the current date.</div> <div class="fieldDescription">The season to display if automation is disabled or no "Auto Selection" rule matches the current date.</div>
</div> </div>
@@ -582,39 +590,29 @@
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableSpring" name="EnableSpring" type="checkbox" is="emby-checkbox" /> <input id="EnableSpring" name="EnableSpring" type="checkbox" is="emby-checkbox" />
<span>Enable Spring Seasonal</span> <span class="checkboxLabel">Enable Spring</span>
</label> </label>
<div class="fieldDescription">Enable the Spring theme in general (e.g. for automation).</div> <div class="fieldDescription">Enables the Spring theme (grass, pollen).</div>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableRandomSpring" name="EnableRandomSpring" type="checkbox" is="emby-checkbox" /> <input id="EnableRandomSpring" name="EnableRandomSpring" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Sakura Petals</span> <span>Enable Animation Assets</span>
</label> </label>
<div class="fieldDescription">Displays additional Sakura petals falling across the screen.</div> <div class="fieldDescription">Enables animated spring assets (birds, butterflies, bees, etc.).</div>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableRandomSpringMobile" name="EnableRandomSpringMobile" type="checkbox" is="emby-checkbox" /> <input id="EnableRandomSpringMobile" name="EnableRandomSpringMobile" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Sakura Petals on Mobile</span> <span>Enable Animation Assets on Mobile</span>
</label> </label>
<div class="fieldDescription">Displays additional Sakura petals falling across the screen on mobile devices. Warning: High values may affect performance.</div> <div class="fieldDescription">Displays animated assets on mobile devices. Warning: High values may affect performance.</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="SpringPetalCount">Sakura Petal Count</label>
<input is="emby-input" type="number" id="SpringPetalCount" name="SpringPetalCount" />
<div class="fieldDescription">Number of additional Sakura petals (if enabled).</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="SpringPollenCount">Pollen Count</label> <label class="inputLabel" for="SpringPollenCount">Pollen Count</label>
<input is="emby-input" type="number" id="SpringPollenCount" name="SpringPollenCount" /> <input is="emby-input" type="number" id="SpringPollenCount" name="SpringPollenCount" />
<div class="fieldDescription">Number of pollen particles (if enabled).</div> <div class="fieldDescription">Number of pollen particles (if enabled).</div>
</div> </div>
<div class="inputContainer">
<label class="inputLabel" for="SpringLadybugCount">Ladybug Count</label>
<input is="emby-input" type="number" id="SpringLadybugCount" name="SpringLadybugCount" />
<div class="fieldDescription">Number of ladybugs.</div>
</div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="SpringSunbeamCount">Sunbeam Count</label> <label class="inputLabel" for="SpringSunbeamCount">Sunbeam Count</label>
<input is="emby-input" type="number" id="SpringSunbeamCount" name="SpringSunbeamCount" /> <input is="emby-input" type="number" id="SpringSunbeamCount" name="SpringSunbeamCount" />
@@ -622,10 +620,37 @@
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableDifferentDurationSpring" name="EnableDifferentDurationSpring" type="checkbox" is="emby-checkbox" /> <input id="EnableSpringSunbeams" name="EnableSpringSunbeams" type="checkbox" is="emby-checkbox" />
<span>Enable Different Falling Speed</span> <span>Enable Sunbeams</span>
</label> </label>
<div class="fieldDescription">Randomize the falling speed of petals.</div> <div class="fieldDescription">Display sunbeams at the top of the screen.</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="SpringBirdCount">Bird Count</label>
<input is="emby-input" type="number" id="SpringBirdCount" name="SpringBirdCount" />
<div class="fieldDescription">Number of birds flying across the screen.</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="SpringButterflyCount">Butterfly Count</label>
<input is="emby-input" type="number" id="SpringButterflyCount" name="SpringButterflyCount" />
<div class="fieldDescription">Number of butterflies.</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="SpringBeeCount">Bee Count</label>
<input is="emby-input" type="number" id="SpringBeeCount" name="SpringBeeCount" />
<div class="fieldDescription">Number of bees.</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="SpringLadybugCount">Ladybug Count</label>
<input is="emby-input" type="number" id="SpringLadybugCount" name="SpringLadybugCount" />
<div class="fieldDescription">Number of ladybugs walking along the bottom.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableDifferentDurationSpring" name="EnableDifferentDurationSpring" type="checkbox" is="emby-checkbox" />
<span>Enable Different Duration</span>
</label>
<div class="fieldDescription">Randomize the animations duration.</div>
</div> </div>
</details> </details>
<hr style="max-width: 800px; margin: 1em 0;"> <hr style="max-width: 800px; margin: 1em 0;">
@@ -708,6 +733,256 @@
</label> </label>
<div class="fieldDescription">Randomize the falling speed of confetti.</div> <div class="fieldDescription">Randomize the falling speed of confetti.</div>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableCarnivalSway" name="EnableCarnivalSway" type="checkbox" is="emby-checkbox" />
<span>Enable Sway</span>
</label>
<div class="fieldDescription">Enable sway animation for carnival confetti.</div>
</div>
</details>
<hr style="max-width: 800px; margin: 1em 0;">
<details>
<summary>Cherry Blossom</summary>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableCherryBlossom" name="EnableCherryBlossom" type="checkbox" is="emby-checkbox" />
<span>Enable Cherry Blossom Seasonal</span>
</label>
<div class="fieldDescription">Enable the Cherry Blossom theme in general (e.g. for automation).</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomCherryBlossom" name="EnableRandomCherryBlossom" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Cherry Blossoms</span>
</label>
<div class="fieldDescription">Displays additional cherry blossoms falling and fluttering across the screen.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomCherryBlossomMobile" name="EnableRandomCherryBlossomMobile" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Cherry Blossoms on Mobile</span>
</label>
<div class="fieldDescription">Displays additional cherry blossoms falling and fluttering across the screen on mobile devices. Warning: High values may affect performance.</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="CherryBlossomPetalCount">Petal Count</label>
<input is="emby-input" type="number" id="CherryBlossomPetalCount" name="CherryBlossomPetalCount" />
<div class="fieldDescription">Number of additional cherry blossoms (if enabled).</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableDifferentDurationCherryBlossom" name="EnableDifferentDurationCherryBlossom" type="checkbox" is="emby-checkbox" />
<span>Enable Different Falling Speed</span>
</label>
<div class="fieldDescription">Randomize the falling speed of cherry blossoms.</div>
</div>
</details>
<hr style="max-width: 800px; margin: 1em 0;">
<details>
<summary>Earth Day</summary>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableEarthDay" name="EnableEarthDay" type="checkbox" is="emby-checkbox" />
<span>Enable Earth Day Seasonal</span>
</label>
<div class="fieldDescription">Enable the Earth Day theme in general (e.g. for automation).</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="EarthDayVineCount">Vine Count</label>
<input is="emby-input" type="number" id="EarthDayVineCount" name="EarthDayVineCount" />
<div class="fieldDescription">Number of animated vines (if enabled).</div>
</div>
</details>
<hr style="max-width: 800px; margin: 1em 0;">
<details>
<summary>Eurovision / Musik</summary>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableEurovision" name="EnableEurovision" type="checkbox" is="emby-checkbox" />
<span>Enable Eurovision Seasonal</span>
</label>
<div class="fieldDescription">Enable the Eurovision/Music theme in general (e.g. for automation).</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomEurovision" name="EnableRandomEurovision" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Music Notes</span>
</label>
<div class="fieldDescription">Displays dancing music notes.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomEurovisionMobile" name="EnableRandomEurovisionMobile" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Music Notes on Mobile</span>
</label>
<div class="fieldDescription">Displays dancing music notes on mobile devices. Warning: High values may affect performance.</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="EurovisionSymbolCount">Symbol Count</label>
<input is="emby-input" type="number" id="EurovisionSymbolCount" name="EurovisionSymbolCount" />
<div class="fieldDescription">Number of additional dancing music notes (if enabled).</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableDifferentDurationEurovision" name="EnableDifferentDurationEurovision" type="checkbox" is="emby-checkbox" />
<span>Enable Different Falling Speed</span>
</label>
<div class="fieldDescription">Randomize the movement speed of music notes.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableColorfulNotes" name="EnableColorfulNotes" type="checkbox" is="emby-checkbox" />
<span>Colorful Notes Mode</span>
</label>
<div class="fieldDescription">If checked, notes will pick colors from the array below. If unchecked, notes will be white.</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="EurovisionColors">Color Array (Comma-separated)</label>
<input is="emby-input" type="text" id="EurovisionColors" name="EurovisionColors" />
<div class="fieldDescription">Example: #FFB6C1,#87CEFA,#98FB98 (Hex or CSS colors separated by commas).</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="EurovisionGlowSize">Note Glow/Shadow Size (px)</label>
<input is="emby-input" type="number" id="EurovisionGlowSize" name="EurovisionGlowSize" />
<div class="fieldDescription">Set the text-shadow size of the notes. Set this to 0 to remove the shadow/glow completely.</div>
</div>
</details>
<hr style="max-width: 800px; margin: 1em 0;">
<details>
<summary>Pi-Day</summary>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnablePiDay" name="EnablePiDay" type="checkbox" is="emby-checkbox" />
<span>Enable Pi-Day Seasonal</span>
</label>
<div class="fieldDescription">Enable the Pi-Day theme in general (e.g. for automation).</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomPiDay" name="EnableRandomPiDay" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Pi Symbols</span>
</label>
<div class="fieldDescription">Displays additional digital rain elements.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomPiDayMobile" name="EnableRandomPiDayMobile" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Pi Symbols on Mobile</span>
</label>
<div class="fieldDescription">Displays additional digital rain elements on mobile devices. Warning: High values may affect performance.</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="PiDaySymbolCount">Symbol Count</label>
<input is="emby-input" type="number" id="PiDaySymbolCount" name="PiDaySymbolCount" />
<div class="fieldDescription">Number of additional digital rain columns (if enabled).</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableDifferentDurationPiDay" name="EnableDifferentDurationPiDay" type="checkbox" is="emby-checkbox" />
<span>Enable Different Falling Speed</span>
</label>
<div class="fieldDescription">Randomize the digital rain falling speed.</div>
</div>
</details>
<hr style="max-width: 800px; margin: 1em 0;">
<details>
<summary>Pride</summary>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnablePride" name="EnablePride" type="checkbox" is="emby-checkbox" />
<span>Enable Pride Seasonal</span>
</label>
<div class="fieldDescription">Enable the Pride theme in general (e.g. for automation).</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="PrideHeartCount">Heart Count</label>
<input is="emby-input" type="number" id="PrideHeartCount" name="PrideHeartCount" />
<div class="fieldDescription">Number of rising rainbow hearts.</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="PrideHeartSize">Heart Size (rem)</label>
<input is="emby-input" type="number" id="PrideHeartSize" name="PrideHeartSize" step="0.1" />
<div class="fieldDescription">Base size of the Pride hearts (default 2).</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="PrideConfettiCount">Confetti Count</label>
<input is="emby-input" type="number" id="PrideConfettiCount" name="PrideConfettiCount" />
<div class="fieldDescription">Number of falling rainbow confetti pieces.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="PrideColorHeader" name="PrideColorHeader" type="checkbox" is="emby-checkbox" />
<span>Rainbow Header</span>
</label>
<div class="fieldDescription">Color the top navigation bar with a rainbow gradient.</div>
</div>
</details>
<hr style="max-width: 800px; margin: 1em 0;">
<details>
<summary>Rain (Pure)</summary>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRain" name="EnableRain" type="checkbox" is="emby-checkbox" />
<span>Enable Rain Seasonal</span>
</label>
<div class="fieldDescription">Enable the pure Rain theme.</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="RaindropCount">Raindrop Count</label>
<input is="emby-input" type="number" id="RaindropCount" name="RaindropCount" />
<div class="fieldDescription">Total number of raindrops.</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="RaindropCountMobile">Raindrop Count (Mobile)</label>
<input is="emby-input" type="number" id="RaindropCountMobile" name="RaindropCountMobile" />
<div class="fieldDescription">Total number of raindrops on mobile devices.</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="RainSpeed">Rain Speed</label>
<input is="emby-input" type="number" id="RainSpeed" name="RainSpeed" step="0.1" />
<div class="fieldDescription">The speed of the falling rain.</div>
</div>
</details>
<hr style="max-width: 800px; margin: 1em 0;">
<details>
<summary>Storm</summary>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableStorm" name="EnableStorm" type="checkbox" is="emby-checkbox" />
<span>Enable Storm Seasonal</span>
</label>
<div class="fieldDescription">Enable the Storm theme in general (e.g. for automation).</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="StormRaindropCount">Raindrop Count</label>
<input is="emby-input" type="number" id="StormRaindropCount" name="StormRaindropCount" />
<div class="fieldDescription">Total number of raindrops in the storm.</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="StormRaindropCountMobile">Raindrop Count (Mobile)</label>
<input is="emby-input" type="number" id="StormRaindropCountMobile" name="StormRaindropCountMobile" />
<div class="fieldDescription">Total number of raindrops on mobile devices. Warning: High values may affect performance.</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="StormRainSpeed">Rain Speed</label>
<input is="emby-input" type="number" id="StormRainSpeed" name="StormRainSpeed" step="0.1" />
<div class="fieldDescription">The speed of the falling rain.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="StormEnableLightning" name="StormEnableLightning" type="checkbox" is="emby-checkbox" />
<span>Enable Lightning Flashes</span>
</label>
<div class="fieldDescription">Periodically flash the screen white to simulate lightning.</div>
</div>
</details> </details>
</div> </div>
@@ -884,13 +1159,20 @@
' <option value="halloween">Halloween</option>' + ' <option value="halloween">Halloween</option>' +
' <option value="hearts">Hearts</option>' + ' <option value="hearts">Hearts</option>' +
' <option value="christmas">Christmas</option>' + ' <option value="christmas">Christmas</option>' +
' <option value="santa">Santa</option>' + ' <option value="santa">Santa (flying santa & snowfall)</option>' +
' <option value="autumn">Autumn</option>' + ' <option value="autumn">Autumn (falling leaves)</option>' +
' <option value="easter">Easter</option>' + ' <option value="easter">Easter</option>' +
' <option value="resurrection">Resurrection</option>' + ' <option value="summer">Summer (Bubbles)</option>' +
' <option value="spring">Spring</option>' + ' <option value="spring">Spring</option>' +
' <option value="summer">Summer</option>' + ' <option value="carnival">Carnival (Confetti)</option>' +
' <option value="carnival">Carnival</option>' + ' <option value="cherryblossom">Cherry Blossom</option>' +
' <option value="earthday">Earth Day</option>' +
' <option value="eurovision">Eurovision</option>' +
' <option value="piday">Pi-Day</option>' +
' <option value="pride">Pride</option>' +
' <option value="rain">Rain</option>' +
' <option value="storm">Storm (Epilepsy Warning!)</option>' +
' <option value="resurrection">Resurrection by Bioflash257</option>' +
' </select>' + ' </select>' +
' </div>' + ' </div>' +
'</div>'; '</div>';
@@ -1065,10 +1347,13 @@
// Spring // Spring
document.querySelector('#EnableSpring').checked = config.Spring.EnableSpring; document.querySelector('#EnableSpring').checked = config.Spring.EnableSpring;
document.querySelector('#SpringPetalCount').value = config.Spring.PetalCount; document.querySelector('#EnableSpringSunbeams').checked = config.Spring.EnableSpringSunbeams !== undefined ? config.Spring.EnableSpringSunbeams : true;
document.querySelector('#SpringPollenCount').value = config.Spring.PollenCount; document.querySelector('#SpringPollenCount').value = config.Spring.PollenCount;
document.querySelector('#SpringLadybugCount').value = config.Spring.LadybugCount;
document.querySelector('#SpringSunbeamCount').value = config.Spring.SunbeamCount; document.querySelector('#SpringSunbeamCount').value = config.Spring.SunbeamCount;
document.querySelector('#SpringBirdCount').value = config.Spring.BirdCount !== undefined ? config.Spring.BirdCount : 3;
document.querySelector('#SpringButterflyCount').value = config.Spring.ButterflyCount !== undefined ? config.Spring.ButterflyCount : 2;
document.querySelector('#SpringBeeCount').value = config.Spring.BeeCount !== undefined ? config.Spring.BeeCount : 1;
document.querySelector('#SpringLadybugCount').value = config.Spring.LadybugCount !== undefined ? config.Spring.LadybugCount : 1;
document.querySelector('#EnableRandomSpring').checked = config.Spring.EnableRandomSpring; document.querySelector('#EnableRandomSpring').checked = config.Spring.EnableRandomSpring;
document.querySelector('#EnableRandomSpringMobile').checked = config.Spring.EnableRandomSpringMobile; document.querySelector('#EnableRandomSpringMobile').checked = config.Spring.EnableRandomSpringMobile;
document.querySelector('#EnableDifferentDurationSpring').checked = config.Spring.EnableDifferentDuration; document.querySelector('#EnableDifferentDurationSpring').checked = config.Spring.EnableDifferentDuration;
@@ -1083,11 +1368,59 @@
// Carnival // Carnival
document.querySelector('#EnableCarnival').checked = config.Carnival.EnableCarnival; document.querySelector('#EnableCarnival').checked = config.Carnival.EnableCarnival;
document.querySelector('#EnableCarnivalSway').checked = config.Carnival.EnableCarnivalSway !== undefined ? config.Carnival.EnableCarnivalSway : true;
document.querySelector('#CarnivalObjectCount').value = config.Carnival.ObjectCount; document.querySelector('#CarnivalObjectCount').value = config.Carnival.ObjectCount;
document.querySelector('#EnableRandomCarnival').checked = config.Carnival.EnableRandomCarnival; document.querySelector('#EnableRandomCarnival').checked = config.Carnival.EnableRandomCarnival;
document.querySelector('#EnableRandomCarnivalMobile').checked = config.Carnival.EnableRandomCarnivalMobile; document.querySelector('#EnableRandomCarnivalMobile').checked = config.Carnival.EnableRandomCarnivalMobile;
document.querySelector('#EnableDifferentDurationCarnival').checked = config.Carnival.EnableDifferentDuration; document.querySelector('#EnableDifferentDurationCarnival').checked = config.Carnival.EnableDifferentDuration;
// Cherry Blossom
document.querySelector('#EnableCherryBlossom').checked = config.CherryBlossom.EnableCherryBlossom;
document.querySelector('#CherryBlossomPetalCount').value = config.CherryBlossom.PetalCount;
document.querySelector('#EnableRandomCherryBlossom').checked = config.CherryBlossom.EnableRandomCherryBlossom;
document.querySelector('#EnableRandomCherryBlossomMobile').checked = config.CherryBlossom.EnableRandomCherryBlossomMobile;
document.querySelector('#EnableDifferentDurationCherryBlossom').checked = config.CherryBlossom.EnableDifferentDuration;
// Earth Day
document.querySelector('#EnableEarthDay').checked = config.EarthDay.EnableEarthDay;
document.querySelector('#EarthDayVineCount').value = config.EarthDay.VineCount;
// Eurovision
document.querySelector('#EnableEurovision').checked = config.Eurovision.EnableEurovision;
document.querySelector('#EurovisionSymbolCount').value = config.Eurovision.SymbolCount;
document.querySelector('#EnableRandomEurovision').checked = config.Eurovision.EnableRandomEurovision;
document.querySelector('#EnableRandomEurovisionMobile').checked = config.Eurovision.EnableRandomEurovisionMobile;
document.querySelector('#EnableDifferentDurationEurovision').checked = config.Eurovision.EnableDifferentDuration;
document.querySelector('#EnableColorfulNotes').checked = config.Eurovision.EnableColorfulNotes;
document.querySelector('#EurovisionColors').value = config.Eurovision.EurovisionColors;
document.querySelector('#EurovisionGlowSize').value = config.Eurovision.EurovisionGlowSize;
// Pi-Day
document.querySelector('#EnablePiDay').checked = config.PiDay.EnablePiDay;
document.querySelector('#PiDaySymbolCount').value = config.PiDay.SymbolCount;
document.querySelector('#EnableRandomPiDay').checked = config.PiDay.EnableRandomPiDay;
document.querySelector('#EnableRandomPiDayMobile').checked = config.PiDay.EnableRandomPiDayMobile;
document.querySelector('#EnableDifferentDurationPiDay').checked = config.PiDay.EnableDifferentDuration;
// Pride
document.querySelector('#EnablePride').checked = config.Pride.EnablePride;
document.querySelector('#PrideHeartCount').value = config.Pride.HeartCount;
document.querySelector('#PrideHeartSize').value = config.Pride.HeartSize;
document.querySelector('#PrideColorHeader').checked = config.Pride.ColorHeader;
// Rain
document.querySelector('#EnableRain').checked = config.Rain.EnableRain;
document.querySelector('#RaindropCount').value = config.Rain.RaindropCount;
document.querySelector('#RaindropCountMobile').value = config.Rain.RaindropCountMobile;
document.querySelector('#RainSpeed').value = config.Rain.RainSpeed;
// Storm
document.querySelector('#EnableStorm').checked = config.Storm.EnableStorm;
document.querySelector('#StormRaindropCount').value = config.Storm.RaindropCount;
document.querySelector('#StormRaindropCountMobile').value = config.Storm.RaindropCountMobile;
document.querySelector('#StormRainSpeed').value = config.Storm.RainSpeed;
document.querySelector('#StormEnableLightning').checked = config.Storm.EnableLightning;
Dashboard.hideLoadingMsg(); Dashboard.hideLoadingMsg();
}); });
}); });
@@ -1197,10 +1530,13 @@
// Spring // Spring
config.Spring.EnableSpring = document.querySelector('#EnableSpring').checked; config.Spring.EnableSpring = document.querySelector('#EnableSpring').checked;
config.Spring.PetalCount = parseInt(document.querySelector('#SpringPetalCount').value); config.Spring.EnableSpringSunbeams = document.querySelector('#EnableSpringSunbeams').checked;
config.Spring.PollenCount = parseInt(document.querySelector('#SpringPollenCount').value); config.Spring.PollenCount = parseInt(document.querySelector('#SpringPollenCount').value);
config.Spring.LadybugCount = parseInt(document.querySelector('#SpringLadybugCount').value);
config.Spring.SunbeamCount = parseInt(document.querySelector('#SpringSunbeamCount').value); config.Spring.SunbeamCount = parseInt(document.querySelector('#SpringSunbeamCount').value);
config.Spring.BirdCount = parseInt(document.querySelector('#SpringBirdCount').value);
config.Spring.ButterflyCount = parseInt(document.querySelector('#SpringButterflyCount').value);
config.Spring.BeeCount = parseInt(document.querySelector('#SpringBeeCount').value);
config.Spring.LadybugCount = parseInt(document.querySelector('#SpringLadybugCount').value);
config.Spring.EnableRandomSpring = document.querySelector('#EnableRandomSpring').checked; config.Spring.EnableRandomSpring = document.querySelector('#EnableRandomSpring').checked;
config.Spring.EnableRandomSpringMobile = document.querySelector('#EnableRandomSpringMobile').checked; config.Spring.EnableRandomSpringMobile = document.querySelector('#EnableRandomSpringMobile').checked;
config.Spring.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationSpring').checked; config.Spring.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationSpring').checked;
@@ -1215,11 +1551,59 @@
// Carnival // Carnival
config.Carnival.EnableCarnival = document.querySelector('#EnableCarnival').checked; config.Carnival.EnableCarnival = document.querySelector('#EnableCarnival').checked;
config.Carnival.EnableCarnivalSway = document.querySelector('#EnableCarnivalSway').checked;
config.Carnival.ObjectCount = parseInt(document.querySelector('#CarnivalObjectCount').value); config.Carnival.ObjectCount = parseInt(document.querySelector('#CarnivalObjectCount').value);
config.Carnival.EnableRandomCarnival = document.querySelector('#EnableRandomCarnival').checked; config.Carnival.EnableRandomCarnival = document.querySelector('#EnableRandomCarnival').checked;
config.Carnival.EnableRandomCarnivalMobile = document.querySelector('#EnableRandomCarnivalMobile').checked; config.Carnival.EnableRandomCarnivalMobile = document.querySelector('#EnableRandomCarnivalMobile').checked;
config.Carnival.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationCarnival').checked; config.Carnival.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationCarnival').checked;
// Cherry Blossom
config.CherryBlossom.EnableCherryBlossom = document.querySelector('#EnableCherryBlossom').checked;
config.CherryBlossom.PetalCount = parseInt(document.querySelector('#CherryBlossomPetalCount').value);
config.CherryBlossom.EnableRandomCherryBlossom = document.querySelector('#EnableRandomCherryBlossom').checked;
config.CherryBlossom.EnableRandomCherryBlossomMobile = document.querySelector('#EnableRandomCherryBlossomMobile').checked;
config.CherryBlossom.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationCherryBlossom').checked;
// Earth Day
config.EarthDay.EnableEarthDay = document.querySelector('#EnableEarthDay').checked;
config.EarthDay.VineCount = parseInt(document.querySelector('#EarthDayVineCount').value);
// Eurovision
config.Eurovision.EnableEurovision = document.querySelector('#EnableEurovision').checked;
config.Eurovision.SymbolCount = parseInt(document.querySelector('#EurovisionSymbolCount').value);
config.Eurovision.EnableRandomEurovision = document.querySelector('#EnableRandomEurovision').checked;
config.Eurovision.EnableRandomEurovisionMobile = document.querySelector('#EnableRandomEurovisionMobile').checked;
config.Eurovision.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationEurovision').checked;
config.Eurovision.EnableColorfulNotes = document.querySelector('#EnableColorfulNotes').checked;
config.Eurovision.EurovisionColors = document.querySelector('#EurovisionColors').value;
config.Eurovision.EurovisionGlowSize = parseInt(document.querySelector('#EurovisionGlowSize').value);
// Pi-Day
config.PiDay.EnablePiDay = document.querySelector('#EnablePiDay').checked;
config.PiDay.SymbolCount = parseInt(document.querySelector('#PiDaySymbolCount').value);
config.PiDay.EnableRandomPiDay = document.querySelector('#EnableRandomPiDay').checked;
config.PiDay.EnableRandomPiDayMobile = document.querySelector('#EnableRandomPiDayMobile').checked;
config.PiDay.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationPiDay').checked;
// Pride
config.Pride.EnablePride = document.querySelector('#EnablePride').checked;
config.Pride.HeartCount = parseInt(document.querySelector('#PrideHeartCount').value);
config.Pride.HeartSize = parseFloat(document.querySelector('#PrideHeartSize').value);
config.Pride.ColorHeader = document.querySelector('#PrideColorHeader').checked;
// Rain
config.Rain.EnableRain = document.querySelector('#EnableRain').checked;
config.Rain.RaindropCount = parseInt(document.querySelector('#RaindropCount').value);
config.Rain.RaindropCountMobile = parseInt(document.querySelector('#RaindropCountMobile').value);
config.Rain.RainSpeed = parseFloat(document.querySelector('#RainSpeed').value);
// Storm
config.Storm.EnableStorm = document.querySelector('#EnableStorm').checked;
config.Storm.RaindropCount = parseInt(document.querySelector('#StormRaindropCount').value);
config.Storm.RaindropCountMobile = parseInt(document.querySelector('#StormRaindropCountMobile').value);
config.Storm.RainSpeed = parseFloat(document.querySelector('#StormRainSpeed').value);
config.Storm.EnableLightning = document.querySelector('#StormEnableLightning').checked;
ApiClient.updatePluginConfiguration(SeasonalsConfigPage.pluginUniqueId, config).then(function (result) { ApiClient.updatePluginConfiguration(SeasonalsConfigPage.pluginUniqueId, config).then(function (result) {
Dashboard.processPluginConfigurationUpdateResult(result); Dashboard.processPluginConfigurationUpdateResult(result);
}); });

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>1.7.1.1</Version> <Version>1.7.2.0</Version>
<RepositoryUrl>https://github.com/CodeDevMLH/Jellyfin-Seasonals</RepositoryUrl> <RepositoryUrl>https://github.com/CodeDevMLH/Jellyfin-Seasonals</RepositoryUrl>
</PropertyGroup> </PropertyGroup>

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;
} }
.leaf { .leaf {
@@ -44,7 +45,7 @@
} }
100% { 100% {
top: 100%; top: 110%;
} }
} }
@@ -54,7 +55,7 @@
} }
100% { 100% {
top: 100%; top: 110%;
} }
} }

View File

@@ -9,16 +9,18 @@
pointer-events: none; pointer-events: none;
z-index: 10; z-index: 10;
perspective: 600px; perspective: 600px;
contain: layout paint;
} }
.carnival-wrapper { .carnival-wrapper {
position: fixed; position: fixed;
z-index: 15; z-index: 15;
top: -20px; top: -20px;
will-change: top; will-change: transform;
animation-name: carnival-fall; animation-name: carnival-fall;
animation-timing-function: linear; animation-timing-function: linear;
animation-iteration-count: infinite; animation-iteration-count: 1;
animation-fill-mode: forwards;
} }
.carnival-sway-wrapper { .carnival-sway-wrapper {
@@ -35,9 +37,8 @@
background-color: #f0f; background-color: #f0f;
will-change: transform; will-change: transform;
animation-name: carnival-flutter; animation-name: carnival-flutter;
animation-timing-function: ease-in-out; animation-timing-function: linear;
animation-iteration-count: infinite; animation-iteration-count: infinite;
animation-duration: 2s;
} }
.carnival-confetti.circle { .carnival-confetti.circle {
@@ -52,24 +53,17 @@
} }
.carnival-confetti.triangle { .carnival-confetti.triangle {
width: 0;
height: 0;
background-color: transparent !important;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-bottom: 10px solid;
width: 10px; width: 10px;
height: 10px; height: 10px;
background-color: inherit;
clip-path: polygon(50% 0%, 0% 100%, 100% 100%); clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
} }
@keyframes carnival-fall { @keyframes carnival-fall {
0% { 0% {
top: -10%; transform: translateY(0);
} }
100% { 100% {
top: 110%; transform: translateY(120vh);
} }
} }
@@ -84,18 +78,9 @@
@keyframes carnival-flutter { @keyframes carnival-flutter {
0% { 0% {
transform: rotate3d(1, 1, 1, 0deg); transform: rotate3d(var(--rx, 1), var(--ry, 1), var(--rz, 0), 0deg);
}
25% {
transform: rotate3d(1, 0.5, 0, 90deg);
}
50% {
transform: rotate3d(0.5, 1, 0.5, 180deg);
}
75% {
transform: rotate3d(0, 0.5, 1, 270deg);
} }
100% { 100% {
transform: rotate3d(1, 1, 1, 360deg); transform: rotate3d(var(--rx, 1), var(--ry, 1), var(--rz, 0), var(--rot-dir, 360deg));
} }
} }

View File

@@ -1,11 +1,11 @@
const config = window.SeasonalsPluginConfig?.Carnival || {}; const config = window.SeasonalsPluginConfig?.Carnival || {};
const carnival = config.EnableCarnival !== undefined ? config.EnableCarnival : true; const carnival = config.EnableCarnival !== undefined ? config.EnableCarnival : true; // Enable/disable carnival
const randomCarnival = config.EnableRandomCarnival !== undefined ? config.EnableRandomCarnival : true; const randomCarnival = config.EnableRandomCarnival !== undefined ? config.EnableRandomCarnival : true; // Enable random carnival objects
const randomCarnivalMobile = config.EnableRandomCarnivalMobile !== undefined ? config.EnableRandomCarnivalMobile : false; const randomCarnivalMobile = config.EnableRandomCarnivalMobile !== undefined ? config.EnableRandomCarnivalMobile : false; // Enable random carnival objects on mobile
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // Randomize falling and flutter speeds
const enableSway = config.EnableCarnivalSway !== undefined ? config.EnableCarnivalSway : true; const enableSway = config.EnableCarnivalSway !== undefined ? config.EnableCarnivalSway : true; // Enable side-to-side sway animation
const carnivalCount = config.ObjectCount || 80; const carnivalCount = config.ObjectCount || 120; // Number of confetti pieces to spawn
let msgPrinted = false; let msgPrinted = false;
@@ -86,14 +86,21 @@ function createConfettiPiece(container, isInitial = false) {
wrapper.style.left = `${Math.random() * 100}%`; wrapper.style.left = `${Math.random() * 100}%`;
// Random dimensions // Random dimensions
// MARK: CONFETTI SIZE (RECTANGLES)
if (!confetti.classList.contains('circle') && !confetti.classList.contains('square') && !confetti.classList.contains('triangle')) { if (!confetti.classList.contains('circle') && !confetti.classList.contains('square') && !confetti.classList.contains('triangle')) {
const width = Math.random() * 5 + 4; const width = Math.random() * 3 + 4; // 4-7px
const height = Math.random() * 6 + 8; const height = Math.random() * 5 + 8; // 8-13px
confetti.style.width = `${width}px`; confetti.style.width = `${width}px`;
confetti.style.height = `${height}px`; confetti.style.height = `${height}px`;
} else if (confetti.classList.contains('circle') || confetti.classList.contains('square')) {
// MARK: CONFETTI SIZE (CIRCLES/SQUARES)
const size = Math.random() * 5 + 5; // 5-10px
confetti.style.width = `${size}px`;
confetti.style.height = `${size}px`;
} }
// Animation settings // Animation settings
// MARK: CONFETTI FALLING SPEED (in seconds)
const duration = Math.random() * 5 + 5; const duration = Math.random() * 5 + 5;
let delay = 0; let delay = 0;
@@ -113,14 +120,22 @@ function createConfettiPiece(container, isInitial = false) {
swayWrapper.style.animationDelay = `-${Math.random() * 5}s`; swayWrapper.style.animationDelay = `-${Math.random() * 5}s`;
// Random sway amplitude (using CSS variable for dynamic keyframe) // Random sway amplitude (using CSS variable for dynamic keyframe)
// Sway between 30px and 100px // MARK: SWAY DISTANCE RANGE (in px)
const swayAmount = Math.random() * 70 + 30; const swayAmount = Math.random() * 70 + 30; // 30-100px
const direction = Math.random() > 0.5 ? 1 : -1; const direction = Math.random() > 0.5 ? 1 : -1;
swayWrapper.style.setProperty('--sway-amount', `${swayAmount * direction}px`); swayWrapper.style.setProperty('--sway-amount', `${swayAmount * direction}px`);
} }
// Flutter speed variation // Flutter speed and random 3D rotation axis
// MARK: CONFETTI FLUTTER ROTATION SPEED
confetti.style.animationDuration = `${Math.random() * 2 + 1}s`; confetti.style.animationDuration = `${Math.random() * 2 + 1}s`;
confetti.style.setProperty('--rx', Math.random().toFixed(2));
confetti.style.setProperty('--ry', Math.random().toFixed(2));
confetti.style.setProperty('--rz', (Math.random() * 0.5).toFixed(2));
// Random direction for 3D rotation
const rotDir = Math.random() > 0.5 ? 1 : -1;
confetti.style.setProperty('--rot-dir', `${rotDir * 360}deg`);
if (enableSway) { if (enableSway) {
swayWrapper.appendChild(confetti); swayWrapper.appendChild(confetti);
@@ -129,6 +144,14 @@ function createConfettiPiece(container, isInitial = false) {
wrapper.appendChild(confetti); wrapper.appendChild(confetti);
} }
// Respawn confetti when it hits the bottom
wrapper.addEventListener('animationend', (e) => {
if (e.animationName === 'carnival-fall') {
wrapper.remove();
createConfettiPiece(container, false); // respawn without initial huge delay
}
});
container.appendChild(wrapper); container.appendChild(wrapper);
} }
@@ -154,7 +177,7 @@ function initCarnivalObjects() {
} }
// Initial confetti // Initial confetti
for (let i = 0; i < 30; i++) { for (let i = 0; i < 60; i++) {
createConfettiPiece(container, true); createConfettiPiece(container, true);
} }
} }

View File

@@ -0,0 +1,59 @@
.cherryblossom-container {
display: block;
position: fixed;
overflow: hidden;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1000;
contain: layout paint;
}
/* Petals */
.cherryblossom-petal {
position: fixed;
top: -20px;
z-index: 1005;
width: 15px;
height: 10px;
background-color: #ffc0cb;
border-radius: 15px 0px 15px 0px;
will-change: transform, top;
animation-name: cherryblossom-fall, cherryblossom-sway;
animation-timing-function: linear, ease-in-out;
animation-iteration-count: infinite, infinite;
animation-duration: 10s, 3s;
}
.cherryblossom-petal.lighter {
background-color: #ffd1dc;
opacity: 0.8;
}
.cherryblossom-petal.darker {
background-color: #ffb7c5;
opacity: 0.9;
}
.cherryblossom-petal.type2 {
width: 12px;
height: 12px;
border-radius: 10px 0px 10px 5px;
}
@keyframes cherryblossom-fall {
0% { top: -10%; }
100% { top: 110%; }
}
@keyframes cherryblossom-sway {
0%, 100% {
transform: translateX(0) rotate(0deg);
}
50% {
transform: translateX(30px) rotate(45deg);
}
}

View File

@@ -0,0 +1,101 @@
const config = window.SeasonalsPluginConfig?.CherryBlossom || {};
const cherryBlossom = config.EnableCherryBlossom !== undefined ? config.EnableCherryBlossom : true;
const petalCount = config.PetalCount || 25;
const randomCherryBlossom = config.EnableRandomCherryBlossom !== undefined ? config.EnableRandomCherryBlossom : true;
const randomCherryBlossomMobile = config.EnableRandomCherryBlossomMobile !== undefined ? config.EnableRandomCherryBlossomMobile : false;
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true;
let msgPrinted = false;
function toggleCherryBlossom() {
const container = document.querySelector('.cherryblossom-container');
if (!container) return;
const videoPlayer = document.querySelector('.videoPlayerContainer');
const trailerPlayer = document.querySelector('.youtubePlayerContainer');
const isDashboard = document.body.classList.contains('dashboardDocument');
const hasUserMenu = document.querySelector('#app-user-menu');
if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) {
container.style.display = 'none';
if (!msgPrinted) {
console.log('CherryBlossom hidden');
msgPrinted = true;
}
} else {
container.style.display = 'block';
if (msgPrinted) {
console.log('CherryBlossom visible');
msgPrinted = false;
}
}
}
const observer = new MutationObserver(toggleCherryBlossom);
observer.observe(document.body, { childList: true, subtree: true, attributes: true });
function createPetal(container) {
const petal = document.createElement('div');
petal.classList.add('cherryblossom-petal');
const type = Math.random() > 0.5 ? 'type1' : 'type2';
petal.classList.add(type);
const color = Math.random() > 0.7 ? 'darker' : 'lighter';
petal.classList.add(color);
const randomLeft = Math.random() * 100;
petal.style.left = `${randomLeft}%`;
const size = Math.random() * 0.5 + 0.5;
petal.style.transform = `scale(${size})`;
const duration = Math.random() * 5 + 8;
const delay = Math.random() * 10;
const swayDuration = Math.random() * 2 + 2;
if (enableDifferentDuration) {
petal.style.animationDuration = `${duration}s, ${swayDuration}s`;
}
petal.style.animationDelay = `${delay}s, ${Math.random() * 3}s`;
container.appendChild(petal);
}
function addRandomObjects() {
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');
if (!container) {
container = document.createElement("div");
container.className = "cherryblossom-container";
container.setAttribute("aria-hidden", "true");
document.body.appendChild(container);
}
// Initial batch
for (let i = 0; i < 15; i++) {
createPetal(container);
}
}
function initializeCherryBlossom() {
if (!cherryBlossom) return;
initObjects();
toggleCherryBlossom();
const screenWidth = window.innerWidth;
if (randomCherryBlossom && (screenWidth > 768 || randomCherryBlossomMobile)) {
addRandomObjects();
}
}
initializeCherryBlossom();

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;
} }
.christmas { .christmas {
@@ -37,7 +38,7 @@
} }
100% { 100% {
top: 100%; top: 110%;
} }
} }
@@ -61,7 +62,7 @@
} }
100% { 100% {
top: 100%; top: 110%;
} }
} }

View File

@@ -0,0 +1,35 @@
.earthday-container {
position: fixed;
bottom: 0;
left: 0;
width: 100vw;
height: 15vh;
pointer-events: none;
z-index: 1000;
overflow: hidden;
}
.earthday-meadow {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
transform-origin: bottom;
animation: grow-meadow 3s cubic-bezier(0.1, 0.8, 0.2, 1) forwards;
}
@keyframes grow-meadow {
0% { transform: translateY(100%); opacity: 0; }
100% { transform: translateY(0); opacity: 0.95; }
}
.earthday-sway {
transform-origin: bottom center;
animation: sway-grass 4s ease-in-out infinite alternate;
}
@keyframes sway-grass {
0% { transform: skewX(-2deg); }
100% { transform: skewX(2deg); }
}

View File

@@ -0,0 +1,126 @@
// 1. Read Configuration
const config = window.SeasonalsPluginConfig?.EarthDay || {};
const enabled = config.EnableEarthDay !== undefined ? config.EnableEarthDay : true;
const vineCount = config.VineCount || 4;
let msgPrinted = false;
// 2. Toggle Function
function toggleEarthDay() {
const container = document.querySelector('.earthday-container');
if (!container) return;
const videoPlayer = document.querySelector('.videoPlayerContainer');
const trailerPlayer = document.querySelector('.youtubePlayerContainer');
const isDashboard = document.body.classList.contains('dashboardDocument');
const hasUserMenu = document.querySelector('#app-user-menu');
if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) {
container.style.display = 'none';
if (!msgPrinted) {
console.log('EarthDay hidden');
msgPrinted = true;
}
} else {
container.style.display = 'block';
if (msgPrinted) {
console.log('EarthDay visible');
msgPrinted = false;
}
}
}
// 3. MutationObserver
const observer = new MutationObserver(toggleEarthDay);
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true
});
// 4. Element Creation
function createElements() {
const container = document.querySelector('.earthday-container') || document.createElement('div');
if (!document.querySelector('.earthday-container')) {
container.className = 'earthday-container';
container.setAttribute('aria-hidden', 'true');
document.body.appendChild(container);
}
const w = window.innerWidth;
const hSVG = Math.floor(window.innerHeight * 0.15) || 100; // 15vh roughly
let paths = '';
// Generate Grass
for (let i = 0; i < 400; i++) {
const x = Math.random() * w;
const h = hSVG * 0.2 + Math.random() * (hSVG * 0.8);
const cY = hSVG - h;
const bend = x + (Math.random() * 40 - 20);
const color = Math.random() > 0.5 ? '#2E8B57' : '#3CB371';
const width = 1 + Math.random() * 2;
paths += `<path d="M ${x} ${hSVG} Q ${bend} ${cY+hSVG*0.2} ${bend} ${cY}" stroke="${color}" stroke-width="${width}" fill="none"/>`;
}
// Generate Flowers
const colors = ['#FF69B4', '#FFD700', '#87CEFA', '#FF4500', '#BA55D3', '#FFA500', '#FF1493'];
const flowerCount = Math.max(10, vineCount * 15);
for (let i = 0; i < flowerCount; i++) {
const x = 10 + Math.random() * (w - 20);
const y = hSVG * 0.1 + Math.random() * (hSVG * 0.5);
const col = colors[Math.floor(Math.random() * colors.length)];
paths += `<path d="M ${x} ${hSVG} Q ${x - 5 + Math.random() * 10} ${y+15} ${x} ${y}" stroke="#006400" stroke-width="1.5" fill="none"/>`;
const r = 2 + Math.random() * 1.5;
paths += `<circle cx="${x-r}" cy="${y-r}" r="${r}" fill="${col}"/>`;
paths += `<circle cx="${x+r}" cy="${y-r}" r="${r}" fill="${col}"/>`;
paths += `<circle cx="${x-r}" cy="${y+r}" r="${r}" fill="${col}"/>`;
paths += `<circle cx="${x+r}" cy="${y+r}" r="${r}" fill="${col}"/>`;
paths += `<circle cx="${x}" cy="${y}" r="${r*0.7}" fill="#FFF8DC"/>`;
}
const svgContent = `
<svg class="earthday-meadow" viewBox="0 0 ${w} ${hSVG}" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
<g class="earthday-sway">
${paths}
</g>
</svg>
`;
container.innerHTML = svgContent;
}
// 5. Responsive Resize
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
const handleResize = debounce(() => {
const container = document.querySelector('.earthday-container');
if (container) {
container.innerHTML = '';
createElements();
}
}, 250);
window.addEventListener('resize', handleResize);
// 6. Initialization
function initializeEarthDay() {
if (!enabled) return;
createElements();
toggleEarthDay();
}
initializeEarthDay();

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;
} }
.hopping-rabbit { .hopping-rabbit {
@@ -58,7 +59,7 @@
} }
100% { 100% {
top: 100%; top: 110%;
} }
} }
@@ -82,7 +83,7 @@
} }
100% { 100% {
top: 100%; top: 110%;
} }
} }

View File

@@ -0,0 +1,43 @@
.eurovision-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: 1000;
overflow: hidden;
contain: layout paint;
}
.music-note-wrapper {
position: absolute;
left: 0;
/* initial top will be set via JS */
opacity: 0;
animation: move-right linear infinite;
will-change: transform, opacity;
}
.music-note {
display: block;
font-size: 2rem;
color: rgba(255, 255, 255, 0.9);
text-shadow: 0 0 8px rgba(255, 255, 255, 0.6);
animation: sway ease-in-out infinite alternate;
will-change: transform;
}
/* Horizontal scroll from left to right */
@keyframes move-right {
0% { transform: translateX(-10vw); opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { transform: translateX(110vw); opacity: 0; }
}
/* Sine-wave style vertical bouncing for the note itself */
@keyframes sway {
0% { transform: translateY(-30px); }
100% { transform: translateY(30px); }
}

View File

@@ -0,0 +1,105 @@
// 1. Read Configuration
const config = window.SeasonalsPluginConfig?.Eurovision || {};
const enabled = config.EnableEurovision !== undefined ? config.EnableEurovision : true;
const elementCount = config.SymbolCount || 25;
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true;
const enableColorfulNotes = config.EnableColorfulNotes !== undefined ? config.EnableColorfulNotes : true;
const eurovisionColorsStr = config.EurovisionColors || '#ff0026ff, #17a6ffff, #32d432ff, #FFD700, #f0821bff, #f826f8ff';
const glowSize = config.EurovisionGlowSize !== undefined ? config.EurovisionGlowSize : 2;
let msgPrinted = false;
// 2. Toggle Function
function toggleEurovision() {
const container = document.querySelector('.eurovision-container');
if (!container) return;
const videoPlayer = document.querySelector('.videoPlayerContainer');
const trailerPlayer = document.querySelector('.youtubePlayerContainer');
const isDashboard = document.body.classList.contains('dashboardDocument');
const hasUserMenu = document.querySelector('#app-user-menu');
if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) {
container.style.display = 'none';
if (!msgPrinted) {
console.log('Eurovision hidden');
msgPrinted = true;
}
} else {
container.style.display = 'block';
if (msgPrinted) {
console.log('Eurovision visible');
msgPrinted = false;
}
}
}
// 3. MutationObserver
const observer = new MutationObserver(toggleEurovision);
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true
});
// 4. Element Creation
function createElements() {
const container = document.querySelector('.eurovision-container') || document.createElement('div');
if (!document.querySelector('.eurovision-container')) {
container.className = 'eurovision-container';
container.setAttribute('aria-hidden', 'true');
document.body.appendChild(container);
}
const notesSymbols = ['♪', '♫', '♬', '♭', '♮', '♯', '𝄞', '𝄢'];
const pColors = eurovisionColorsStr.split(',').map(s => s.trim()).filter(s => s);
for (let i = 0; i < elementCount; i++) {
const wrapper = document.createElement('div');
wrapper.className = 'music-note-wrapper';
const note = document.createElement('span');
note.className = 'music-note';
note.textContent = notesSymbols[Math.floor(Math.random() * notesSymbols.length)];
wrapper.appendChild(note);
wrapper.style.top = `${Math.random() * 90}vh`;
const minMoveDur = 10;
const maxMoveDur = 25;
const moveDur = enableDifferentDuration
? minMoveDur + Math.random() * (maxMoveDur - minMoveDur)
: (minMoveDur + maxMoveDur) / 2;
wrapper.style.animationDuration = `${moveDur}s`;
wrapper.style.animationDelay = `${Math.random() * 15}s`;
const minSwayDur = 1;
const maxSwayDur = 3;
const swayDur = minSwayDur + Math.random() * (maxSwayDur - minSwayDur);
note.style.animationDuration = `${swayDur}s`;
note.style.animationDelay = `${Math.random() * 2}s`;
note.style.fontSize = `${Math.random() * 1.5 + 1.5}rem`;
if (enableColorfulNotes && pColors.length > 0) {
note.style.color = pColors[Math.floor(Math.random() * pColors.length)];
note.style.textShadow = `0 0 ${glowSize}px ${note.style.color}`;
} else {
note.style.color = `rgba(255, 255, 255, 0.9)`;
note.style.textShadow = `0 0 ${glowSize}px rgba(255, 255, 255, 0.6)`;
}
container.appendChild(wrapper);
}
}
// 5. Initialization
function initializeEurovision() {
if (!enabled) return;
createElements();
toggleEurovision();
}
initializeEurovision();

View File

@@ -7,6 +7,7 @@
height: 100%; height: 100%;
pointer-events: none; pointer-events: none;
z-index: 10; z-index: 10;
contain: layout paint;
} }
.rocket-trail { .rocket-trail {

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;
} }
.halloween { .halloween {
@@ -34,11 +35,11 @@
@-webkit-keyframes halloween-fall { @-webkit-keyframes halloween-fall {
0% { 0% {
bottom: -10% bottom: -10%;
} }
100% { 100% {
bottom: 100% bottom: 110%;
} }
} }
@@ -58,11 +59,11 @@
@keyframes halloween-fall { @keyframes halloween-fall {
0% { 0% {
bottom: -10% bottom: -10%;
} }
100% { 100% {
bottom: 100% bottom: 110%;
} }
} }

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;
} }
.heart { .heart {
@@ -32,11 +33,11 @@
@-webkit-keyframes heart-fall { @-webkit-keyframes heart-fall {
0% { 0% {
bottom: -10% bottom: -10%;
} }
100% { 100% {
bottom: 100% bottom: 110%;
} }
} }
@@ -56,11 +57,11 @@
@keyframes heart-fall { @keyframes heart-fall {
0% { 0% {
bottom: -10% bottom: -10%;
} }
100% { 100% {
bottom: 100% bottom: 110%;
} }
} }

View File

@@ -0,0 +1,11 @@
.piday-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: 1000;
overflow: hidden;
contain: layout paint;
}

View File

@@ -0,0 +1,165 @@
// 1. Read Configuration
const config = window.SeasonalsPluginConfig?.PiDay || {};
const enabled = config.EnablePiDay !== undefined ? config.EnablePiDay : true;
const maxTrails = config.SymbolCount || 25; // Directly mapped, smaller default
let msgPrinted = false;
let isHidden = false;
// 2. Toggle Function
function togglePiDay() {
const container = document.querySelector('.piday-container');
if (!container) return;
const videoPlayer = document.querySelector('.videoPlayerContainer');
const trailerPlayer = document.querySelector('.youtubePlayerContainer');
const isDashboard = document.body.classList.contains('dashboardDocument');
const hasUserMenu = document.querySelector('#app-user-menu');
if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) {
if (!isHidden) {
container.style.display = 'none';
isHidden = true;
if (!msgPrinted) {
console.log('PiDay hidden');
msgPrinted = true;
}
}
} else {
if (isHidden) {
container.style.display = 'block';
isHidden = false;
if (msgPrinted) {
console.log('PiDay visible');
msgPrinted = false;
}
}
}
}
// 3. MutationObserver
const observer = new MutationObserver(togglePiDay);
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true
});
// 4. Element Creation
function createElements() {
const container = document.querySelector('.piday-container') || document.createElement('div');
if (!document.querySelector('.piday-container')) {
container.className = 'piday-container';
container.setAttribute('aria-hidden', 'true');
document.body.appendChild(container);
}
const canvas = document.createElement('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.display = 'block';
container.appendChild(canvas);
const ctx = canvas.getContext('2d');
// const chars = '0123456789πABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
const chars = '0123456789'.split('');
const fontSize = 18;
class Trail {
constructor() {
this.reset();
this.y = Math.random() * -100; // Allow initial staggered start
}
reset() {
const cols = Math.floor(canvas.width / fontSize);
this.x = Math.floor(Math.random() * cols);
this.y = -Math.round(Math.random() * 20);
this.speed = 0.5 + Math.random() * 0.5;
this.len = 10 + Math.floor(Math.random() * 20);
this.chars = [];
for(let i=0; i<this.len; i++) {
this.chars.push(chars[Math.floor(Math.random() * chars.length)]);
}
}
update() {
const oldY = Math.floor(this.y);
this.y += this.speed;
const newY = Math.floor(this.y);
// If crossed a full vertical unit, push a new character and pop the old one to preserve screen positions
if (newY > oldY) {
this.chars.unshift(chars[Math.floor(Math.random() * chars.length)]);
this.chars.pop();
}
// Randomly mutate some characters (heads mutate faster)
for (let i = 0; i < this.len; i++) {
const chance = i < 3 ? 0.90 : 0.98;
if (Math.random() > chance) {
this.chars[i] = chars[Math.floor(Math.random() * chars.length)];
}
}
if (this.y - this.len > Math.ceil(canvas.height / fontSize)) {
this.reset();
}
}
draw(ctx) {
const headY = Math.floor(this.y);
for (let i = 0; i < this.len; i++) {
const charY = headY - i;
if (charY < 0 || charY * fontSize > canvas.height + fontSize) continue;
const ratio = i / this.len;
const alpha = 1 - ratio;
if (i === 0) {
ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`;
ctx.shadowBlur = 8;
ctx.shadowColor = '#0F0';
} else if (i === 1) {
ctx.fillStyle = `rgba(150, 255, 150, ${alpha})`;
ctx.shadowBlur = 4;
ctx.shadowColor = '#0F0';
} else {
ctx.fillStyle = `rgba(0, 255, 0, ${alpha * 0.8})`;
ctx.shadowBlur = 0;
}
ctx.fillText(this.chars[i], this.x * fontSize + fontSize/2, charY * fontSize);
}
}
}
const trails = [];
for(let i=0; i<maxTrails; i++) trails.push(new Trail());
function loop() {
if (isHidden) return; // Pause drawing when hidden
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = 'bold ' + fontSize + 'px monospace';
ctx.textAlign = 'center';
for (const t of trails) {
t.update();
t.draw(ctx);
}
}
if (window.pidayInterval) clearInterval(window.pidayInterval);
window.pidayInterval = setInterval(loop, 50);
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
}
// 5. Initialization
function initializePiDay() {
if (!enabled) return;
createElements();
togglePiDay();
}
initializePiDay();

View File

@@ -0,0 +1,32 @@
.pride-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: 9999;
overflow: hidden;
contain: layout paint;
}
.pride-heart {
position: absolute;
bottom: -50px;
animation: pride-rise ease-in infinite;
will-change: transform;
}
@keyframes pride-rise {
0% { transform: translateY(0) scale(0.8); opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { transform: translateY(-115vh) scale(1.2); opacity: 0; }
}
/* Coloring the Jellyfin Header */
.skinHeader.pride-header {
background: linear-gradient(90deg, #E40303, #FF8C00, #FFED00, #008026, #24408E, #732982) !important;
}

View File

@@ -0,0 +1,88 @@
// 1. Read Configuration
const config = window.SeasonalsPluginConfig?.Pride || {};
const enabled = config.EnablePride !== undefined ? config.EnablePride : true;
const elementCount = config.HeartCount || 20;
const heartSize = config.HeartSize || 1.5;
const colorHeader = config.ColorHeader !== undefined ? config.ColorHeader : true;
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() {
const container = document.querySelector('.pride-container');
if (!container) return;
const videoPlayer = document.querySelector('.videoPlayerContainer');
const trailerPlayer = document.querySelector('.youtubePlayerContainer');
const isDashboard = document.body.classList.contains('dashboardDocument');
const hasUserMenu = document.querySelector('#app-user-menu');
if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) {
container.style.display = 'none';
if (!msgPrinted) {
console.log('Pride hidden');
msgPrinted = true;
}
} else {
container.style.display = 'block';
if (msgPrinted) {
console.log('Pride visible');
msgPrinted = false;
}
}
}
// 3. MutationObserver
// Watches the DOM for changes so the effect can auto-hide/show.
const observer = new MutationObserver(togglePride);
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true
});
// 4. Element Creation
// Create and append your animated elements to the container.
function createElements() {
const container = document.querySelector('.pride-container') || document.createElement('div');
if (!document.querySelector('.pride-container')) {
container.className = 'pride-container';
container.setAttribute('aria-hidden', 'true');
document.body.appendChild(container);
}
if (colorHeader) {
const header = document.querySelector('.skinHeader');
if (header) {
header.classList.add('pride-header');
}
}
const heartEmojis = ['❤️', '🧡', '💛', '💚', '💙', '💜'];
for (let i = 0; i < elementCount; i++) {
const el = document.createElement('div');
el.className = 'pride-heart';
el.innerText = heartEmojis[Math.floor(Math.random() * heartEmojis.length)];
el.style.fontSize = `${heartSize}rem`;
el.style.left = `${Math.random() * 100}vw`;
el.style.animationDuration = `${5 + Math.random() * 5}s`;
el.style.animationDelay = `${Math.random() * 5}s`;
el.style.marginLeft = `${(Math.random() - 0.5) * 100}px`;
container.appendChild(el);
}
}
// 5. Initialization
function initializePride() {
if (!enabled) return;
createElements();
togglePride();
}
initializePride();

View File

@@ -0,0 +1,26 @@
.rain-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: 1000;
overflow: hidden;
contain: layout paint;
}
.raindrop-pure {
position: absolute;
width: 2px;
height: 40px;
background: linear-gradient(to bottom, rgba(200, 200, 255, 0), rgba(200, 200, 255, 0.7));
transform-origin: bottom;
}
@keyframes pure-rain {
0% { transform: translateY(-30vh) translateX(0) rotate(20deg); opacity: 0; }
5% { opacity: 1; }
95% { opacity: 1; }
100% { transform: translateY(110vh) translateX(-40vh) rotate(20deg); opacity: 0; }
}

View File

@@ -0,0 +1,77 @@
// 1. Read Configuration
const config = window.SeasonalsPluginConfig?.Rain || {};
const enabled = config.EnableRain !== undefined ? config.EnableRain : true;
const isMobile = window.innerWidth <= 768;
const elementCount = isMobile ? (config.RaindropCountMobile || 150) : (config.RaindropCount || 300);
const rainSpeed = config.RainSpeed || 1.0;
let msgPrinted = false;
// 2. Toggle Function
function toggleRain() {
const container = document.querySelector('.rain-container');
if (!container) return;
const videoPlayer = document.querySelector('.videoPlayerContainer');
const trailerPlayer = document.querySelector('.youtubePlayerContainer');
const isDashboard = document.body.classList.contains('dashboardDocument');
const hasUserMenu = document.querySelector('#app-user-menu');
if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) {
container.style.display = 'none';
if (!msgPrinted) {
console.log('Rain hidden');
msgPrinted = true;
}
} else {
container.style.display = 'block';
if (msgPrinted) {
console.log('Rain visible');
msgPrinted = false;
}
}
}
// 3. MutationObserver
const observer = new MutationObserver(toggleRain);
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true
});
// 4. Element Creation
function createElements() {
const container = document.querySelector('.rain-container') || document.createElement('div');
if (!document.querySelector('.rain-container')) {
container.className = 'rain-container';
container.setAttribute('aria-hidden', 'true');
document.body.appendChild(container);
}
for (let i = 0; i < elementCount; i++) {
const drop = document.createElement('div');
drop.className = 'raindrop-pure';
drop.style.left = `${Math.random() * 140}vw`;
drop.style.top = `${-20 - Math.random() * 50}vh`;
const duration = (0.5 + Math.random() * 0.5) / (rainSpeed || 1);
drop.style.animation = `pure-rain ${duration}s linear infinite`;
drop.style.animationDelay = `${Math.random() * 2}s`;
drop.style.opacity = Math.random() * 0.5 + 0.3;
container.appendChild(drop);
}
}
// 5. Initialization
function initializeRain() {
if (!enabled) return;
createElements();
toggleRain();
}
initializeRain();

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;
} }
.resurrection-symbol { .resurrection-symbol {
@@ -43,7 +44,7 @@
} }
100% { 100% {
top: 105%; top: 110%;
} }
} }

View File

@@ -73,6 +73,41 @@ const ThemeConfigs = {
js: '../Seasonals/Resources/carnival.js', js: '../Seasonals/Resources/carnival.js',
containerClass: 'carnival-container' containerClass: 'carnival-container'
}, },
cherryblossom: {
css: '../Seasonals/Resources/cherryblossom.css',
js: '../Seasonals/Resources/cherryblossom.js',
containerClass: 'cherryblossom-container'
},
piday: {
css: '../Seasonals/Resources/piday.css',
js: '../Seasonals/Resources/piday.js',
containerClass: 'piday-container'
},
eurovision: {
css: '../Seasonals/Resources/eurovision.css',
js: '../Seasonals/Resources/eurovision.js',
containerClass: 'eurovision-container'
},
storm: {
css: '../Seasonals/Resources/storm.css',
js: '../Seasonals/Resources/storm.js',
containerClass: 'storm-container'
},
pride: {
css: '../Seasonals/Resources/pride.css',
js: '../Seasonals/Resources/pride.js',
containerClass: 'pride-container'
},
rain: {
css: '../Seasonals/Resources/rain.css',
js: '../Seasonals/Resources/rain.js',
containerClass: 'rain-container'
},
earthday: {
css: '../Seasonals/Resources/earthday.css',
js: '../Seasonals/Resources/earthday.js',
containerClass: 'earthday-container'
},
none: { none: {
containerClass: 'none' containerClass: 'none'
}, },
@@ -246,6 +281,12 @@ const SeasonalsManager = {
if (response.ok) { if (response.ok) {
this.config = await response.json(); this.config = await response.json();
window.SeasonalsPluginConfig = this.config; window.SeasonalsPluginConfig = this.config;
if (this.config.IsEnabled === false) {
console.log('Seasonals: Plugin is disabled globally.');
return;
}
console.log('Seasonals: Seasonals Config loaded:', this.config); console.log('Seasonals: Seasonals Config loaded:', this.config);
} }
} catch (error) { } catch (error) {

View File

@@ -8,6 +8,7 @@
top: 0; top: 0;
left: 0; left: 0;
z-index: 10; z-index: 10;
contain: layout paint;
} }
#snowfallCanvas { #snowfallCanvas {

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;
} }
.snowflake { .snowflake {
@@ -37,7 +38,7 @@
} }
100% { 100% {
top: 100%; top: 110%;
} }
} }
@@ -61,7 +62,7 @@
} }
100% { 100% {
top: 100%; top: 110%;
} }
} }

View File

@@ -8,6 +8,7 @@
top: 0; top: 0;
left: 0; left: 0;
z-index: 10; z-index: 10;
contain: layout paint;
} }
#snowfallCanvas { #snowfallCanvas {

View File

@@ -5,42 +5,10 @@
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100vh;
pointer-events: none; pointer-events: none;
z-index: 1000; z-index: 1000;
} contain: layout paint;
/* Petals */
.spring-petal {
position: fixed;
top: -20px;
z-index: 1005;
width: 15px;
height: 10px;
background-color: #ffc0cb;
border-radius: 15px 0px 15px 0px;
will-change: transform, top;
animation-name: spring-fall, spring-sway;
animation-timing-function: linear, ease-in-out;
animation-iteration-count: infinite, infinite;
animation-duration: 10s, 3s;
}
.spring-petal.lighter {
background-color: #ffd1dc;
opacity: 0.8;
}
.spring-petal.darker {
background-color: #ffb7c5;
opacity: 0.9;
}
.spring-petal.type2 {
width: 12px;
height: 12px;
border-radius: 10px 0px 10px 5px;
} }
/* Pollen */ /* Pollen */
@@ -67,24 +35,21 @@
z-index: 5; z-index: 5;
transform-origin: top center; transform-origin: top center;
pointer-events: none; pointer-events: none;
opacity: 0;
animation-name: spring-beam-pulse;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
} }
/* Grass */ /* Grass Container (Wrapper) */
.spring-grass-container { .spring-grass-container {
position: fixed; position: absolute;
bottom: 0; bottom: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 80px; height: 80px;
z-index: 1002;
overflow: hidden;
pointer-events: none; pointer-events: none;
transform-origin: bottom;
} }
/* HTML Grass Overlayer */
.spring-grass { .spring-grass {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
@@ -99,55 +64,119 @@
animation-iteration-count: infinite; animation-iteration-count: infinite;
} }
/* Ladybugs */ @keyframes spring-grass-sway {
.spring-ladybug { 0% { transform: rotate(0deg); }
50% { transform: rotate(8deg); }
100% { transform: rotate(0deg); }
}
/* SVG Meadow Layer */
.spring-meadow-layer {
position: absolute; position: absolute;
width: 6px; bottom: 0;
height: 4px; left: 0;
background-color: #e74c3c; /* Red */ width: 100%;
border-radius: 3px 3px 0 0; height: 100%;
z-index: 1003; animation: spring-grow-meadow 3s cubic-bezier(0.1, 0.8, 0.2, 1) forwards;
}
will-change: left, transform;
.spring-meadow {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
}
@keyframes spring-grow-meadow {
0% { transform: translateY(100%); opacity: 0; }
100% { transform: translateY(0); opacity: 0.95; }
}
.spring-sway {
transform-origin: bottom center;
animation: spring-meadow-sway 4s ease-in-out infinite alternate;
}
@keyframes spring-meadow-sway {
0% { transform: skewX(-2deg); }
100% { transform: skewX(2deg); }
}
/* Birds */
.spring-bird {
position: static !important;
display: block;
z-index: 1001;
/* MARK: BIRD SIZE */
width: 80px;
height: auto;
will-change: transform;
animation-timing-function: linear; animation-timing-function: linear;
animation-iteration-count: infinite; animation-iteration-count: infinite;
} }
.spring-ladybug.right { /* Butterflies */
animation-name: spring-bug-crawl-right; .spring-butterfly {
transform: scaleX(1); position: static !important;
display: block;
z-index: 1001;
/* MARK: BUTTERFLY SIZE */
width: 40px;
height: auto;
will-change: transform;
animation-timing-function: linear;
animation-iteration-count: infinite;
} }
.spring-ladybug.left { /* Bee */
animation-name: spring-bug-crawl-left; .spring-bee {
transform: scaleX(-1); position: static !important;
display: block;
z-index: 1001;
/* MARK: BEE SIZE */
width: 30px;
height: auto;
will-change: transform;
} }
.spring-ladybug::before { /* Ladybug */
content: ''; .spring-ladybug-gif {
position: absolute; position: static !important;
right: -2px; display: block;
top: 1px; /* MARK: LADYBUG SIZE */
width: 2px; width: 30px;
height: 2px; height: auto;
background-color: #000; will-change: transform;
border-radius: 50%; animation-timing-function: linear;
animation-iteration-count: infinite;
--bug-rotation: -55deg;
} }
@keyframes spring-fall { .spring-ladybug-wrapper {
0% { top: -10%; } z-index: 1002;
100% { top: 100%; }
} }
@keyframes spring-sway { /* Generic Wrappers */
0%, 100% { .spring-anim-wrapper {
transform: translateX(0) rotate(0deg); position: fixed;
} z-index: 1001;
50% { will-change: transform;
transform: translateX(30px) rotate(45deg); top: 0; left: 0;
}
} }
.spring-align-y {
width: 100%; height: 100%;
will-change: transform;
}
.spring-mirror-wrapper {
width: 100%; height: 100%;
will-change: transform;
transform-origin: center;
}
@keyframes spring-float { @keyframes spring-float {
0% { transform: translateX(0) translateY(0); } 0% { transform: translateX(0) translateY(0); }
25% { transform: translateX(20px) translateY(-10px); } 25% { transform: translateX(20px) translateY(-10px); }
@@ -157,23 +186,70 @@
} }
@keyframes spring-beam-pulse { @keyframes spring-beam-pulse {
0% { opacity: 0.3; transform: rotate(45deg) scaleX(1); } 0% { opacity: 0; transform: rotate(var(--beam-rotation, 45deg)) scaleX(0.8); }
50% { opacity: 0.6; transform: rotate(45deg) scaleX(1.1); } 50% { opacity: 0.6; transform: rotate(var(--beam-rotation, 45deg)) scaleX(1.2); }
100% { opacity: 0.3; transform: rotate(45deg) scaleX(1); } 100% { opacity: 0; transform: rotate(var(--beam-rotation, 45deg)) scaleX(0.8); }
} }
@keyframes spring-grass-sway {
0% { transform: rotate(0deg); }
50% { transform: rotate(8deg); } /* Wrapper animations (Flight across screen) */
100% { transform: rotate(0deg); } @keyframes spring-fly-right-wrapper {
0% { transform: translateX(-10vw); }
100% { transform: translateX(110vw); }
} }
@keyframes spring-bug-crawl-right { @keyframes spring-fly-left-wrapper {
0% { left: -5%; } 0% { transform: translateX(110vw); }
100% { left: 105%; } 100% { transform: translateX(-10vw); }
} }
@keyframes spring-bug-crawl-left { /* Vertical Drift for Sloped Flight */
0% { left: 105%; } @keyframes spring-vertical-drift {
100% { left: -5%; } 0% { transform: translateY(var(--start-y, 10vh)); }
100% { transform: translateY(var(--end-y, 10vh)); }
}
/* Inner animations (Bobbing/Fluttering) */
@keyframes spring-bob {
0% { transform: translateY(0); }
50% { transform: translateY(-20px); }
100% { transform: translateY(0); }
}
@keyframes spring-flutter {
0% { transform: translateY(0) rotate(0deg); }
25% { transform: translateY(-5px) rotate(5deg); }
50% { transform: translateY(0) rotate(0deg); }
75% { transform: translateY(5px) rotate(-5deg); }
100% { transform: translateY(0) rotate(0deg); }
}
/* Bee Buzz - Reduced Intensity */
@keyframes spring-buzz {
0% { transform: translate(0, 0); }
25% { transform: translate(2px, -2px); }
50% { transform: translate(0, 2px); }
75% { transform: translate(-2px, -2px); }
100% { transform: translate(0, 0); }
}
/* Ladybug Walk (Wrapper handles X) */
@keyframes spring-walk-right {
0% { transform: translateX(-10vw); }
100% { transform: translateX(110vw); }
}
@keyframes spring-walk-left {
0% { transform: translateX(110vw); }
100% { transform: translateX(-10vw); }
}
/* Ladybug Crawl (Inner Wobble) */
@keyframes spring-crawl {
0% { transform: translateY(0) rotate(var(--bug-rotation, 0deg)); }
25% { transform: translateY(-3px) rotate(calc(var(--bug-rotation, 0deg) + 8deg)); }
50% { transform: translateY(0) rotate(var(--bug-rotation, 0deg)); }
75% { transform: translateY(-3px) rotate(calc(var(--bug-rotation, 0deg) - 8deg)); }
100% { transform: translateY(0) rotate(var(--bug-rotation, 0deg)); }
} }

View File

@@ -1,16 +1,28 @@
const config = window.SeasonalsPluginConfig?.Spring || {}; const config = window.SeasonalsPluginConfig?.Spring || {};
const spring = config.EnableSpring !== undefined ? config.EnableSpring : true; const spring = config.EnableSpring !== undefined ? config.EnableSpring : true; // Enable/disable spring
const petalCount = config.PetalCount || 25; const pollenCount = config.PollenCount || 30; // Number of pollen particles
const pollenCount = config.PollenCount || 15; const sunbeamCount = config.SunbeamCount || 5; // Number of sunbeams
const ladybugCountConfig = config.LadybugCount || 5; const enableSunbeams = config.EnableSpringSunbeams !== undefined ? config.EnableSpringSunbeams : true; // Enable/disable sunbeams
const sunbeamCount = config.SunbeamCount || 5; const birdCount = config.BirdCount !== undefined ? config.BirdCount : 3; // Number of birds
const butterflyCount = config.ButterflyCount !== undefined ? config.ButterflyCount : 4; // Number of butterflies
const beeCount = config.BeeCount !== undefined ? config.BeeCount : 2; // Number of bees
const ladybugCount = config.LadybugCount !== undefined ? config.LadybugCount : 2; // Number of ladybugs
const randomSpring = config.EnableRandomSpring !== undefined ? config.EnableRandomSpring : true; // Enable random spring objects
const randomSpring = config.EnableRandomSpring !== undefined ? config.EnableRandomSpring : true; const birdImages = [
const randomSpringMobile = config.EnableRandomSpringMobile !== undefined ? config.EnableRandomSpringMobile : false; '../Seasonals/Resources/spring_assets/Bird_1.gif',
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; '../Seasonals/Resources/spring_assets/Bird_2.gif',
const enablePetals = config.EnableSpringPetals !== undefined ? config.EnableSpringPetals : true; '../Seasonals/Resources/spring_assets/Bird_3.gif'
const enableSunbeams = config.EnableSpringSunbeams !== undefined ? config.EnableSpringSunbeams : true; ];
const butterflyImages = [
'../Seasonals/Resources/spring_assets/Butterfly_1.gif',
'../Seasonals/Resources/spring_assets/Butterfly_2.gif'
];
const beeImage = '../Seasonals/Resources/spring_assets/Bee.gif';
const ladybugImage = '../Seasonals/Resources/spring_assets/ladybug.gif';
let msgPrinted = false; let msgPrinted = false;
@@ -41,45 +53,18 @@ function toggleSpring() {
const observer = new MutationObserver(toggleSpring); const observer = new MutationObserver(toggleSpring);
observer.observe(document.body, { childList: true, subtree: true, attributes: true }); observer.observe(document.body, { childList: true, subtree: true, attributes: true });
function createPetal(container) {
if (!enablePetals) return;
const petal = document.createElement('div');
petal.classList.add('spring-petal');
const type = Math.random() > 0.5 ? 'type1' : 'type2';
petal.classList.add(type);
const color = Math.random() > 0.7 ? 'darker' : 'lighter';
petal.classList.add(color);
const randomLeft = Math.random() * 100;
petal.style.left = `${randomLeft}%`;
const size = Math.random() * 0.5 + 0.5;
petal.style.transform = `scale(${size})`;
const duration = Math.random() * 5 + 8;
const delay = Math.random() * 10;
const swayDuration = Math.random() * 2 + 2;
if (enableDifferentDuration) {
petal.style.animationDuration = `${duration}s, ${swayDuration}s`;
}
petal.style.animationDelay = `${delay}s, ${Math.random() * 3}s`;
container.appendChild(petal);
}
function createPollen(container) { function createPollen(container) {
const pollen = document.createElement('div'); const pollen = document.createElement('div');
pollen.classList.add('spring-pollen'); pollen.classList.add('spring-pollen');
// MARK: POLLEN START VERTICAL POSITION (in %)
const startY = Math.random() * 60 + 20; const startY = Math.random() * 60 + 20;
pollen.style.top = `${startY}%`; pollen.style.top = `${startY}%`;
pollen.style.left = `${Math.random() * 100}%`; pollen.style.left = `${Math.random() * 100}%`;
const size = Math.random() * 3 + 1; // MARK: POLLEN SIZE
const size = Math.random() * 3 + 1; // 1-4px
pollen.style.width = `${size}px`; pollen.style.width = `${size}px`;
pollen.style.height = `${size}px`; pollen.style.height = `${size}px`;
@@ -90,27 +75,40 @@ function createPollen(container) {
container.appendChild(pollen); container.appendChild(pollen);
} }
function createSunbeam(container) { function spawnSunbeamGroup(container, count) {
if (!enableSunbeams) return; if (!enableSunbeams) return;
const beam = document.createElement('div'); const rotate = Math.random() * 30 - 15 + 45;
beam.classList.add('spring-sunbeam'); let beamsActive = count;
const left = Math.random() * 100; // Spread across full width for (let i = 0; i < count; i++) {
beam.style.left = `${left}%`; const beam = document.createElement('div');
beam.classList.add('spring-sunbeam');
// Thinner beams as requested
const width = Math.random() * 20 + 10; // 10-30px wide const left = Math.random() * 100;
beam.style.width = `${width}px`; beam.style.left = `${left}%`;
const rotate = Math.random() * 20 - 10 + 45; // MARK: SUNBEAM WIDTH (in px)
beam.style.transform = `rotate(${rotate}deg)`; const width = Math.random() * 12 + 8; // 8-20px wide
beam.style.width = `${width}px`;
const duration = Math.random() * 10 + 10;
beam.style.animationDuration = `${duration}s`; beam.style.setProperty('--beam-rotation', `${rotate}deg`);
beam.style.animationDelay = `-${Math.random() * 10}s`;
const duration = Math.random() * 7 + 8; // 8-15s
container.appendChild(beam); beam.style.animation = `spring-beam-pulse ${duration}s ease-in-out forwards`;
beam.style.animationDelay = `${Math.random() * 3}s`;
beam.addEventListener('animationend', () => {
beam.remove();
beamsActive--;
if (beamsActive === 0) {
spawnSunbeamGroup(container, count);
}
});
container.appendChild(beam);
}
} }
function createGrass(container) { function createGrass(container) {
@@ -121,71 +119,82 @@ function createGrass(container) {
container.appendChild(grassContainer); container.appendChild(grassContainer);
} }
// More grass: 1 blade every 3px (was 15px) grassContainer.innerHTML = '';
const bladeCount = window.innerWidth / 3;
let pathsBg = '';
let pathsFg = '';
const w = window.innerWidth;
const hSVG = 80;
// 1. Generate Straight Line HTML-Style Grass (converted to SVG Paths)
const bladeCount = w / 5; // Reduced from w/3
for (let i = 0; i < bladeCount; i++) { for (let i = 0; i < bladeCount; i++) {
const blade = document.createElement('div');
blade.classList.add('spring-grass');
const height = Math.random() * 40 + 20; // 20-60px height const height = Math.random() * 40 + 20; // 20-60px height
blade.style.height = `${height}px`; const x = i * 5 + Math.random() * 3;
blade.style.left = `${i * 3 + Math.random() * 2}px`;
const duration = Math.random() * 2 + 3;
blade.style.animationDuration = `${duration}s`;
blade.style.animationDelay = `-${Math.random() * 5}s`;
const hue = 100 + Math.random() * 40; const hue = 100 + Math.random() * 40;
blade.style.backgroundColor = `hsl(${hue}, 60%, 40%)`; const color = `hsl(${hue}, 60%, 40%)`;
grassContainer.appendChild(blade); const line = `<line x1="${x}" y1="${hSVG}" x2="${x}" y2="${hSVG - height}" stroke="${color}" stroke-width="2" />`;
// ~66% chance to be in background (1001), 33% foreground (1003)
if (Math.random() > 0.33) pathsBg += line; else pathsFg += line;
} }
// Add Ladybugs // 2. Generate Curved Earth-Day Style Grass
const bugs = ladybugCountConfig; for (let i = 0; i < 200; i++) { // Reduced from 400
for (let i = 0; i < bugs; i++) { const x = Math.random() * w;
createLadybug(grassContainer); const h = 20 + Math.random() * 50;
} const cY = hSVG - h;
} const bend = x + (Math.random() * 40 - 20);
const color = Math.random() > 0.5 ? '#4caf50' : '#45a049';
function createLadybug(container) { const width = 1 + Math.random() * 2;
const bug = document.createElement('div'); const path = `<path d="M ${x} ${hSVG} Q ${bend} ${cY+20} ${bend} ${cY}" stroke="${color}" stroke-width="${width}" fill="none"/>`;
bug.classList.add('spring-ladybug'); // ~66% chance to be in background (1001), 33% foreground (1003)
if (Math.random() > 0.33) pathsBg += path; else pathsFg += path;
const direction = Math.random() > 0.5 ? 'right' : 'left';
bug.classList.add(direction);
// Position lower (bottom of grass), but ensure visibility
const bottomOffset = direction === 'right' ? Math.random() * 5 + 6 : Math.random() * 5 + 2;
bug.style.bottom = `${bottomOffset}px`;
// Start position depends on direction
if (direction === 'right') {
bug.style.left = '-5%'; // Start off-screen left
} else {
bug.style.left = '105%'; // Start off-screen right
} }
const duration = Math.random() * 20 + 20; // Slow crawl // 3. Generate SVG Flowers
bug.style.animationDuration = `${duration}s`; const colors = ['#FF69B4', '#FFD700', '#87CEFA', '#FF4500', '#BA55D3', '#FFA500', '#FF1493', '#FFFFFF'];
bug.style.animationDelay = `-${Math.random() * 20}s`; const flowerCount = Math.floor(w / 40); // Reduced from w/30
for (let i = 0; i < flowerCount; i++) {
container.appendChild(bug); const x = 10 + Math.random() * (w - 20);
} const y = 10 + Math.random() * 40; // 10-50px from top of SVG
const col = colors[Math.floor(Math.random() * colors.length)];
let flower = '';
// Stem
flower += `<path d="M ${x} ${hSVG} Q ${x - 5 + Math.random() * 10} ${y+15} ${x} ${y}" stroke="#2e7d32" stroke-width="1.5" fill="none"/>`;
// Petals
const r = 2 + Math.random() * 1.5;
flower += `<circle cx="${x-r}" cy="${y-r}" r="${r}" fill="${col}"/>`;
flower += `<circle cx="${x+r}" cy="${y-r}" r="${r}" fill="${col}"/>`;
flower += `<circle cx="${x-r}" cy="${y+r}" r="${r}" fill="${col}"/>`;
flower += `<circle cx="${x+r}" cy="${y+r}" r="${r}" fill="${col}"/>`;
// Center
flower += `<circle cx="${x}" cy="${y}" r="${r*0.7}" fill="#FFF8DC"/>`;
function addRandomSpringObjects() { // ~66% chance to be in background (1001), 33% foreground (1003)
const container = document.querySelector('.spring-container'); if (Math.random() > 0.33) pathsBg += flower; else pathsFg += flower;
if (!container) return; }
if (enablePetals) { // Inject purely SVG based grass container
for (let i = 0; i < petalCount; i++) { grassContainer.innerHTML = `
createPetal(container); <div class="spring-meadow-layer" style="z-index: 1001;">
} <svg class="spring-meadow" viewBox="0 0 ${w} ${hSVG}" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
} <g class="spring-sway">
${pathsBg}
for (let i = 0; i < pollenCount; i++) { </g>
createPollen(container); </svg>
} </div>
<div class="spring-meadow-layer" style="z-index: 1003;">
<svg class="spring-meadow" viewBox="0 0 ${w} ${hSVG}" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
<g class="spring-sway" style="animation-delay: -2s;">
${pathsFg}
</g>
</svg>
</div>
`;
} }
function initSpringObjects() { function initSpringObjects() {
@@ -199,47 +208,254 @@ function initSpringObjects() {
createGrass(container); createGrass(container);
if (enablePetals) {
for (let i = 0; i < 15; i++) {
const petal = document.createElement('div');
petal.classList.add('spring-petal');
const type = Math.random() > 0.5 ? 'type1' : 'type2';
petal.classList.add(type);
const color = Math.random() > 0.7 ? 'darker' : 'lighter';
petal.classList.add(color);
const randomLeft = Math.random() * 100;
petal.style.left = `${randomLeft}%`;
const size = Math.random() * 0.5 + 0.5;
petal.style.transform = `scale(${size})`;
const duration = Math.random() * 5 + 8;
const swayDuration = Math.random() * 2 + 2;
if (enableDifferentDuration) {
petal.style.animationDuration = `${duration}s, ${swayDuration}s`;
}
petal.style.animationDelay = `-${Math.random() * 10}s, -${Math.random() * 3}s`;
container.appendChild(petal);
}
}
if (enableSunbeams) { if (enableSunbeams) {
// Initial sunbeams spawnSunbeamGroup(container, sunbeamCount);
for (let i = 0; i < sunbeamCount; i++) {
createSunbeam(container);
}
} }
} }
function initializeSpring() { function initializeSpring() {
if (!spring) return; if (!spring) {
console.warn('Spring is disabled.');
return;
}
initSpringObjects(); initSpringObjects();
toggleSpring(); toggleSpring();
const screenWidth = window.innerWidth; const container = document.querySelector('.spring-container');
if (randomSpring && (screenWidth > 768 || randomSpringMobile)) { if (container) {
addRandomSpringObjects(); if (randomSpring) {
// Add Pollen
for (let i = 0; i < pollenCount; i++) {
createPollen(container);
}
// Add Birds
for (let i = 0; i < birdCount; i++) {
setTimeout(() => createBird(container), Math.random() * 1000); // 0-1s desync
}
// Add Butterflies
for (let i = 0; i < butterflyCount; i++) {
setTimeout(() => createButterfly(container), Math.random() * 1000); // 0-1s desync
}
// Add Bees
for (let i = 0; i < beeCount; i++) {
setTimeout(() => createBee(container), Math.random() * 1000); // 0-1s desync
}
// Add Ladybugs
for (let i = 0; i < ladybugCount; i++) {
setTimeout(() => createLadybugGif(container), Math.random() * 1000); // 0-1s desync
}
}
} }
} }
function createBird(container) {
const wrapper = document.createElement('div');
wrapper.classList.add('spring-anim-wrapper');
wrapper.classList.add('spring-bird-wrapper');
const alignY = document.createElement('div');
alignY.classList.add('spring-align-y');
const mirror = document.createElement('div');
mirror.classList.add('spring-mirror-wrapper');
const bird = document.createElement('img');
bird.classList.add('spring-bird');
const randomSrc = birdImages[Math.floor(Math.random() * birdImages.length)];
bird.src = randomSrc;
const direction = Math.random() > 0.5 ? 'right' : 'left';
// MARK: BIRD SPEED (10-15s)
const duration = Math.random() * 5 + 10;
// MARK: BIRD HEIGHT RANGE (in vh)
const startY = Math.random() * 55 + 5; // Start 5-60vh
const endY = Math.random() * 55 + 5; // End 5-60vh
alignY.style.setProperty('--start-y', `${startY}vh`);
alignY.style.setProperty('--end-y', `${endY}vh`);
if (direction === 'right') {
wrapper.style.animation = `spring-fly-right-wrapper ${duration}s linear forwards`;
mirror.style.transform = 'scaleX(-1)';
} else {
wrapper.style.animation = `spring-fly-left-wrapper ${duration}s linear forwards`;
mirror.style.transform = 'scaleX(1)';
}
alignY.style.animation = `spring-vertical-drift ${duration}s linear forwards`;
wrapper.addEventListener('animationend', (e) => {
if (e.animationName.includes('fly-')) {
wrapper.remove();
createBird(container);
}
});
bird.style.animation = `spring-bob 2s ease-in-out infinite`;
mirror.appendChild(bird);
alignY.appendChild(mirror);
wrapper.appendChild(alignY);
container.appendChild(wrapper);
}
function createButterfly(container) {
const wrapper = document.createElement('div');
wrapper.classList.add('spring-anim-wrapper');
wrapper.classList.add('spring-butterfly-wrapper');
const alignY = document.createElement('div');
alignY.classList.add('spring-align-y');
const mirror = document.createElement('div');
mirror.classList.add('spring-mirror-wrapper');
const butterfly = document.createElement('img');
butterfly.classList.add('spring-butterfly');
const randomSrc = butterflyImages[Math.floor(Math.random() * butterflyImages.length)];
butterfly.src = randomSrc;
const duration = Math.random() * 15 + 25;
const direction = Math.random() > 0.5 ? 'right' : 'left';
if (direction === 'right') {
wrapper.style.animation = `spring-fly-right-wrapper ${duration}s linear forwards`;
mirror.style.transform = 'scaleX(1)';
} else {
wrapper.style.animation = `spring-fly-left-wrapper ${duration}s linear forwards`;
mirror.style.transform = 'scaleX(-1)';
}
wrapper.addEventListener('animationend', (e) => {
if (e.animationName.includes('fly-')) {
wrapper.remove();
createButterfly(container);
}
});
// MARK: BUTTERFLY FLUTTER RHYTHM
butterfly.style.animation = `spring-flutter 3s ease-in-out infinite`;
butterfly.style.animationDelay = `-${Math.random() * 3}s`;
// MARK: BUTTERFLY HEIGHT (in vh)
const top = Math.random() * 35 + 30; // 30-65vh
alignY.style.transform = `translateY(${top}vh)`;
mirror.appendChild(butterfly);
alignY.appendChild(mirror);
wrapper.appendChild(alignY);
container.appendChild(wrapper);
}
function createBee(container) {
const wrapper = document.createElement('div');
wrapper.classList.add('spring-anim-wrapper');
wrapper.classList.add('spring-bee-wrapper');
const alignY = document.createElement('div');
alignY.classList.add('spring-align-y');
const mirror = document.createElement('div');
mirror.classList.add('spring-mirror-wrapper');
const bee = document.createElement('img');
bee.classList.add('spring-bee');
bee.src = beeImage;
const duration = Math.random() * 10 + 15;
const direction = Math.random() > 0.5 ? 'right' : 'left';
if (direction === 'right') {
wrapper.style.animation = `spring-fly-right-wrapper ${duration}s linear forwards`;
mirror.style.transform = 'scaleX(1)';
} else {
wrapper.style.animation = `spring-fly-left-wrapper ${duration}s linear forwards`;
mirror.style.transform = 'scaleX(-1)';
}
wrapper.addEventListener('animationend', (e) => {
if (e.animationName.includes('fly-')) {
wrapper.remove();
createBee(container);
}
});
// MARK: BEE HEIGHT (in vh)
const top = Math.random() * 60 + 20; // 20-80vh
alignY.style.transform = `translateY(${top}vh)`;
mirror.appendChild(bee);
alignY.appendChild(mirror);
wrapper.appendChild(alignY);
container.appendChild(wrapper);
}
function createLadybugGif(container) {
const wrapper = document.createElement('div');
wrapper.classList.add('spring-anim-wrapper');
wrapper.classList.add('spring-ladybug-wrapper');
const alignY = document.createElement('div');
alignY.classList.add('spring-align-y');
const mirror = document.createElement('div');
mirror.classList.add('spring-mirror-wrapper');
const bug = document.createElement('img');
bug.classList.add('spring-ladybug-gif');
bug.src = ladybugImage;
const direction = Math.random() > 0.5 ? 'right' : 'left';
const duration = Math.random() * 20 + 30;
if (direction === 'right') {
wrapper.style.animation = `spring-walk-right ${duration}s linear forwards`;
mirror.style.transform = 'scaleX(1)';
} else {
wrapper.style.animation = `spring-walk-left ${duration}s linear forwards`;
mirror.style.transform = 'scaleX(-1)';
}
wrapper.addEventListener('animationend', (e) => {
if (e.animationName.includes('walk-')) {
wrapper.remove();
createLadybugGif(container);
}
});
bug.style.animation = `spring-crawl 2s ease-in-out infinite`;
// Target the Ladybug to walk on the ground visually (aligning properly with the CSS/SVG grass size)
alignY.style.transform = `translateY(calc(100vh - 5px - 30px))`;
mirror.appendChild(bug);
alignY.appendChild(mirror);
wrapper.appendChild(alignY);
container.appendChild(wrapper);
}
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
const handleResize = debounce(() => {
const container = document.querySelector('.spring-container');
if (container) {
createGrass(container);
}
}, 250);
window.addEventListener('resize', handleResize);
initializeSpring(); initializeSpring();

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -0,0 +1,39 @@
.storm-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: 1000;
overflow: hidden;
contain: layout paint;
}
.raindrop {
position: absolute;
width: 2px;
height: 40px;
background: linear-gradient(to bottom, rgba(200, 200, 255, 0), rgba(200, 200, 255, 0.7));
transform-origin: bottom;
}
@keyframes stormy-rain {
0% { transform: translateY(-30vh) translateX(0) rotate(20deg); opacity: 0; }
5% { opacity: 1; }
95% { opacity: 1; }
100% { transform: translateY(110vh) translateX(-40vh) rotate(20deg); opacity: 0; }
}
.lightning-flash {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: #fff;
opacity: 0;
pointer-events: none;
z-index: 999;
will-change: opacity;
}

View File

@@ -0,0 +1,99 @@
// 1. Read Configuration
const config = window.SeasonalsPluginConfig?.Storm || {};
const enabled = config.EnableStorm !== undefined ? config.EnableStorm : true;
const isMobile = window.innerWidth <= 768;
const elementCount = isMobile ? (config.RaindropCountMobile || 150) : (config.RaindropCount || 300);
const enableLightning = config.EnableLightning !== undefined ? config.EnableLightning : true;
const rainSpeed = config.RainSpeed || 1.0;
let msgPrinted = false;
// 2. Toggle Function
function toggleStorm() {
const container = document.querySelector('.storm-container');
if (!container) return;
const videoPlayer = document.querySelector('.videoPlayerContainer');
const trailerPlayer = document.querySelector('.youtubePlayerContainer');
const isDashboard = document.body.classList.contains('dashboardDocument');
const hasUserMenu = document.querySelector('#app-user-menu');
if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) {
container.style.display = 'none';
if (!msgPrinted) {
console.log('Storm hidden');
msgPrinted = true;
}
} else {
container.style.display = 'block';
if (msgPrinted) {
console.log('Storm visible');
msgPrinted = false;
}
}
}
// 3. MutationObserver
const observer = new MutationObserver(toggleStorm);
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true
});
// 4. Element Creation
function createElements() {
const container = document.querySelector('.storm-container') || document.createElement('div');
if (!document.querySelector('.storm-container')) {
container.className = 'storm-container';
container.setAttribute('aria-hidden', 'true');
document.body.appendChild(container);
}
for (let i = 0; i < elementCount; i++) {
const drop = document.createElement('div');
drop.className = 'raindrop';
drop.style.left = `${Math.random() * 140}vw`;
drop.style.top = `${-20 - Math.random() * 50}vh`;
const duration = (0.5 + Math.random() * 0.5) / (rainSpeed || 1);
drop.style.animation = `stormy-rain ${duration}s linear infinite`;
drop.style.animationDelay = `${Math.random() * 2}s`;
drop.style.opacity = Math.random() * 0.5 + 0.3;
container.appendChild(drop);
}
if (enableLightning) {
const flash = document.createElement('div');
flash.className = 'lightning-flash';
container.appendChild(flash);
function triggerFlash() {
const nextFlashDelay = 5000 + Math.random() * 10000;
setTimeout(() => {
flash.style.opacity = '0.8';
setTimeout(() => { flash.style.opacity = '0'; }, 50);
setTimeout(() => { flash.style.opacity = '0.5'; }, 100);
setTimeout(() => { flash.style.opacity = '0'; }, 150);
triggerFlash();
}, nextFlashDelay);
}
triggerFlash();
}
}
// 5. Initialization
function initializeStorm() {
if (!enabled) return;
createElements();
toggleStorm();
}
initializeStorm();

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;
} }
.summer-bubble { .summer-bubble {

View File

@@ -1,11 +1,11 @@
const config = window.SeasonalsPluginConfig?.Summer || {}; const config = window.SeasonalsPluginConfig?.Summer || {};
const summer = config.EnableSummer !== undefined ? config.EnableSummer : true; // enable/disable summer const summer = config.EnableSummer !== undefined ? config.EnableSummer : true; // Enable/disable summer theme
const bubbleCount = config.BubbleCount || 20; const bubbleCount = config.BubbleCount || 30; // Number of bubbles
const dustCount = config.DustCount || 50; const dustCount = config.DustCount || 50; // Number of dust particles
const randomSummer = config.EnableRandomSummer !== undefined ? config.EnableRandomSummer : true; // enable random objects const randomSummer = config.EnableRandomSummer !== undefined ? config.EnableRandomSummer : true; // Enable random generating objects
const randomSummerMobile = config.EnableRandomSummerMobile !== undefined ? config.EnableRandomSummerMobile : false; // enable random 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 animation duration const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // Randomize animation duration of bubbles and dust
let msgPrinted = false; let msgPrinted = false;
@@ -51,10 +51,12 @@ function createBubble(container, isDust = false) {
// Random size // Random size
if (!isDust) { if (!isDust) {
// MARK: BUBBLE SIZE
const size = Math.random() * 20 + 10; // 10-30px bubbles const size = Math.random() * 20 + 10; // 10-30px bubbles
bubble.style.width = `${size}px`; bubble.style.width = `${size}px`;
bubble.style.height = `${size}px`; bubble.style.height = `${size}px`;
} else { } else {
// MARK: DUST SIZE
const size = Math.random() * 3 + 1; // 1-4px dust const size = Math.random() * 3 + 1; // 1-4px dust
bubble.style.width = `${size}px`; bubble.style.width = `${size}px`;
bubble.style.height = `${size}px`; bubble.style.height = `${size}px`;
@@ -81,7 +83,7 @@ function addRandomSummerObjects() {
createBubble(container, false); createBubble(container, false);
} }
// Add some dust particles (more of them, they are subtle) // Add some dust particles
for (let i = 0; i < dustCount; i++) { for (let i = 0; i < dustCount; i++) {
createBubble(container, true); createBubble(container, true);
} }
@@ -110,10 +112,12 @@ function initSummerObjects() {
bubble.style.left = `${randomLeft}%`; bubble.style.left = `${randomLeft}%`;
if (!isDust) { if (!isDust) {
// MARK: BUBBLE SIZE
const size = Math.random() * 20 + 10; const size = Math.random() * 20 + 10;
bubble.style.width = `${size}px`; bubble.style.width = `${size}px`;
bubble.style.height = `${size}px`; bubble.style.height = `${size}px`;
} else { } else {
// MARK: DUST SIZE
const size = Math.random() * 3 + 1; const size = Math.random() * 3 + 1;
bubble.style.width = `${size}px`; bubble.style.width = `${size}px`;
bubble.style.height = `${size}px`; bubble.style.height = `${size}px`;

View File

@@ -248,6 +248,13 @@
<option value="spring">Spring</option> <option value="spring">Spring</option>
<option value="summer">Summer (Bubbles)</option> <option value="summer">Summer (Bubbles)</option>
<option value="carnival">Carnival (Confetti)</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="piday">Pi-Day</option>
<option value="pride">Pride</option>
<option value="rain">Rain</option>
<option value="storm">Storm (Epilepsy Warning!)</option>
<option value="custom">⚙ Custom (Local Files)</option> <option value="custom">⚙ Custom (Local Files)</option>
</select> </select>
@@ -333,6 +340,13 @@
spring: { css: 'spring.css', js: 'spring.js', container: 'spring-container' }, spring: { css: 'spring.css', js: 'spring.js', container: 'spring-container' },
summer: { css: 'summer.css', js: 'summer.js', container: 'summer-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' },
earthday: { css: 'earthday.css', js: 'earthday.js', container: 'earthday-container' },
eurovision: { css: 'eurovision.css', js: 'eurovision.js', container: 'eurovision-container' },
piday: { css: 'piday.css', js: 'piday.js', container: 'piday-container' },
pride: { css: 'pride.css', js: 'pride.js', container: 'pride-container' },
rain: { css: 'rain.css', js: 'rain.js', container: 'rain-container' },
storm: { css: 'storm.css', js: 'storm.js', container: 'storm-container' },
}; };
const select = document.getElementById('theme-select'); const select = document.getElementById('theme-select');
@@ -360,7 +374,9 @@
'.christmas-container', '.santa-container', '.autumn-container', '.christmas-container', '.santa-container', '.autumn-container',
'.christmas-container', '.santa-container', '.autumn-container', '.christmas-container', '.santa-container', '.autumn-container',
'.easter-container', '.resurrection-container', '.spring-container', '.easter-container', '.resurrection-container', '.spring-container',
'.summer-container', '.carnival-container' '.summer-container', '.carnival-container', '.cherryblossom-container',
'.earthday-container', '.eurovision-container', '.piday-container',
'.pride-container', '.rain-container', '.storm-container'
]; ];
knownContainers.forEach(sel => { knownContainers.forEach(sel => {
document.querySelectorAll(sel).forEach(el => { document.querySelectorAll(sel).forEach(el => {

View File

@@ -9,12 +9,20 @@
"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": "1.7.1.1", "version": "1.7.2.0",
"changelog": "- feat: add Pi Day, Pride, Rain, and Storm themes\n- fix: improve performance",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.7.2.0/Jellyfin.Plugin.Seasonals.zip",
"checksum": "34c8426c48bd7d470c3e8dc7f02f86da",
"timestamp": "2026-02-23T00:34:13Z"
},
{
"version": "1.7.1.5",
"changelog": "- feat: add summer, spring and carnival themes", "changelog": "- feat: add summer, spring and carnival themes",
"targetAbi": "10.11.0.0", "targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.7.1.1/Jellyfin.Plugin.Seasonals.zip", "sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.7.1.5/Jellyfin.Plugin.Seasonals.zip",
"checksum": "58a418e0070aab6f47b136fc305e2fc1", "checksum": "f6447d476189e69fb96fe1675c55a1a0",
"timestamp": "2026-02-19T18:00:57Z" "timestamp": "2026-02-21T14:28:30Z"
}, },
{ {
"version": "1.7.0.15", "version": "1.7.0.15",