Compare commits

..

25 Commits

Author SHA1 Message Date
CodeDevMLH
3d9a474aae Update manifest.json for release v2.0.0.1 [skip ci] 2026-02-27 03:27:03 +00:00
CodeDevMLH
db5baa1fd7 Bump version to 2.0.0.1
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 53s
2026-02-27 04:26:12 +01:00
CodeDevMLH
72ad4ee1a4 Add option to enable different symbol durations for spooky theme; fix casing in snowstorm config 2026-02-27 04:25:53 +01:00
CodeDevMLH
bb6c7796d5 Reduce z-index of matrix-container to improve stacking context and prevent overlap issues [skip ci] 2026-02-27 04:08:00 +01:00
CodeDevMLH
bd8088c52b Add checks for container presence in animation functions to prevent errors when elements are removed from the DOM [skip ci] 2026-02-27 04:07:29 +01:00
CodeDevMLH
3c1bd01373 Fix positioning of olympia-symbol and adjust ring CSS for improved alignment [skip ci] 2026-02-27 03:59:19 +01:00
CodeDevMLH
669ac6d3da Fix spider and mouse creation logic to ensure animations only continue if the container is present in the DOM 2026-02-27 03:58:34 +01:00
CodeDevMLH
73f9be91ef Refactor z-index values and optimize CSS properties for various seasonal styles; enhance performance with will-change property [skip ci] 2026-02-27 03:53:05 +01:00
CodeDevMLH
f14785c54a Enhance CSS animations and performance across seasonal styles; add will-change property for smoother transitions and optimize keyframes. [skip ci] 2026-02-27 03:42:23 +01:00
CodeDevMLH
296873f89e Fix rabbit animation transform origin and improve GIF reset logic
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 40s
2026-02-27 01:58:53 +01:00
CodeDevMLH
d6a9ff7176 Fix z-index for pride container and clean up comments in pride.js
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 42s
2026-02-27 01:35:25 +01:00
CodeDevMLH
ef15857533 Update manifest.json for release v2.0.0.0 [skip ci] 2026-02-27 00:20:16 +00:00
CodeDevMLH
19b21ba94f Bump version to 2.0.0.0
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 58s
2026-02-27 01:19:21 +01:00
CodeDevMLH
8f322fd6cf Refactor birthday, olympia, space, and sports animations; enhance symbol management and visual effects
- Updated birthday.js to improve balloon visuals and confetti effects, including new color schemes and enhanced pop animations.
- Removed legacy fallback logic in olympia.js and improved image error handling.
- Enhanced space.js with configurable counts for planets, astronauts, satellites, ISS, and rockets; improved shooting star animations and added random image swapping.
- Simplified sports.js by removing legacy emoji fallback logic and optimizing ball creation based on selected categories.
- Adjusted CSS styles across birthday, olympia, space, and sports for better visual consistency and performance.
[skip ci]
2026-02-27 01:18:08 +01:00
CodeDevMLH
bdc7d2e325 Refine options in seasonal themes: update epilepsy warning and simplify sports options 2026-02-27 01:17:54 +01:00
CodeDevMLH
8afe397c23 add new balloons 2026-02-27 01:17:41 +01:00
CodeDevMLH
30c29d440f Enhance seasonal themes: replace Pi-Day options with Matrix options and add new configuration settings
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 43s
2026-02-26 21:53:40 +01:00
CodeDevMLH
69adc64a44 Enhance CSS and JS injection: add cache-buster to prevent caching issues 2026-02-26 21:53:34 +01:00
CodeDevMLH
b0fae10aa1 Enhance seasonal themes: update Pi-Day to Matrix, add Underwater theme options, and refine descriptions for various settings 2026-02-26 21:53:29 +01:00
CodeDevMLH
cee4dae769 Enhance birthday feature: add garland decoration, improve balloon pop confetti effect, and refine animation dynamics 2026-02-26 21:53:24 +01:00
CodeDevMLH
f9aeeadccf Enhance Mario Day feature: adjust animation duration for Mario's running speed and refine coin positioning logic 2026-02-26 21:53:10 +01:00
CodeDevMLH
fc35fcd3c4 Enhance Olympic animation: add new ring types, implement 3D effects for rings and medals, and improve confetti generation with random shapes and animations 2026-02-26 21:52:55 +01:00
CodeDevMLH
6a83981e1d Enhance space animation: add nebula glow effect, shooting stars, and random space images for improved visual experience 2026-02-26 21:52:48 +01:00
CodeDevMLH
540d7f9baa Enhance sports feature: add asset management for various sports, improve animation effects, and refine confetti generation logic 2026-02-26 21:52:42 +01:00
CodeDevMLH
a162b30bcd Enhance underwater animation: add new creature types, improve movement animations, and implement light rays effect 2026-02-26 21:52:36 +01:00
57 changed files with 2448 additions and 1275 deletions

View File

@@ -32,7 +32,7 @@ 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(); Matrix = new MatrixOptions();
Eurovision = new EurovisionOptions(); Eurovision = new EurovisionOptions();
Storm = new StormOptions(); Storm = new StormOptions();
Pride = new PrideOptions(); Pride = new PrideOptions();
@@ -97,7 +97,7 @@ 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 MatrixOptions Matrix { get; set; }
public EurovisionOptions Eurovision { get; set; } public EurovisionOptions Eurovision { get; set; }
public StormOptions Storm { get; set; } public StormOptions Storm { get; set; }
public PrideOptions Pride { get; set; } public PrideOptions Pride { get; set; }
@@ -277,14 +277,15 @@ public class CherryBlossomOptions
public bool EnableDifferentDuration { get; set; } = true; public bool EnableDifferentDuration { get; set; } = true;
} }
public class PiDayOptions public class MatrixOptions
{ {
public int SymbolCount { get; set; } = 50; public int SymbolCount { get; set; } = 25;
public bool EnablePiDay { get; set; } = true; public bool EnableMatrix { get; set; } = true;
public bool EnableRandomPiDay { get; set; } = true; public bool EnableRandomMatrix { get; set; } = true;
public bool EnableRandomPiDayMobile { get; set; } = false; public bool EnableRandomMatrixMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true; public bool EnableDifferentDuration { get; set; } = true;
public bool EnablePiDayBackground { get; set; } = false; public bool EnableMatrixBackground { get; set; } = false;
public string MatrixChars { get; set; } = "0123456789";
} }
public class EurovisionOptions public class EurovisionOptions
@@ -374,6 +375,7 @@ public class SpookyOptions
{ {
public bool EnableSpooky { get; set; } = true; public bool EnableSpooky { get; set; } = true;
public int SymbolCount { get; set; } = 25; public int SymbolCount { get; set; } = 25;
public bool EnableDifferentDuration { get; set; } = true;
public bool EnableSpookySway { get; set; } = true; public bool EnableSpookySway { get; set; } = true;
public int SpookySize { get; set; } = 20; public int SpookySize { get; set; } = 20;
public int SpookyGlowSize { get; set; } = 2; public int SpookyGlowSize { get; set; } = 2;
@@ -381,11 +383,14 @@ public class SpookyOptions
public class SportsOptions public class SportsOptions
{ {
public int SymbolCount { get; set; } = 25; public int SymbolCount { get; set; } = 5;
public bool EnableSports { get; set; } = true; public bool EnableSports { get; set; } = true;
public bool EnableRandomSymbols { get; set; } = true; public bool EnableRandomSymbols { get; set; } = true;
public bool EnableRandomSymbolsMobile { get; set; } = false; public bool EnableRandomSymbolsMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true; public bool EnableDifferentDuration { get; set; } = true;
public string TurfColor { get; set; } = "#228b22";
public string SportsBalls { get; set; } = "football,basketball,tennis,volleyball";
public bool EnableTrophy { get; set; } = false;
} }
public class OlympiaOptions public class OlympiaOptions
@@ -399,7 +404,11 @@ public class OlympiaOptions
public class SpaceOptions public class SpaceOptions
{ {
public int SymbolCount { get; set; } = 25; public int PlanetCount { get; set; } = 12;
public int AstronautCount { get; set; } = 5;
public int SatelliteCount { get; set; } = 2;
public int IssCount { get; set; } = 1;
public int RocketCount { get; set; } = 1;
public bool EnableSpace { get; set; } = true; public bool EnableSpace { get; set; } = true;
public bool EnableRandomSymbols { get; set; } = true; public bool EnableRandomSymbols { get; set; } = true;
public bool EnableRandomSymbolsMobile { get; set; } = false; public bool EnableRandomSymbolsMobile { get; set; } = false;
@@ -413,13 +422,24 @@ public class UnderwaterOptions
public bool EnableRandomSymbols { get; set; } = true; public bool EnableRandomSymbols { get; set; } = true;
public bool EnableRandomSymbolsMobile { get; set; } = false; public bool EnableRandomSymbolsMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true; public bool EnableDifferentDuration { get; set; } = true;
public bool EnableLightRays { get; set; } = true;
public int SeaweedCount { get; set; } = 30;
public int CrabCount { get; set; } = 2;
public int StarfishCount { get; set; } = 2;
public int ShellCount { get; set; } = 2;
public int FishCount { get; set; } = 15;
public int SeahorseCount { get; set; } = 3;
public int JellyfishCount { get; set; } = 3;
public int TurtleCount { get; set; } = 1;
} }
public class BirthdayOptions public class BirthdayOptions
{ {
public int SymbolCount { get; set; } = 25; public int SymbolCount { get; set; } = 5;
public int ConfettiCount { get; set; } = 60;
public bool EnableBirthday { get; set; } = true; public bool EnableBirthday { get; set; } = true;
public bool EnableRandomSymbols { get; set; } = true; public bool EnableRandomSymbols { get; set; } = true;
public bool EnableRandomSymbolsMobile { get; set; } = false; public bool EnableRandomSymbolsMobile { get; set; } = false;
public bool EnableDifferentDuration { get; set; } = true; public bool EnableDifferentDuration { get; set; } = true;
public bool EnableGarland { get; set; } = true;
} }

View File

@@ -70,24 +70,23 @@
<option value="carnival">Carnival (Confetti)</option> <option value="carnival">Carnival (Confetti)</option>
<option value="cherryblossom">Cherry Blossom</option> <option value="cherryblossom">Cherry Blossom</option>
<option value="resurrection">Resurrection by Bioflash257</option> <option value="resurrection">Resurrection by Bioflash257</option>
<option value="championships" disabled>European/World Championships (not implemented yet. Please commit ideas/implementation 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="earthday">Earth Day (Growing Vines)</option>
<option value="eurovision">Eurovision (Dancing Notes)</option> <option value="eurovision">Eurovision (Dancing Notes)</option>
<option value="oscar">Oscar Awards (Glamour & Flashes)</option> <option value="oscar">Oscar Awards (Glamour & Flashes)</option>
<option value="piday">Pi-Day (Matrix Rain)</option> <option value="matrix">Matrix</option>
<option value="pride">Pride (Rainbow Border)</option> <option value="pride">Pride (Rainbow Border)</option>
<option value="rain">Rain (Pure Rain)</option> <option value="rain">Rain (Pure Rain)</option>
<option value="storm">Storm (Heavy Rain & Lightning (Epilepsy Warning!))</option> <option value="storm">Storm (Heavy Rain & Lightning (⚠️Epilepsy Warning⚠️))</option>
<option value="frost">Frost / Ice</option> <option value="frost">Frost / Ice</option>
<option value="filmnoir">Film-Noir (Classic B&W Cinema)</option> <option value="filmnoir">Film-Noir (Classic B&W Cinema)</option>
<option value="marioday">Mario Day (March 10)</option> <option value="marioday">Mario Day (March 10)</option>
<option value="starwars">Star Wars Day (May 4th)</option> <option value="starwars">Star Wars Day (May 4th)</option>
<option value="oktoberfest">Oktoberfest</option> <option value="oktoberfest">Oktoberfest</option>
<option value="friday13">Friday the 13th</option> <option value="friday13">Friday the 13th</option>
<option value="eid">Eid al-Fitr</option> <option value="eidalfitr">Eid al-Fitr (Sugar Feast)</option>
<option value="spooky">Spooky</option> <option value="spooky">Spooky</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>
</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>
@@ -123,7 +122,7 @@
<!-- Advanced Tab --> <!-- Advanced Tab -->
<div id="seasonals-advanced" class="seasonals-tab-content" style="display: none;"> <div id="seasonals-advanced" class="seasonals-tab-content" style="display: none;">
<h2 class="sectionTitle">Configure specific settings for each seasonal theme</h2> <h2 class="sectionTitle">Configure specific settings for each seasonal theme</h2>
<!-- <p>Configure specific settings for each seasonal theme below.</p> --> <!-- <p>All symbol count settings add this number in addition to the standard 12 symbols (if additional symbols is enabled).</p> -->
<p>All symbol count settings add this number in addition to the standard 12 symbols (if additional symbols is enabled).</p> <p>All symbol count settings add this number in addition to the standard 12 symbols (if additional symbols is enabled).</p>
<details> <details>
<summary>Autumn</summary> <summary>Autumn</summary>
@@ -151,7 +150,7 @@
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="AutumnLeafCount">Leaf Count</label> <label class="inputLabel" for="AutumnLeafCount">Leaf Count</label>
<input is="emby-input" type="number" id="AutumnLeafCount" name="AutumnLeafCount" /> <input is="emby-input" type="number" id="AutumnLeafCount" name="AutumnLeafCount" />
<div class="fieldDescription">Number of additional leaves displayed on screen (if enabled)</div> <div class="fieldDescription">Number of additional leaves displayed (if enabled)</div>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
@@ -371,7 +370,7 @@
<hr style="max-width: 800px; margin: 1em 0;"> <hr style="max-width: 800px; margin: 1em 0;">
<details> <details>
<summary>Spooky Theme</summary> <summary>Spooky</summary>
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableSpooky" name="EnableSpooky" type="checkbox" is="emby-checkbox" /> <input id="EnableSpooky" name="EnableSpooky" type="checkbox" is="emby-checkbox" />
@@ -389,6 +388,12 @@
<input is="emby-input" type="number" id="SpookySize" name="SpookySize" /> <input is="emby-input" type="number" id="SpookySize" name="SpookySize" />
<div class="fieldDescription">Size of the floating symbols in pixels (default 30).</div> <div class="fieldDescription">Size of the floating symbols in pixels (default 30).</div>
</div> </div>
<div class="checkboxContainer">
<label class="emby-checkbox-label">
<input id="EnableDifferentDurationSpooky" name="EnableDifferentDurationSpooky" type="checkbox" is="emby-checkbox" />
<span>Enable Different Symbol Durations</span>
</label>
</div>
<div class="checkboxContainer"> <div class="checkboxContainer">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableSpookySway" name="EnableSpookySway" type="checkbox" is="emby-checkbox" /> <input id="EnableSpookySway" name="EnableSpookySway" type="checkbox" is="emby-checkbox" />
@@ -403,6 +408,47 @@
</details> </details>
<hr style="max-width: 800px; margin: 1em 0;"> <hr style="max-width: 800px; margin: 1em 0;">
<details>
<summary>Underwater</summary>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableUnderwater" name="EnableUnderwater" type="checkbox" is="emby-checkbox" />
<span>Enable Underwater Seasonal</span>
</label>
<div class="fieldDescription">Enable the Underwater theme (fishes, bubbles, seaweed).</div>
</div>
<div class="checkboxContainer">
<label class="emby-checkbox-label">
<input id="EnableUnderwaterLightRays" name="EnableUnderwaterLightRays" type="checkbox" is="emby-checkbox" />
<span>Enable Light Rays (God Rays)</span>
</label>
</div>
<div class="inputContainer">
<label class="inputLabel" for="UnderwaterSymbolCount">Symbol Count</label>
<input is="emby-input" type="number" id="UnderwaterSymbolCount" name="UnderwaterSymbolCount" />
<div class="fieldDescription">Number of sea creatures.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomUnderwater" name="EnableRandomUnderwater" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Symbols</span>
</label>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomUnderwaterMobile" name="EnableRandomUnderwaterMobile" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Symbols on Mobile</span>
</label>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableDifferentDurationUnderwater" name="EnableDifferentDurationUnderwater" type="checkbox" is="emby-checkbox" />
<span>Enable Different Duration</span>
</label>
</div>
</details>
<hr style="max-width: 800px; margin: 1em 0;">
<details> <details>
<summary>Hearts</summary> <summary>Hearts</summary>
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
@@ -894,6 +940,72 @@
<span>Enable Different Duration</span> <span>Enable Different Duration</span>
</label> </label>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableTrophy" name="EnableTrophy" type="checkbox" is="emby-checkbox" />
<span>Enable Trophy</span>
</label>
<div class="fieldDescription">Enable the flying trophy animation.</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="TurfColor">Turf Color</label>
<input is="emby-input" type="color" id="TurfColor" name="TurfColor" value="#228b22" />
<div class="fieldDescription">Color for the grass/turf overlay at the bottom.</div>
</div>
<div class="inputContainer">
<label class="inputLabel">Enabled Sports Balls</label>
<div class="fieldDescription" style="margin-bottom: 8px;">Select which sports balls should be featured.</div>
<div style="display: flex; flex-wrap: wrap; gap: 15px;">
<label class="emby-checkbox-label">
<input type="checkbox" is="emby-checkbox" class="sport-ball-cb" value="badminton" />
<span>Badminton</span>
</label>
<label class="emby-checkbox-label">
<input type="checkbox" is="emby-checkbox" class="sport-ball-cb" value="baseball" />
<span>Baseball</span>
</label>
<label class="emby-checkbox-label">
<input type="checkbox" is="emby-checkbox" class="sport-ball-cb" value="basketball" />
<span>Basketball</span>
</label>
<label class="emby-checkbox-label">
<input type="checkbox" is="emby-checkbox" class="sport-ball-cb" value="billiard" />
<span>Billiard</span>
</label>
<label class="emby-checkbox-label">
<input type="checkbox" is="emby-checkbox" class="sport-ball-cb" value="bowling" />
<span>Bowling</span>
</label>
<label class="emby-checkbox-label">
<input type="checkbox" is="emby-checkbox" class="sport-ball-cb" value="football" />
<span>Football/Soccer</span>
</label>
<label class="emby-checkbox-label">
<input type="checkbox" is="emby-checkbox" class="sport-ball-cb" value="golf" />
<span>Golf</span>
</label>
<label class="emby-checkbox-label">
<input type="checkbox" is="emby-checkbox" class="sport-ball-cb" value="rugby" />
<span>Rugby</span>
</label>
<label class="emby-checkbox-label">
<input type="checkbox" is="emby-checkbox" class="sport-ball-cb" value="table_tennis" />
<span>Table Tennis</span>
</label>
<label class="emby-checkbox-label">
<input type="checkbox" is="emby-checkbox" class="sport-ball-cb" value="tennis" />
<span>Tennis</span>
</label>
<label class="emby-checkbox-label">
<input type="checkbox" is="emby-checkbox" class="sport-ball-cb" value="volleyball" />
<span>Volleyball</span>
</label>
<label class="emby-checkbox-label">
<input type="checkbox" is="emby-checkbox" class="sport-ball-cb" value="waterball" />
<span>Waterpolo</span>
</label>
</div>
</div>
</details> </details>
<hr style="max-width: 800px; margin: 1em 0;"> <hr style="max-width: 800px; margin: 1em 0;">
@@ -954,9 +1066,29 @@
</label> </label>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="SpaceSymbolCount">Symbol Count</label> <label class="inputLabel" for="PlanetCount">Planet Count</label>
<input is="emby-input" type="number" id="SpaceSymbolCount" name="SpaceSymbolCount" /> <input is="emby-input" type="number" id="PlanetCount" name="PlanetCount" />
<div class="fieldDescription">Number of additional symbols displayed (if enabled).</div> <div class="fieldDescription">Number of planets displayed (if enabled).</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="AstronautCount">Astronaut Count</label>
<input is="emby-input" type="number" id="AstronautCount" name="AstronautCount" />
<div class="fieldDescription">Number of astronauts displayed (if enabled).</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="SatelliteCount">Satellite Count</label>
<input is="emby-input" type="number" id="SatelliteCount" name="SatelliteCount" />
<div class="fieldDescription">Number of satellites displayed (if enabled).</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="IssCount">ISS Count</label>
<input is="emby-input" type="number" id="IssCount" name="IssCount" />
<div class="fieldDescription">Number of ISS symbols displayed (if enabled).</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="RocketCount">Rocket Count</label>
<input is="emby-input" type="number" id="RocketCount" name="RocketCount" />
<div class="fieldDescription">Number of rockets displayed (if enabled).</div>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
@@ -976,6 +1108,13 @@
</label> </label>
<div class="fieldDescription">Enable the underwater theme effects in general (e.g. for automation).</div> <div class="fieldDescription">Enable the underwater theme effects in general (e.g. for automation).</div>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableUnderwaterLightRays" name="EnableUnderwaterLightRays" type="checkbox" is="emby-checkbox" />
<span>Enable Light Rays</span>
</label>
<div class="fieldDescription">Enable god rays from the top.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableRandomSymbolsUnderwater" name="EnableRandomSymbolsUnderwater" type="checkbox" is="emby-checkbox" /> <input id="EnableRandomSymbolsUnderwater" name="EnableRandomSymbolsUnderwater" type="checkbox" is="emby-checkbox" />
@@ -989,9 +1128,38 @@
</label> </label>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="UnderwaterSymbolCount">Symbol Count</label> <label class="inputLabel" for="UnderwaterSeaweedCount">Seaweed Count</label>
<input is="emby-input" type="number" id="UnderwaterSymbolCount" name="UnderwaterSymbolCount" /> <input is="emby-input" type="number" id="UnderwaterSeaweedCount" name="UnderwaterSeaweedCount" />
<div class="fieldDescription">Number of additional symbols displayed (if enabled).</div> <div class="fieldDescription">Amount of dynamic seaweed (GIFs + Emojis) at the bottom.</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="UnderwaterFishCount">Fish Count</label>
<input is="emby-input" type="number" id="UnderwaterFishCount" name="UnderwaterFishCount" />
<div class="fieldDescription">Number of swimming fish.</div>
</div>
<div class="inputContainer">
<label class="inputLabel" for="UnderwaterSeahorseCount">Seahorse Count</label>
<input is="emby-input" type="number" id="UnderwaterSeahorseCount" name="UnderwaterSeahorseCount" />
</div>
<div class="inputContainer">
<label class="inputLabel" for="UnderwaterJellyfishCount">Jellyfish Count</label>
<input is="emby-input" type="number" id="UnderwaterJellyfishCount" name="UnderwaterJellyfishCount" />
</div>
<div class="inputContainer">
<label class="inputLabel" for="UnderwaterTurtleCount">Turtle Count</label>
<input is="emby-input" type="number" id="UnderwaterTurtleCount" name="UnderwaterTurtleCount" />
</div>
<div class="inputContainer">
<label class="inputLabel" for="UnderwaterCrabCount">Crab Count</label>
<input is="emby-input" type="number" id="UnderwaterCrabCount" name="UnderwaterCrabCount" />
</div>
<div class="inputContainer">
<label class="inputLabel" for="UnderwaterStarfishCount">Starfish Count</label>
<input is="emby-input" type="number" id="UnderwaterStarfishCount" name="UnderwaterStarfishCount" />
</div>
<div class="inputContainer">
<label class="inputLabel" for="UnderwaterShellCount">Shell Count</label>
<input is="emby-input" type="number" id="UnderwaterShellCount" name="UnderwaterShellCount" />
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
@@ -1011,6 +1179,13 @@
</label> </label>
<div class="fieldDescription">Enable the birthday theme effects in general (e.g. for automation).</div> <div class="fieldDescription">Enable the birthday theme effects in general (e.g. for automation).</div>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableGarland" name="EnableGarland" type="checkbox" is="emby-checkbox" />
<span>Enable Garland</span>
</label>
<div class="fieldDescription">Enable dropping confetti / popping a garland at the top.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableRandomSymbolsBirthday" name="EnableRandomSymbolsBirthday" type="checkbox" is="emby-checkbox" /> <input id="EnableRandomSymbolsBirthday" name="EnableRandomSymbolsBirthday" type="checkbox" is="emby-checkbox" />
@@ -1028,6 +1203,11 @@
<input is="emby-input" type="number" id="BirthdaySymbolCount" name="BirthdaySymbolCount" /> <input is="emby-input" type="number" id="BirthdaySymbolCount" name="BirthdaySymbolCount" />
<div class="fieldDescription">Number of additional symbols displayed (if enabled).</div> <div class="fieldDescription">Number of additional symbols displayed (if enabled).</div>
</div> </div>
<div class="inputContainer">
<label class="inputLabel" for="BirthdayConfettiCount">Confetti Count</label>
<input is="emby-input" type="number" id="BirthdayConfettiCount" name="BirthdayConfettiCount" />
<div class="fieldDescription">Number of confetti pieces created when a balloon bursts.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableDifferentDurationBirthday" name="EnableDifferentDurationBirthday" type="checkbox" is="emby-checkbox" /> <input id="EnableDifferentDurationBirthday" name="EnableDifferentDurationBirthday" type="checkbox" is="emby-checkbox" />
@@ -1093,46 +1273,51 @@
<hr style="max-width: 800px; margin: 1em 0;"> <hr style="max-width: 800px; margin: 1em 0;">
<details> <details>
<summary>Pi-Day</summary> <summary>Matrix</summary>
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnablePiDay" name="EnablePiDay" type="checkbox" is="emby-checkbox" /> <input id="EnableMatrix" name="EnableMatrix" type="checkbox" is="emby-checkbox" />
<span>Enable Pi-Day Seasonal</span> <span>Enable Matrix Seasonal</span>
</label> </label>
<div class="fieldDescription">Enable the Pi-Day theme in general (e.g. for automation).</div> <div class="fieldDescription">Enable the Matrix theme in general (e.g. for automation).</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="EnableRandomPiDay" name="EnableRandomPiDay" type="checkbox" is="emby-checkbox" /> <input id="EnableRandomMatrix" name="EnableRandomMatrix" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Pi Symbols</span> <span>Enable Additional Random Matrix Symbols</span>
</label> </label>
<div class="fieldDescription">Displays additional digital rain elements.</div> <div class="fieldDescription">Displays additional digital rain elements.</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="EnableRandomPiDayMobile" name="EnableRandomPiDayMobile" type="checkbox" is="emby-checkbox" /> <input id="EnableRandomMatrixMobile" name="EnableRandomMatrixMobile" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Pi Symbols on Mobile</span> <span>Enable Additional Random Matrix Symbols on Mobile</span>
</label> </label>
<div class="fieldDescription">Displays additional digital rain elements on mobile devices. Warning: High values may affect performance.</div> <div class="fieldDescription">Displays additional digital rain elements on mobile devices. Warning: High values may affect performance.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="PiDaySymbolCount">Symbol Count</label> <label class="inputLabel" for="MatrixSymbolCount">Symbol Count</label>
<input is="emby-input" type="number" id="PiDaySymbolCount" name="PiDaySymbolCount" /> <input is="emby-input" type="number" id="MatrixSymbolCount" name="MatrixSymbolCount" />
<div class="fieldDescription">Number of additional digital rain columns (if enabled).</div> <div class="fieldDescription">Number of additional digital rain columns (if enabled).</div>
</div> </div>
<div class="inputContainer">
<label class="inputLabel" for="MatrixChars">Raining Characters</label>
<input is="emby-input" type="text" id="MatrixChars" name="MatrixChars" />
<div class="fieldDescription">Characters to use for the rain effect (default is 0-9).</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableDifferentDurationPiDay" name="EnableDifferentDurationPiDay" type="checkbox" is="emby-checkbox" /> <input id="EnableDifferentDurationMatrix" name="EnableDifferentDurationMatrix" type="checkbox" is="emby-checkbox" />
<span>Enable Different Falling Speed</span> <span>Enable Different Falling Speed</span>
</label> </label>
<div class="fieldDescription">Randomize the digital rain falling speed.</div> <div class="fieldDescription">Randomize the digital rain falling speed.</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="EnablePiDayBackground" name="EnablePiDayBackground" type="checkbox" is="emby-checkbox" /> <input id="EnableMatrixBackground" name="EnableMatrixBackground" type="checkbox" is="emby-checkbox" />
<span>Background Mode (Behind UI)</span> <span>Background Mode (Behind UI)</span>
</label> </label>
<div class="fieldDescription">Displays the Pi-Day animation behind library tiles and content.</div> <div class="fieldDescription">Displays the Matrix animation behind library tiles and content.</div>
</div> </div>
</details> </details>
<hr style="max-width: 800px; margin: 1em 0;"> <hr style="max-width: 800px; margin: 1em 0;">
@@ -1200,6 +1385,7 @@
<details> <details>
<summary>Storm</summary> <summary>Storm</summary>
<div><p>⚠️ Epilepsy warning ⚠️</p></div>
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableStorm" name="EnableStorm" type="checkbox" is="emby-checkbox" /> <input id="EnableStorm" name="EnableStorm" type="checkbox" is="emby-checkbox" />
@@ -1310,7 +1496,7 @@
<hr style="max-width: 800px; margin: 1em 0;"> <hr style="max-width: 800px; margin: 1em 0;">
<details> <details>
<summary>Zuckerfest (Eid)</summary> <summary>Eid al-Fitr (Sugar Feast)</summary>
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableEid" name="EnableEid" type="checkbox" is="emby-checkbox" /> <input id="EnableEid" name="EnableEid" type="checkbox" is="emby-checkbox" />
@@ -1319,7 +1505,6 @@
</div> </div>
</details> </details>
</div> </div>
<div style="background-color: rgba(255, 255, 255, 0.05); border-left: 4px solid #00a4dc; border-radius: 4px; padding: 1em 1.5em; margin: 1.5em 0; display: flex; align-items: center; gap: 1em;"> <div style="background-color: rgba(255, 255, 255, 0.05); border-left: 4px solid #00a4dc; border-radius: 4px; padding: 1em 1.5em; margin: 1.5em 0; display: flex; align-items: center; gap: 1em;">
<i class="material-icons" style="color: #00a4dc; font-size: 24px;">info</i> <i class="material-icons" style="color: #00a4dc; font-size: 24px;">info</i>
<div> <div>
@@ -1502,7 +1687,7 @@
' <option value="cherryblossom">Cherry Blossom</option>' + ' <option value="cherryblossom">Cherry Blossom</option>' +
' <option value="earthday">Earth Day</option>' + ' <option value="earthday">Earth Day</option>' +
' <option value="eurovision">Eurovision</option>' + ' <option value="eurovision">Eurovision</option>' +
' <option value="piday">Pi-Day</option>' + ' <option value="matrix">Matrix</option>' +
' <option value="pride">Pride</option>' + ' <option value="pride">Pride</option>' +
' <option value="rain">Rain</option>' + ' <option value="rain">Rain</option>' +
' <option value="storm">Storm (Epilepsy Warning!)</option>' + ' <option value="storm">Storm (Epilepsy Warning!)</option>' +
@@ -1514,7 +1699,8 @@
' <option value="starwars">Star Wars Day</option>' + ' <option value="starwars">Star Wars Day</option>' +
' <option value="oktoberfest">Oktoberfest</option>' + ' <option value="oktoberfest">Oktoberfest</option>' +
' <option value="friday13">Friday the 13th</option>' + ' <option value="friday13">Friday the 13th</option>' +
' <option value="eid">Eid al-Fitr</option>' + ' <option value="eidalfitr">Eid al-Fitr</option>' +
' <option value="spooky">Spooky</option>' +
' <option value="legacyhalloween">Legacy Halloween</option>' + ' <option value="legacyhalloween">Legacy Halloween</option>' +
' </select>' + ' </select>' +
' </div>' + ' </div>' +
@@ -1599,6 +1785,7 @@
// Advanced Config // Advanced Config
// Autumn // Autumn
document.querySelector('#EnableAutumn').checked = config.Autumn.EnableAutumn; document.querySelector('#EnableAutumn').checked = config.Autumn.EnableAutumn;
document.querySelector('#AutumnLeafCount').value = config.Autumn.LeafCount; document.querySelector('#AutumnLeafCount').value = config.Autumn.LeafCount;
document.querySelector('#EnableRandomLeaves').checked = config.Autumn.EnableRandomLeaves; document.querySelector('#EnableRandomLeaves').checked = config.Autumn.EnableRandomLeaves;
@@ -1606,113 +1793,49 @@
document.querySelector('#EnableDifferentDurationAutumn').checked = config.Autumn.EnableDifferentDuration; document.querySelector('#EnableDifferentDurationAutumn').checked = config.Autumn.EnableDifferentDuration;
document.querySelector('#EnableRotation').checked = config.Autumn.EnableRotation; document.querySelector('#EnableRotation').checked = config.Autumn.EnableRotation;
// Snowflakes
document.querySelector('#SnowflakesCount').value = config.Snowflakes.SnowflakeCount;
document.querySelector('#EnableSnowflakes').checked = config.Snowflakes.EnableSnowflakes;
document.querySelector('#EnableRandomSnowflakes').checked = config.Snowflakes.EnableRandomSnowflakes;
document.querySelector('#EnableRandomSnowflakesMobile').checked = config.Snowflakes.EnableRandomSnowflakesMobile;
document.querySelector('#EnableColoredSnowflakes').checked = config.Snowflakes.EnableColoredSnowflakes;
document.querySelector('#EnableDifferentDurationSnowflakes').checked = config.Snowflakes.EnableDifferentDuration;
// Snowfall
document.querySelector('#EnableSnowfall').checked = config.Snowfall.EnableSnowfall;
document.querySelector('#SnowfallCount').value = config.Snowfall.SnowflakesCount;
document.querySelector('#SnowfallCountMobile').value = config.Snowfall.SnowflakesCountMobile;
document.querySelector('#SnowfallSpeed').value = config.Snowfall.Speed;
// Snowstorm
document.querySelector('#EnableSnowstorm').checked = config.Snowstorm.EnableSnowstorm;
document.querySelector('#SnowstormCount').value = config.Snowstorm.SnowflakesCount;
document.querySelector('#SnowstormCountMobile').value = config.Snowstorm.SnowflakesCountMobile;
document.querySelector('#SnowstormSpeed').value = config.Snowstorm.Speed;
document.querySelector('#SnowstormHorizontalWind').value = config.Snowstorm.HorizontalWind;
document.querySelector('#SnowstormVerticalVariation').value = config.Snowstorm.VerticalVariation;
// Fireworks
document.querySelector('#EnableFireworks').checked = config.Fireworks.EnableFireworks;
document.querySelector('#FireworksParticles').value = config.Fireworks.ParticleCount;
document.querySelector('#FireworksInterval').value = config.Fireworks.LaunchInterval;
document.querySelector('#ScrollFireworks').checked = config.Fireworks.ScrollFireworks;
document.querySelector('#MinFireworks').value = config.Fireworks.MinFireworks;
document.querySelector('#MaxFireworks').value = config.Fireworks.MaxFireworks;
// Eid
document.querySelector('#EidSymbolCount').value = config.Eid.SymbolCount || 25;
// Spooky Theme
document.querySelector('#SpookyCount').value = config.Spooky.SymbolCount || 25;
// Sports
document.querySelector('#EnableSports').checked = config.Sports.EnableSports || false;
document.querySelector('#EnableRandomSymbolsSports').checked = config.Sports.EnableRandomSymbols || false;
document.querySelector('#EnableRandomSymbolsMobileSports').checked = config.Sports.EnableRandomSymbolsMobile || false;
document.querySelector('#EnableDifferentDurationSports').checked = config.Sports.EnableDifferentDuration || false;
document.querySelector('#SportsSymbolCount').value = config.Sports.SymbolCount || 25;
// Olympia
document.querySelector('#EnableOlympia').checked = config.Olympia.EnableOlympia || false;
document.querySelector('#EnableRandomSymbolsOlympia').checked = config.Olympia.EnableRandomSymbols || false;
document.querySelector('#EnableRandomSymbolsMobileOlympia').checked = config.Olympia.EnableRandomSymbolsMobile || false;
document.querySelector('#EnableDifferentDurationOlympia').checked = config.Olympia.EnableDifferentDuration || false;
document.querySelector('#OlympiaSymbolCount').value = config.Olympia.SymbolCount || 25;
// Space
document.querySelector('#EnableSpace').checked = config.Space.EnableSpace || false;
document.querySelector('#EnableRandomSymbolsSpace').checked = config.Space.EnableRandomSymbols || false;
document.querySelector('#EnableRandomSymbolsMobileSpace').checked = config.Space.EnableRandomSymbolsMobile || false;
document.querySelector('#EnableDifferentDurationSpace').checked = config.Space.EnableDifferentDuration || false;
document.querySelector('#SpaceSymbolCount').value = config.Space.SymbolCount || 25;
// Underwater
document.querySelector('#EnableUnderwater').checked = config.Underwater.EnableUnderwater || false;
document.querySelector('#EnableRandomSymbolsUnderwater').checked = config.Underwater.EnableRandomSymbols || false;
document.querySelector('#EnableRandomSymbolsMobileUnderwater').checked = config.Underwater.EnableRandomSymbolsMobile || false;
document.querySelector('#EnableDifferentDurationUnderwater').checked = config.Underwater.EnableDifferentDuration || false;
document.querySelector('#UnderwaterSymbolCount').value = config.Underwater.SymbolCount || 15;
// Birthday // Birthday
document.querySelector('#EnableBirthday').checked = config.Birthday.EnableBirthday || false;
document.querySelector('#EnableRandomSymbolsBirthday').checked = config.Birthday.EnableRandomSymbols || false; document.querySelector('#EnableBirthday').checked = config.Birthday.EnableBirthday !== false;
document.querySelector('#EnableRandomSymbolsMobileBirthday').checked = config.Birthday.EnableRandomSymbolsMobile || false; document.querySelector('#EnableGarland').checked = config.Birthday.EnableGarland !== false;
document.querySelector('#EnableDifferentDurationBirthday').checked = config.Birthday.EnableDifferentDuration || false; document.querySelector('#EnableRandomSymbolsBirthday').checked = config.Birthday.EnableRandomSymbols !== false;
document.querySelector('#EnableRandomSymbolsMobileBirthday').checked = config.Birthday.EnableRandomSymbolsMobile === true;
document.querySelector('#EnableDifferentDurationBirthday').checked = config.Birthday.EnableDifferentDuration !== false;
document.querySelector('#BirthdaySymbolCount').value = config.Birthday.SymbolCount || 25; document.querySelector('#BirthdaySymbolCount').value = config.Birthday.SymbolCount || 25;
document.querySelector('#BirthdayConfettiCount').value = config.Birthday.ConfettiCount || 60;
config.Birthday = config.Birthday || {};
// Halloween // Carnival
document.querySelector('#EnableHalloween').checked = config.Halloween.EnableHalloween;
document.querySelector('#HalloweenCount').value = config.Halloween.SymbolCount;
document.querySelector('#EnableRandomHalloween').checked = config.Halloween.EnableRandomSymbols;
document.querySelector('#EnableRandomHalloweenMobile').checked = config.Halloween.EnableRandomSymbolsMobile;
document.querySelector('#EnableDifferentDurationHalloween').checked = config.Halloween.EnableDifferentDuration;
document.querySelector('#EnableSpiders').checked = config.Halloween.EnableSpiders !== undefined ? config.Halloween.EnableSpiders : true;
document.querySelector('#EnableMice').checked = config.Halloween.EnableMice !== undefined ? config.Halloween.EnableMice : true;
// Hearts document.querySelector('#EnableCarnival').checked = config.Carnival.EnableCarnival;
document.querySelector('#EnableHearts').checked = config.Hearts.EnableHearts; document.querySelector('#EnableCarnivalSway').checked = config.Carnival.EnableCarnivalSway !== undefined ? config.Carnival.EnableCarnivalSway : true;
document.querySelector('#HeartsCount').value = config.Hearts.SymbolCount; document.querySelector('#CarnivalObjectCount').value = config.Carnival.ObjectCount;
document.querySelector('#EnableRandomHearts').checked = config.Hearts.EnableRandomSymbols; document.querySelector('#EnableRandomCarnival').checked = config.Carnival.EnableRandomCarnival;
document.querySelector('#EnableRandomHeartsMobile').checked = config.Hearts.EnableRandomSymbolsMobile; document.querySelector('#EnableRandomCarnivalMobile').checked = config.Carnival.EnableRandomCarnivalMobile;
document.querySelector('#EnableDifferentDurationHearts').checked = config.Hearts.EnableDifferentDuration; document.querySelector('#EnableDifferentDurationCarnival').checked = config.Carnival.EnableDifferentDuration;
// CherryBlossom
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;
// Christmas // Christmas
document.querySelector('#EnableChristmas').checked = config.Christmas.EnableChristmas; document.querySelector('#EnableChristmas').checked = config.Christmas.EnableChristmas;
document.querySelector('#ChristmasCount').value = config.Christmas.SymbolCount; document.querySelector('#ChristmasCount').value = config.Christmas.SymbolCount;
document.querySelector('#EnableRandomChristmas').checked = config.Christmas.EnableRandomChristmas; document.querySelector('#EnableRandomChristmas').checked = config.Christmas.EnableRandomChristmas;
document.querySelector('#EnableRandomChristmasMobile').checked = config.Christmas.EnableRandomChristmasMobile; document.querySelector('#EnableRandomChristmasMobile').checked = config.Christmas.EnableRandomChristmasMobile;
document.querySelector('#EnableDifferentDurationChristmas').checked = config.Christmas.EnableDifferentDuration; document.querySelector('#EnableDifferentDurationChristmas').checked = config.Christmas.EnableDifferentDuration;
// Santa // EarthDay
document.querySelector('#EnableSanta').checked = config.Santa.EnableSanta;
document.querySelector('#SantaSnowflakes').value = config.Santa.SnowflakesCount; document.querySelector('#EnableEarthDay').checked = config.EarthDay.EnableEarthDay;
document.querySelector('#SantaSnowflakesMobile').value = config.Santa.SnowflakesCountMobile; document.querySelector('#EarthDayVineCount').value = config.EarthDay.VineCount;
document.querySelector('#SantaSpeed').value = config.Santa.SantaSpeed;
document.querySelector('#SantaSpeedMobile').value = config.Santa.SantaSpeedMobile;
document.querySelector('#SantaSnowFallSpeed').value = config.Santa.SnowFallSpeed;
document.querySelector('#MaxSantaRestTime').value = config.Santa.MaxSantaRestTime;
document.querySelector('#MinSantaRestTime').value = config.Santa.MinSantaRestTime;
document.querySelector('#MaxPresentFallSpeed').value = config.Santa.MaxPresentFallSpeed;
document.querySelector('#MinPresentFallSpeed').value = config.Santa.MinPresentFallSpeed;
// Easter // Easter
document.querySelector('#EnableEaster').checked = config.Easter.EnableEaster; document.querySelector('#EnableEaster').checked = config.Easter.EnableEaster;
document.querySelector('#EasterEggCount').value = config.Easter.EggCount; document.querySelector('#EasterEggCount').value = config.Easter.EggCount;
document.querySelector('#EnableRandomEaster').checked = config.Easter.EnableRandomEaster; document.querySelector('#EnableRandomEaster').checked = config.Easter.EnableRandomEaster;
@@ -1724,14 +1847,163 @@
document.querySelector('#MinBunnyRestTime').value = config.Easter.MinBunnyRestTime; document.querySelector('#MinBunnyRestTime').value = config.Easter.MinBunnyRestTime;
document.querySelector('#MaxBunnyRestTime').value = config.Easter.MaxBunnyRestTime; document.querySelector('#MaxBunnyRestTime').value = config.Easter.MaxBunnyRestTime;
// 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;
// Fireworks
document.querySelector('#EnableFireworks').checked = config.Fireworks.EnableFireworks;
document.querySelector('#FireworksParticles').value = config.Fireworks.ParticleCount;
document.querySelector('#FireworksInterval').value = config.Fireworks.LaunchInterval;
document.querySelector('#ScrollFireworks').checked = config.Fireworks.ScrollFireworks;
document.querySelector('#MinFireworks').value = config.Fireworks.MinFireworks;
document.querySelector('#MaxFireworks').value = config.Fireworks.MaxFireworks;
// Halloween
document.querySelector('#EnableHalloween').checked = config.Halloween.EnableHalloween;
document.querySelector('#HalloweenCount').value = config.Halloween.SymbolCount;
document.querySelector('#EnableRandomHalloween').checked = config.Halloween.EnableRandomSymbols;
document.querySelector('#EnableRandomHalloweenMobile').checked = config.Halloween.EnableRandomSymbolsMobile;
document.querySelector('#EnableDifferentDurationHalloween').checked = config.Halloween.EnableDifferentDuration;
document.querySelector('#EnableSpiders').checked = config.Halloween.EnableSpiders !== undefined ? config.Halloween.EnableSpiders : true;
document.querySelector('#EnableMice').checked = config.Halloween.EnableMice !== undefined ? config.Halloween.EnableMice : true;
// Hearts
document.querySelector('#EnableHearts').checked = config.Hearts.EnableHearts;
document.querySelector('#HeartsCount').value = config.Hearts.SymbolCount;
document.querySelector('#EnableRandomHearts').checked = config.Hearts.EnableRandomSymbols;
document.querySelector('#EnableRandomHeartsMobile').checked = config.Hearts.EnableRandomSymbolsMobile;
document.querySelector('#EnableDifferentDurationHearts').checked = config.Hearts.EnableDifferentDuration;
// Matrix
document.querySelector('#EnableMatrix').checked = config.Matrix.EnableMatrix;
document.querySelector('#MatrixSymbolCount').value = config.Matrix.SymbolCount;
document.querySelector('#MatrixChars').value = config.Matrix.MatrixChars !== undefined ? config.Matrix.MatrixChars : '0123456789';
document.querySelector('#EnableRandomMatrix').checked = config.Matrix.EnableRandomMatrix;
document.querySelector('#EnableRandomMatrixMobile').checked = config.Matrix.EnableRandomMatrixMobile;
document.querySelector('#EnableDifferentDurationMatrix').checked = config.Matrix.EnableDifferentDuration;
document.querySelector('#EnableMatrixBackground').checked = config.Matrix.EnableMatrixBackground !== undefined ? config.Matrix.EnableMatrixBackground : false;
// Olympia
if (!config.Olympia) config.Olympia = { EnableOlympia: true, SymbolCount: 25, EnableRandomSymbols: true, EnableRandomSymbolsMobile: false, EnableDifferentDuration: true };
document.querySelector('#EnableOlympia').checked = config.Olympia.EnableOlympia !== false;
document.querySelector('#EnableRandomSymbolsOlympia').checked = config.Olympia.EnableRandomSymbols !== false;
document.querySelector('#EnableRandomSymbolsMobileOlympia').checked = config.Olympia.EnableRandomSymbolsMobile === true;
document.querySelector('#EnableDifferentDurationOlympia').checked = config.Olympia.EnableDifferentDuration !== false;
document.querySelector('#OlympiaSymbolCount').value = config.Olympia.SymbolCount || 25;
// 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;
// Resurrection // Resurrection
document.querySelector('#EnableResurrection').checked = config.Resurrection.EnableResurrection; document.querySelector('#EnableResurrection').checked = config.Resurrection.EnableResurrection;
document.querySelector('#ResurrectionSymbolCount').value = config.Resurrection.SymbolCount; document.querySelector('#ResurrectionSymbolCount').value = config.Resurrection.SymbolCount;
document.querySelector('#EnableRandomResurrection').checked = config.Resurrection.EnableRandomSymbols; document.querySelector('#EnableRandomResurrection').checked = config.Resurrection.EnableRandomSymbols;
document.querySelector('#EnableRandomResurrectionMobile').checked = config.Resurrection.EnableRandomSymbolsMobile; document.querySelector('#EnableRandomResurrectionMobile').checked = config.Resurrection.EnableRandomSymbolsMobile;
document.querySelector('#EnableDifferentDurationResurrection').checked = config.Resurrection.EnableDifferentDuration; document.querySelector('#EnableDifferentDurationResurrection').checked = config.Resurrection.EnableDifferentDuration;
// Santa
document.querySelector('#EnableSanta').checked = config.Santa.EnableSanta;
document.querySelector('#SantaSnowflakes').value = config.Santa.SnowflakesCount;
document.querySelector('#SantaSnowflakesMobile').value = config.Santa.SnowflakesCountMobile;
document.querySelector('#SantaSpeed').value = config.Santa.SantaSpeed;
document.querySelector('#SantaSpeedMobile').value = config.Santa.SantaSpeedMobile;
document.querySelector('#SantaSnowFallSpeed').value = config.Santa.SnowFallSpeed;
document.querySelector('#MaxSantaRestTime').value = config.Santa.MaxSantaRestTime;
document.querySelector('#MinSantaRestTime').value = config.Santa.MinSantaRestTime;
document.querySelector('#MaxPresentFallSpeed').value = config.Santa.MaxPresentFallSpeed;
document.querySelector('#MinPresentFallSpeed').value = config.Santa.MinPresentFallSpeed;
// Snowfall
document.querySelector('#EnableSnowfall').checked = config.Snowfall.EnableSnowfall;
document.querySelector('#SnowfallCount').value = config.Snowfall.SnowflakesCount;
document.querySelector('#SnowfallCountMobile').value = config.Snowfall.SnowflakesCountMobile;
document.querySelector('#SnowfallSpeed').value = config.Snowfall.Speed;
// Snowflakes
document.querySelector('#SnowflakesCount').value = config.Snowflakes.SnowflakeCount;
document.querySelector('#EnableSnowflakes').checked = config.Snowflakes.EnableSnowflakes;
document.querySelector('#EnableRandomSnowflakes').checked = config.Snowflakes.EnableRandomSnowflakes;
document.querySelector('#EnableRandomSnowflakesMobile').checked = config.Snowflakes.EnableRandomSnowflakesMobile;
document.querySelector('#EnableColoredSnowflakes').checked = config.Snowflakes.EnableColoredSnowflakes;
document.querySelector('#EnableDifferentDurationSnowflakes').checked = config.Snowflakes.EnableDifferentDuration;
// Snowstorm
document.querySelector('#EnableSnowstorm').checked = config.Snowstorm.EnableSnowstorm;
document.querySelector('#SnowstormCount').value = config.Snowstorm.SnowflakesCount;
document.querySelector('#SnowstormCountMobile').value = config.Snowstorm.SnowflakesCountMobile;
document.querySelector('#SnowstormSpeed').value = config.Snowstorm.Speed;
document.querySelector('#SnowstormHorizontalWind').value = config.Snowstorm.HorizontalWind;
document.querySelector('#SnowstormVerticalVariation').value = config.Snowstorm.VerticalVariation;
// Space
document.querySelector('#EnableSpace').checked = config.Space.EnableSpace || false;
document.querySelector('#EnableRandomSymbolsSpace').checked = config.Space.EnableRandomSymbols || false;
document.querySelector('#EnableRandomSymbolsMobileSpace').checked = config.Space.EnableRandomSymbolsMobile || false;
document.querySelector('#EnableDifferentDurationSpace').checked = config.Space.EnableDifferentDuration || false;
document.querySelector('#PlanetCount').value = config.Space.PlanetCount !== undefined ? config.Space.PlanetCount : 12;
document.querySelector('#AstronautCount').value = config.Space.AstronautCount !== undefined ? config.Space.AstronautCount : 5;
document.querySelector('#SatelliteCount').value = config.Space.SatelliteCount !== undefined ? config.Space.SatelliteCount : 2;
document.querySelector('#IssCount').value = config.Space.IssCount !== undefined ? config.Space.IssCount : 1;
document.querySelector('#RocketCount').value = config.Space.RocketCount !== undefined ? config.Space.RocketCount : 1;
// Spooky
document.querySelector('#SpookyCount').value = config.Spooky.SymbolCount !== undefined ? config.Spooky.SymbolCount : 25;
document.querySelector('#EnableSpooky').checked = config.Spooky.EnableSpooky !== undefined ? config.Spooky.EnableSpooky : true;
document.querySelector('#SpookySize').value = config.Spooky.SpookySize !== undefined ? config.Spooky.SpookySize : 30;
document.querySelector('#EnableSpookySway').checked = config.Spooky.EnableSpookySway !== undefined ? config.Spooky.EnableSpookySway : true;
document.querySelector('#SpookyGlowSize').value = config.Spooky.SpookyGlowSize !== undefined ? config.Spooky.SpookyGlowSize : 5;
document.querySelector('#EnableDifferentDurationSpooky').checked = config.Spooky.EnableDifferentDuration !== false;
// Sports
if (!config.Sports) config.Sports = { EnableSports: true, SymbolCount: 25, EnableRandomSymbols: true, EnableRandomSymbolsMobile: false, EnableDifferentDuration: true };
// Load Checkboxes
const savedBallsString = config.Sports.SportsBalls || 'football,basketball,tennis,volleyball';
const savedBalls = savedBallsString.split(',');
document.querySelector('#EnableSports').checked = config.Sports.EnableSports !== false;
document.querySelector('#EnableRandomSymbolsSports').checked = config.Sports.EnableRandomSymbols !== false;
document.querySelector('#EnableRandomSymbolsMobileSports').checked = config.Sports.EnableRandomSymbolsMobile === true;
document.querySelector('#EnableDifferentDurationSports').checked = config.Sports.EnableDifferentDuration !== false;
document.querySelector('#SportsSymbolCount').value = config.Sports.SymbolCount || 25;
document.querySelector('#TurfColor').value = config.Sports.TurfColor || '#228b22';
document.querySelector('#EnableTrophy').checked = config.Sports.EnableTrophy !== false;
document.querySelectorAll('.sport-ball-cb').forEach(cb => {
// Spring // Spring
document.querySelector('#EnableSpring').checked = config.Spring.EnableSpring; document.querySelector('#EnableSpring').checked = config.Spring.EnableSpring;
document.querySelector('#EnableSpringSunbeams').checked = config.Spring.EnableSpringSunbeams !== undefined ? config.Spring.EnableSpringSunbeams : true; 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;
@@ -1744,7 +2016,16 @@
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;
// 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;
// Summer // Summer
document.querySelector('#EnableSummer').checked = config.Summer.EnableSummer; document.querySelector('#EnableSummer').checked = config.Summer.EnableSummer;
document.querySelector('#SummerBubbleCount').value = config.Summer.BubbleCount; document.querySelector('#SummerBubbleCount').value = config.Summer.BubbleCount;
document.querySelector('#SummerDustCount').value = config.Summer.DustCount; document.querySelector('#SummerDustCount').value = config.Summer.DustCount;
@@ -1752,68 +2033,29 @@
document.querySelector('#EnableRandomSummerMobile').checked = config.Summer.EnableRandomSummerMobile; document.querySelector('#EnableRandomSummerMobile').checked = config.Summer.EnableRandomSummerMobile;
document.querySelector('#EnableDifferentDurationSummer').checked = config.Summer.EnableDifferentDuration; document.querySelector('#EnableDifferentDurationSummer').checked = config.Summer.EnableDifferentDuration;
// Carnival // Support for both new category string and legacy filename strings
document.querySelector('#EnableCarnival').checked = config.Carnival.EnableCarnival; cb.checked = savedBalls.some(b => b === cb.value || b.startsWith(cb.value + '_'));
document.querySelector('#EnableCarnivalSway').checked = config.Carnival.EnableCarnivalSway !== undefined ? config.Carnival.EnableCarnivalSway : true; });
document.querySelector('#CarnivalObjectCount').value = config.Carnival.ObjectCount;
document.querySelector('#EnableRandomCarnival').checked = config.Carnival.EnableRandomCarnival;
document.querySelector('#EnableRandomCarnivalMobile').checked = config.Carnival.EnableRandomCarnivalMobile;
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 // Underwater
document.querySelector('#EnableEarthDay').checked = config.EarthDay.EnableEarthDay;
document.querySelector('#EarthDayVineCount').value = config.EarthDay.VineCount;
// Eurovision document.querySelector('#EnableUnderwater').checked = config.Underwater.EnableUnderwater !== false;
document.querySelector('#EnableEurovision').checked = config.Eurovision.EnableEurovision; document.querySelector('#EnableRandomSymbolsUnderwater').checked = config.Underwater.EnableRandomSymbols !== false;
document.querySelector('#EurovisionSymbolCount').value = config.Eurovision.SymbolCount; document.querySelector('#EnableRandomSymbolsMobileUnderwater').checked = config.Underwater.EnableRandomSymbolsMobile === true;
document.querySelector('#EnableRandomEurovision').checked = config.Eurovision.EnableRandomEurovision; document.querySelector('#EnableDifferentDurationUnderwater').checked = config.Underwater.EnableDifferentDuration !== false;
document.querySelector('#EnableRandomEurovisionMobile').checked = config.Eurovision.EnableRandomEurovisionMobile; document.querySelector('#UnderwaterSeaweedCount').value = config.Underwater.SeaweedCount !== undefined ? config.Underwater.SeaweedCount : 30;
document.querySelector('#EnableDifferentDurationEurovision').checked = config.Eurovision.EnableDifferentDuration; document.querySelector('#UnderwaterFishCount').value = config.Underwater.FishCount !== undefined ? config.Underwater.FishCount : 15;
document.querySelector('#EnableColorfulNotes').checked = config.Eurovision.EnableColorfulNotes; document.querySelector('#UnderwaterSeahorseCount').value = config.Underwater.SeahorseCount !== undefined ? config.Underwater.SeahorseCount : 3;
document.querySelector('#EurovisionColors').value = config.Eurovision.EurovisionColors; document.querySelector('#UnderwaterJellyfishCount').value = config.Underwater.JellyfishCount !== undefined ? config.Underwater.JellyfishCount : 3;
document.querySelector('#EurovisionGlowSize').value = config.Eurovision.EurovisionGlowSize; document.querySelector('#UnderwaterTurtleCount').value = config.Underwater.TurtleCount !== undefined ? config.Underwater.TurtleCount : 1;
document.querySelector('#UnderwaterCrabCount').value = config.Underwater.CrabCount !== undefined ? config.Underwater.CrabCount : 2;
document.querySelector('#UnderwaterStarfishCount').value = config.Underwater.StarfishCount !== undefined ? config.Underwater.StarfishCount : 2;
document.querySelector('#UnderwaterShellCount').value = config.Underwater.ShellCount !== undefined ? config.Underwater.ShellCount : 2;
config.Underwater = config.Underwater || {};
document.querySelector('#EnableUnderwaterLightRays').checked = config.Underwater.EnableLightRays !== false;
document.querySelector('#UnderwaterSymbolCount').value = config.Underwater.SymbolCount || 15;
// 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;
document.querySelector('#EnablePiDayBackground').checked = config.PiDay.EnablePiDayBackground !== undefined ? config.PiDay.EnablePiDayBackground : false;
// 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;
// Spooky Theme
document.querySelector('#EnableSpooky').checked = config.Spooky.EnableSpooky !== undefined ? config.Spooky.EnableSpooky : true;
document.querySelector('#SpookyCount').value = config.Spooky.SymbolCount !== undefined ? config.Spooky.SymbolCount : 25;
document.querySelector('#SpookySize').value = config.Spooky.SpookySize !== undefined ? config.Spooky.SpookySize : 30;
document.querySelector('#EnableSpookySway').checked = config.Spooky.EnableSpookySway !== undefined ? config.Spooky.EnableSpookySway : true;
document.querySelector('#SpookyGlowSize').value = config.Spooky.SpookyGlowSize !== undefined ? config.Spooky.SpookyGlowSize : 5;
// 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();
}); });
@@ -1840,22 +2082,6 @@
config.Autumn.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationAutumn').checked; config.Autumn.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationAutumn').checked;
config.Autumn.EnableRotation = document.querySelector('#EnableRotation').checked; config.Autumn.EnableRotation = document.querySelector('#EnableRotation').checked;
// Friday13
if (!config.Friday13) config.Friday13 = {};
config.Friday13.EnableFriday13 = document.querySelector('#EnableFriday13').checked;
config.Friday13.SymbolCount = parseInt(document.querySelector('#Friday13SymbolCount').value);
config.Friday13.EnableRandomSymbols = document.querySelector('#EnableRandomFriday13').checked;
config.Friday13.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomFriday13Mobile').checked;
config.Friday13.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationFriday13').checked;
// Eid
if (!config.Eid) config.Eid = {};
config.Eid.EnableEid = document.querySelector('#EnableEid').checked;
config.Eid.EnableRandomSymbols = document.querySelector('#EnableRandomEid').checked;
config.Eid.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomEidMobile').checked;
config.Eid.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationEid').checked;
config.Eid.SymbolCount = parseInt(document.querySelector('#EidSymbolCount').value);
// Legacy Halloween // Legacy Halloween
if (!config.LegacyHalloween) config.LegacyHalloween = {}; if (!config.LegacyHalloween) config.LegacyHalloween = {};
config.LegacyHalloween.SymbolCount = parseInt(document.querySelector('#LegacyHalloweenSymbolCount').value); config.LegacyHalloween.SymbolCount = parseInt(document.querySelector('#LegacyHalloweenSymbolCount').value);
@@ -1867,6 +2093,15 @@
config.Sports.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomSymbolsMobileSports').checked; config.Sports.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomSymbolsMobileSports').checked;
config.Sports.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationSports').checked; config.Sports.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationSports').checked;
config.Sports.SymbolCount = parseInt(document.querySelector('#SportsSymbolCount').value); config.Sports.SymbolCount = parseInt(document.querySelector('#SportsSymbolCount').value);
config.Sports.TurfColor = document.querySelector('#TurfColor').value;
config.Sports.EnableTrophy = document.querySelector('#EnableTrophy').checked;
// Save Checkboxes
const selectedBalls = Array.from(document.querySelectorAll('.sport-ball-cb'))
.filter(cb => cb.checked)
.map(cb => cb.value);
config.Sports.SportsBalls = selectedBalls.join(',');
// Olympia // Olympia
if (!config.Olympia) config.Olympia = {}; if (!config.Olympia) config.Olympia = {};
@@ -1882,23 +2117,37 @@
config.Space.EnableRandomSymbols = document.querySelector('#EnableRandomSymbolsSpace').checked; config.Space.EnableRandomSymbols = document.querySelector('#EnableRandomSymbolsSpace').checked;
config.Space.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomSymbolsMobileSpace').checked; config.Space.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomSymbolsMobileSpace').checked;
config.Space.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationSpace').checked; config.Space.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationSpace').checked;
config.Space.SymbolCount = parseInt(document.querySelector('#SpaceSymbolCount').value); config.Space.PlanetCount = parseInt(document.querySelector('#PlanetCount').value);
config.Space.AstronautCount = parseInt(document.querySelector('#AstronautCount').value);
config.Space.SatelliteCount = parseInt(document.querySelector('#SatelliteCount').value);
config.Space.IssCount = parseInt(document.querySelector('#IssCount').value);
config.Space.RocketCount = parseInt(document.querySelector('#RocketCount').value);
// Underwater // Underwater
if (!config.Underwater) config.Underwater = {}; if (!config.Underwater) config.Underwater = {};
config.Underwater.EnableUnderwater = document.querySelector('#EnableUnderwater').checked; config.Underwater.EnableUnderwater = document.querySelector('#EnableUnderwater').checked;
config.Underwater.EnableUnderwaterLightRays = document.querySelector('#EnableUnderwaterLightRays').checked;
config.Underwater.EnableRandomSymbols = document.querySelector('#EnableRandomSymbolsUnderwater').checked; config.Underwater.EnableRandomSymbols = document.querySelector('#EnableRandomSymbolsUnderwater').checked;
config.Underwater.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomSymbolsMobileUnderwater').checked; config.Underwater.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomSymbolsMobileUnderwater').checked;
config.Underwater.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationUnderwater').checked; config.Underwater.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationUnderwater').checked;
config.Underwater.SymbolCount = parseInt(document.querySelector('#UnderwaterSymbolCount').value); config.Underwater.SeaweedCount = parseInt(document.querySelector('#UnderwaterSeaweedCount').value);
config.Underwater.FishCount = parseInt(document.querySelector('#UnderwaterFishCount').value);
config.Underwater.SeahorseCount = parseInt(document.querySelector('#UnderwaterSeahorseCount').value);
config.Underwater.JellyfishCount = parseInt(document.querySelector('#UnderwaterJellyfishCount').value);
config.Underwater.TurtleCount = parseInt(document.querySelector('#UnderwaterTurtleCount').value);
config.Underwater.CrabCount = parseInt(document.querySelector('#UnderwaterCrabCount').value);
config.Underwater.StarfishCount = parseInt(document.querySelector('#UnderwaterStarfishCount').value);
config.Underwater.ShellCount = parseInt(document.querySelector('#UnderwaterShellCount').value);
// Birthday // Birthday
if (!config.Birthday) config.Birthday = {}; if (!config.Birthday) config.Birthday = {};
config.Birthday.EnableBirthday = document.querySelector('#EnableBirthday').checked; config.Birthday.EnableBirthday = document.querySelector('#EnableBirthday').checked;
config.Birthday.EnableGarland = document.querySelector('#EnableGarland').checked;
config.Birthday.EnableRandomSymbols = document.querySelector('#EnableRandomSymbolsBirthday').checked; config.Birthday.EnableRandomSymbols = document.querySelector('#EnableRandomSymbolsBirthday').checked;
config.Birthday.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomSymbolsMobileBirthday').checked; config.Birthday.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomSymbolsMobileBirthday').checked;
config.Birthday.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationBirthday').checked; config.Birthday.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationBirthday').checked;
config.Birthday.SymbolCount = parseInt(document.querySelector('#BirthdaySymbolCount').value); config.Birthday.SymbolCount = parseInt(document.querySelector('#BirthdaySymbolCount').value);
config.Birthday.ConfettiCount = parseInt(document.querySelector('#BirthdayConfettiCount').value);
// Snowflakes // Snowflakes
config.Snowflakes.SnowflakeCount = parseInt(document.querySelector('#SnowflakesCount').value); config.Snowflakes.SnowflakeCount = parseInt(document.querySelector('#SnowflakesCount').value);
@@ -2041,13 +2290,14 @@
config.Eurovision.EurovisionColors = document.querySelector('#EurovisionColors').value; config.Eurovision.EurovisionColors = document.querySelector('#EurovisionColors').value;
config.Eurovision.EurovisionGlowSize = parseInt(document.querySelector('#EurovisionGlowSize').value); config.Eurovision.EurovisionGlowSize = parseInt(document.querySelector('#EurovisionGlowSize').value);
// Pi-Day // Matrix
config.PiDay.EnablePiDay = document.querySelector('#EnablePiDay').checked; config.Matrix.EnableMatrix = document.querySelector('#EnableMatrix').checked;
config.PiDay.SymbolCount = parseInt(document.querySelector('#PiDaySymbolCount').value); config.Matrix.SymbolCount = parseInt(document.querySelector('#MatrixSymbolCount').value);
config.PiDay.EnableRandomPiDay = document.querySelector('#EnableRandomPiDay').checked; config.Matrix.MatrixChars = document.querySelector('#MatrixChars').value;
config.PiDay.EnableRandomPiDayMobile = document.querySelector('#EnableRandomPiDayMobile').checked; config.Matrix.EnableRandomMatrix = document.querySelector('#EnableRandomMatrix').checked;
config.PiDay.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationPiDay').checked; config.Matrix.EnableRandomMatrixMobile = document.querySelector('#EnableRandomMatrixMobile').checked;
config.PiDay.EnablePiDayBackground = document.querySelector('#EnablePiDayBackground').checked; config.Matrix.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationMatrix').checked;
config.Matrix.EnableMatrixBackground = document.querySelector('#EnableMatrixBackground').checked;
// Pride // Pride
config.Pride.EnablePride = document.querySelector('#EnablePride').checked; config.Pride.EnablePride = document.querySelector('#EnablePride').checked;
@@ -2068,7 +2318,15 @@
config.Storm.RainSpeed = parseFloat(document.querySelector('#StormRainSpeed').value); config.Storm.RainSpeed = parseFloat(document.querySelector('#StormRainSpeed').value);
config.Storm.EnableLightning = document.querySelector('#StormEnableLightning').checked; config.Storm.EnableLightning = document.querySelector('#StormEnableLightning').checked;
// New Themes // Underwater
config.Underwater.EnableUnderwater = document.querySelector('#EnableUnderwater').checked;
config.Underwater.EnableLightRays = document.querySelector('#EnableUnderwaterLightRays').checked;
config.Underwater.SymbolCount = parseInt(document.querySelector('#UnderwaterSymbolCount').value);
config.Underwater.EnableRandomSymbols = document.querySelector('#EnableRandomUnderwater').checked;
config.Underwater.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomUnderwaterMobile').checked;
config.Underwater.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationUnderwater').checked;
// Simple Themes (just on/off toggles)
config.Frost.EnableFrost = document.querySelector('#EnableFrost').checked; config.Frost.EnableFrost = document.querySelector('#EnableFrost').checked;
config.FilmNoir.EnableFilmNoir = document.querySelector('#EnableFilmNoir').checked; config.FilmNoir.EnableFilmNoir = document.querySelector('#EnableFilmNoir').checked;
config.Oscar.EnableOscar = document.querySelector('#EnableOscar').checked; config.Oscar.EnableOscar = document.querySelector('#EnableOscar').checked;
@@ -2152,12 +2410,28 @@
config.Eurovision.EurovisionColors = document.querySelector('#EurovisionColors').value; config.Eurovision.EurovisionColors = document.querySelector('#EurovisionColors').value;
config.Eurovision.EurovisionGlowSize = parseInt(document.querySelector('#EurovisionGlowSize').value); config.Eurovision.EurovisionGlowSize = parseInt(document.querySelector('#EurovisionGlowSize').value);
// Pi-Day // Birthday
config.PiDay.EnablePiDay = document.querySelector('#EnablePiDay').checked; config.Birthday.EnableBirthday = document.querySelector('#EnableBirthday').checked;
config.PiDay.SymbolCount = parseInt(document.querySelector('#PiDaySymbolCount').value); config.Birthday.EnableGarland = document.querySelector('#EnableGarland').checked;
config.PiDay.EnableRandomPiDay = document.querySelector('#EnableRandomPiDay').checked; config.Birthday.SymbolCount = parseInt(document.querySelector('#BirthdaySymbolCount').value);
config.PiDay.EnableRandomPiDayMobile = document.querySelector('#EnableRandomPiDayMobile').checked; config.Birthday.ConfettiCount = parseInt(document.querySelector('#BirthdayConfettiCount').value);
config.PiDay.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationPiDay').checked; config.Birthday.EnableRandomSymbols = document.querySelector('#EnableRandomSymbolsBirthday').checked;
config.Birthday.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomSymbolsMobileBirthday').checked;
config.Birthday.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationBirthday').checked;
// Sports
config.Sports.EnableSports = document.querySelector('#EnableSports').checked;
config.Sports.SymbolCount = parseInt(document.querySelector('#SportsSymbolCount').value);
config.Sports.EnableRandomSymbols = document.querySelector('#EnableRandomSymbolsSports').checked;
config.Sports.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomSymbolsMobileSports').checked;
config.Sports.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationSports').checked;
// Olympia
config.Olympia.EnableOlympia = document.querySelector('#EnableOlympia').checked;
config.Olympia.SymbolCount = parseInt(document.querySelector('#OlympiaSymbolCount').value);
config.Olympia.EnableRandomSymbols = document.querySelector('#EnableRandomSymbolsOlympia').checked;
config.Olympia.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomSymbolsMobileOlympia').checked;
config.Olympia.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationOlympia').checked;
// Pride // Pride
config.Pride.EnablePride = document.querySelector('#EnablePride').checked; config.Pride.EnablePride = document.querySelector('#EnablePride').checked;

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

View File

@@ -1,164 +1,139 @@
.autumn-container { .autumn-container {
display: block; display: block;
position: fixed; position: fixed;
overflow: hidden; overflow: hidden;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none; pointer-events: none;
z-index: 10; z-index: 10;
contain: layout paint; contain: layout paint;
} }
.leaf { .leaf {
position: fixed; position: fixed;
z-index: 15; z-index: 15;
top: 0; top: 0;
will-change: transform; will-change: transform;
translate: 0 -10vh; translate: 0 -10vh;
font-size: 1em; font-size: 1em;
color: #fff; color: #fff;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
text-shadow: 0 0 5px #000; text-shadow: 0 0 5px #000;
user-select: none; user-select: none;
-webkit-user-select: none; cursor: default;
cursor: default; animation-name: leaf-fall, leaf-shake;
-webkit-animation-name: leaf-fall, leaf-shake; animation-duration: 7s, 4s;
-webkit-animation-duration: 7s, 4s; animation-timing-function: linear, ease-in-out;
-webkit-animation-timing-function: linear, ease-in-out; animation-iteration-count: infinite, infinite;
-webkit-animation-iteration-count: infinite, infinite; }
-webkit-user-select: none;
animation-name: leaf-fall, leaf-shake; /* Class to disable rotation */
animation-duration: 7s, 4s; .no-rotation {
animation-timing-function: linear, ease-in-out; --rotate-start: 0deg !important;
animation-iteration-count: infinite, infinite; --rotate-end: 0deg !important;
} }
/* Class to disable rotation */
.no-rotation { @keyframes leaf-fall {
--rotate-start: 0deg !important; 0% {
--rotate-end: 0deg !important; translate: 0 -10vh;
} }
@-webkit-keyframes leaf-fall { 100% {
0% { translate: 0 100vh;
translate: 0 -10vh; }
} }
100% {
translate: 0 100vh; @keyframes leaf-shake {
} 0%, 100% {
} transform: translateX(0) rotate(var(--rotate-start, -20deg));
}
@keyframes leaf-fall { 50% {
0% { transform: translateX(80px) rotate(var(--rotate-end, 20deg));
translate: 0 -10vh; }
} }
100% { .leaf:nth-of-type(0) {
translate: 0 100vh; left: 0%;
} animation-delay: 0s, 0s;
} --rotate-start: -25deg;
--rotate-end: 22deg;
@-webkit-keyframes leaf-shake { }
0%, 100% {
transform: translateX(0) rotate(var(--rotate-start, -20deg)); .leaf:nth-of-type(1) {
} left: 10%;
50% { animation-delay: 1s, 0.5s;
transform: translateX(80px) rotate(var(--rotate-end, 20deg)); --rotate-start: -32deg;
} --rotate-end: 35deg;
} }
@keyframes leaf-shake { .leaf:nth-of-type(2) {
0%, 100% { left: 20%;
transform: translateX(0) rotate(var(--rotate-start, -20deg)); animation-delay: 6s, 1s;
} --rotate-start: -28deg;
50% { --rotate-end: 28deg;
transform: translateX(80px) rotate(var(--rotate-end, 20deg)); }
}
} .leaf:nth-of-type(3) {
left: 30%;
.leaf:nth-of-type(0) { animation-delay: 4s, 1.5s;
left: 0%; --rotate-start: -38deg;
animation-delay: 0s, 0s; --rotate-end: 32deg;
--rotate-start: -25deg; }
--rotate-end: 22deg;
} .leaf:nth-of-type(4) {
left: 40%;
.leaf:nth-of-type(1) { animation-delay: 2s, 0.8s;
left: 10%; --rotate-start: -22deg;
animation-delay: 1s, 0.5s; --rotate-end: 38deg;
--rotate-start: -32deg; }
--rotate-end: 35deg;
} .leaf:nth-of-type(5) {
left: 50%;
.leaf:nth-of-type(2) { animation-delay: 8s, 2s;
left: 20%; --rotate-start: -35deg;
animation-delay: 6s, 1s; --rotate-end: 25deg;
--rotate-start: -28deg; }
--rotate-end: 28deg;
} .leaf:nth-of-type(6) {
left: 60%;
.leaf:nth-of-type(3) { animation-delay: 6s, 1.2s;
left: 30%; --rotate-start: -40deg;
animation-delay: 4s, 1.5s; --rotate-end: 40deg;
--rotate-start: -38deg; }
--rotate-end: 32deg;
} .leaf:nth-of-type(7) {
left: 70%;
.leaf:nth-of-type(4) { animation-delay: 2.5s, 0.3s;
left: 40%; --rotate-start: -30deg;
animation-delay: 2s, 0.8s; --rotate-end: 30deg;
--rotate-start: -22deg; }
--rotate-end: 38deg;
} .leaf:nth-of-type(8) {
left: 80%;
.leaf:nth-of-type(5) { animation-delay: 1s, 1.8s;
left: 50%; --rotate-start: -26deg;
animation-delay: 8s, 2s; --rotate-end: 36deg;
--rotate-start: -35deg; }
--rotate-end: 25deg;
} .leaf:nth-of-type(9) {
left: 90%;
.leaf:nth-of-type(6) { animation-delay: 3s, 0.7s;
left: 60%; --rotate-start: -34deg;
animation-delay: 6s, 1.2s; --rotate-end: 24deg;
--rotate-start: -40deg; }
--rotate-end: 40deg;
} .leaf:nth-of-type(10) {
left: 25%;
.leaf:nth-of-type(7) { animation-delay: 2s, 2.3s;
left: 70%; --rotate-start: -29deg;
animation-delay: 2.5s, 0.3s; --rotate-end: 33deg;
--rotate-start: -30deg; }
--rotate-end: 30deg;
} .leaf:nth-of-type(11) {
left: 65%;
.leaf:nth-of-type(8) { animation-delay: 4s, 1.4s;
left: 80%;
animation-delay: 1s, 1.8s;
--rotate-start: -26deg;
--rotate-end: 36deg;
}
.leaf:nth-of-type(9) {
left: 90%;
animation-delay: 3s, 0.7s;
--rotate-start: -34deg;
--rotate-end: 24deg;
}
.leaf:nth-of-type(10) {
left: 25%;
animation-delay: 2s, 2.3s;
--rotate-start: -29deg;
--rotate-end: 33deg;
}
.leaf:nth-of-type(11) {
left: 65%;
animation-delay: 4s, 1.4s;
--rotate-start: -37deg;
--rotate-end: 27deg;
--rotate-start: -37deg; --rotate-start: -37deg;

View File

@@ -5,86 +5,151 @@
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
pointer-events: none; pointer-events: none;
z-index: 9999; z-index: 10;
overflow: hidden; overflow: hidden;
contain: strict; contain: strict;
} contain: layout paint;
.birthday-cake {
position: absolute;
bottom: 2vh;
left: 50vw;
transform: translateX(-50%);
font-size: 8rem;
z-index: 50;
filter: drop-shadow(0 0 10px rgba(255,255,255,0.4));
}
.birthday-cake img {
height: 15vh;
width: auto;
object-fit: contain;
max-height: 150px;
} }
.birthday-symbol { .birthday-symbol {
position: absolute; will-change: opacity;
bottom: -10vh; /* balloons rise from bottom */ position: fixed;
animation: birthday-rise linear infinite; top: 0;
font-size: 3rem; left: 0;
animation: birthday-rise linear infinite forwards;
opacity: 0.95; opacity: 0.95;
z-index: 40; z-index: 40;
pointer-events: none;
} }
.birthday-sway {
will-change: transform;
animation-name: birthday-sway;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
animation-direction: alternate;
}
.birthday-inner {
pointer-events: auto;
cursor: crosshair;
display: inline-block;
}
/* MARK: Balloon Size */
.birthday-symbol img { .birthday-symbol img {
width: 6vh; width: 18vh;
height: auto; height: auto;
max-width: 60px; max-width: 100px;
object-fit: contain; object-fit: contain;
} }
.birthday-confetti-wrapper {
position: fixed;
top: 0;
left: 0;
z-index: 30;
will-change: transform;
animation-name: birthday-confetti-fall;
animation-timing-function: linear;
animation-iteration-count: infinite;
pointer-events: none;
}
.birthday-confetti { .birthday-confetti {
position: absolute; width: 8px;
top: -5vh; height: 16px;
background-color: rgb(0, 0, 0);
will-change: transform;
animation-name: birthday-flutter;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
.birthday-confetti.circle {
width: 8px;
height: 8px;
border-radius: 50%;
}
.birthday-confetti.square {
width: 8px;
height: 8px;
}
.birthday-confetti.triangle {
width: 10px; width: 10px;
height: 10px; height: 10px;
opacity: 0.9; clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
animation: birthday-confetti-fall linear infinite;
z-index: 30;
/* Mix of circles and squares by using CSS variables or random in JS. For simplicity, we make all slightly rounded rectangles */
border-radius: 2px;
} }
@keyframes birthday-rise { @keyframes birthday-rise {
0% { 0% { transform: translate3d(var(--x-pos, 0vw), 110vh, 0) rotate(var(--start-rot, 0deg)); opacity: 0; }
transform: translateY(10vh) rotate(var(--start-rot, 0deg)); 10% { opacity: 1; }
opacity: 0; 90% { opacity: 1; }
} 100% { transform: translate3d(var(--x-pos, 0vw), -20vh, 0) rotate(calc(var(--start-rot, 0deg) * -1)); opacity: 0; }
10% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
transform: translateY(-110vh) rotate(calc(var(--start-rot, 0deg) * -1));
opacity: 0;
}
} }
@keyframes birthday-confetti-fall { @keyframes birthday-confetti-fall {
0% { transform: translate3d(var(--x-pos, 0vw), -10vh, 0); }
100% { transform: translate3d(var(--x-pos, 0vw), 110vh, 0); }
}
@keyframes birthday-sway {
0% { transform: translateX(calc(var(--sway-amount, 50px) * -1)); }
100% { transform: translateX(var(--sway-amount, 50px)); }
}
@keyframes birthday-flutter {
0% { transform: rotate3d(var(--rx, 1), var(--ry, 1), var(--rz, 0), 0deg); }
100% { transform: rotate3d(var(--rx, 1), var(--ry, 1), var(--rz, 0), var(--rot-dir, 360deg)); }
}
@keyframes birthday-pop {
0% { transform: scale(1); opacity: 1; filter: brightness(1); }
30% { transform: scale(1.3); opacity: 1; filter: brightness(1.5); }
100% { transform: scale(0); opacity: 0; filter: brightness(2); }
}
.birthday-burst-wrapper {
position: absolute;
pointer-events: none;
z-index: 1000;
will-change: transform, opacity;
animation: birthday-burst-y 1.2s cubic-bezier(0.42, 0, 1, 1) forwards;
}
.birthday-burst-confetti {
will-change: transform;
animation: birthday-burst-x 1.2s cubic-bezier(0.25, 1, 0.5, 1) forwards;
}
.birthday-burst-confetti.circle {
border-radius: 50%;
}
.birthday-burst-confetti.triangle {
width: 10px;
height: 10px;
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
}
@keyframes birthday-burst-y {
0% { 0% {
transform: translateY(-5vh) rotateX(0deg) rotateY(0deg) rotateZ(0deg); transform: translateY(0);
opacity: 0;
}
5% {
opacity: 1;
}
90% {
opacity: 1; opacity: 1;
} }
100% { 100% {
transform: translateY(105vh) rotateX(720deg) rotateY(360deg) rotateZ(180deg); transform: translateY(calc(var(--burst-y) + 150px));
opacity: 0; opacity: 0;
} }
} }
@keyframes birthday-burst-x {
0% {
transform: translateX(0) rotate3d(var(--rx), var(--ry), var(--rz), 0deg);
}
100% {
transform: translateX(calc(var(--burst-x) * 1.5)) rotate3d(var(--rx), var(--ry), var(--rz), var(--rot-dir));
}
}

View File

@@ -1,11 +1,37 @@
const config = window.SeasonalsPluginConfig?.Birthday || {}; const config = window.SeasonalsPluginConfig?.Birthday || {};
const birthday = config.EnableBirthday !== undefined ? config.EnableBirthday : true; const birthday = config.EnableBirthday !== undefined ? config.EnableBirthday : true;
const symbolCount = config.SymbolCount || 25; const symbolCount = config.SymbolCount || 5;
const useRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true; const useRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true;
const enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false; const enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false;
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true;
const birthdayImages = [
'../Seasonals/Resources/birthday_assets/balloon_blue.gif',
'../Seasonals/Resources/birthday_assets/balloon_green.gif',
'../Seasonals/Resources/birthday_assets/balloon_lightblue.gif',
'../Seasonals/Resources/birthday_assets/balloon_orange.gif',
'../Seasonals/Resources/birthday_assets/balloon_pink.gif',
'../Seasonals/Resources/birthday_assets/balloon_red.gif',
'../Seasonals/Resources/birthday_assets/balloon_yellow.gif',
'../Seasonals/Resources/birthday_assets/balloon_turquoise.gif',
'../Seasonals/Resources/birthday_assets/balloon_violet.gif'
];
const balloonColors = {
'balloon_blue': ['#3498db', '#2980b9', '#1f618d'],
'balloon_green': ['#2ecc71', '#27ae60', '#1e8449'],
'balloon_lightblue': ['#36c5f0', '#81ecec', '#00cec9'],
'balloon_orange': ['#e67e22', '#d35400', '#a04000'],
'balloon_pink': ['#ff726d', '#f4306d', '#e84393'],
'balloon_red': ['#e74c3c', '#c0392b', '#922b21'],
'balloon_yellow': ['#f1c40f', '#f39c12', '#b7950b'],
'balloon_turquoise': ['#36c5f0', '#81ecec', '#00cec9'],
'balloon_violet': ['#9b59b6', '#8e44ad', '#6c3483']
};
let msgPrinted = false; let msgPrinted = false;
function toggleBirthday() { function toggleBirthday() {
@@ -39,6 +65,72 @@ observer.observe(document.body, {
attributes: true attributes: true
}); });
function createBalloonPopConfetti(container, x, y, colors) {
const popConfettiColors = colors || [
'#fce18a', '#ff726d', '#b48def', '#f4306d',
'#36c5f0', '#2ccc5d', '#e9b31d', '#9b59b6',
'#3498db', '#e74c3c', '#1abc9c', '#f1c40f'
];
// Spawn 15-20 particles
const particleCount = Math.floor(Math.random() * 5) + 15;
for (let i = 0; i < particleCount; i++) {
const wrapper = document.createElement('div');
wrapper.className = 'birthday-burst-wrapper';
wrapper.style.position = 'absolute';
wrapper.style.left = `${x}px`;
wrapper.style.top = `${y}px`;
wrapper.style.zIndex = '1000';
const particle = document.createElement('div');
particle.classList.add('birthday-burst-confetti');
// Random color
const color = popConfettiColors[Math.floor(Math.random() * popConfettiColors.length)];
particle.style.backgroundColor = color;
// Random shape
const shape = Math.random();
if (shape > 0.66) {
particle.classList.add('circle');
const size = Math.random() * 4 + 4; // 4-8px
particle.style.width = `${size}px`;
particle.style.height = `${size}px`;
} else if (shape > 0.33) {
particle.classList.add('rect');
const width = Math.random() * 3 + 3; // 3-6px
const height = Math.random() * 4 + 6; // 6-10px
particle.style.width = `${width}px`;
particle.style.height = `${height}px`;
} else {
particle.classList.add('triangle');
}
// Random direction for explosion (circular)
const angle = Math.random() * 2 * Math.PI;
const distance = Math.random() * 60 + 20; // 20-80px burst radius
const xOffset = Math.cos(angle) * distance;
const yOffset = Math.sin(angle) * distance;
particle.style.setProperty('--burst-x', `${xOffset}px`);
wrapper.style.setProperty('--burst-y', `${yOffset}px`);
// Random rotation during fall
particle.style.setProperty('--rot-dir', `${(Math.random() > 0.5 ? 1 : -1) * 360}deg`);
particle.style.setProperty('--rx', Math.random().toFixed(2));
particle.style.setProperty('--ry', Math.random().toFixed(2));
particle.style.setProperty('--rz', (Math.random() * 0.5).toFixed(2));
wrapper.appendChild(particle);
container.appendChild(wrapper);
// Remove particle after animation
setTimeout(() => wrapper.remove(), 1000);
}
}
function createBirthday() { function createBirthday() {
const container = document.querySelector('.birthday-container') || document.createElement('div'); const container = document.querySelector('.birthday-container') || document.createElement('div');
@@ -48,17 +140,7 @@ function createBirthday() {
document.body.appendChild(container); document.body.appendChild(container);
} }
// Spawn Birthday Cake at the bottom // Cake and Garland have been removed
const cake = document.createElement('div');
cake.className = 'birthday-cake';
let cakeImg = document.createElement('img');
cakeImg.src = `../Seasonals/Resources/birthday_images/cake.png`;
cakeImg.onerror = function() {
this.style.display = 'none';
this.parentElement.innerHTML = '🎂';
};
cake.appendChild(cakeImg);
container.appendChild(cake);
const standardCount = 15; const standardCount = 15;
const totalSymbols = symbolCount + standardCount; const totalSymbols = symbolCount + standardCount;
@@ -72,35 +154,99 @@ function createBirthday() {
const useRandomDuration = enableDifferentDuration !== false; const useRandomDuration = enableDifferentDuration !== false;
// We'll treat balloons and gifts as rising symbols // Arrays moved to top of file
const activeItems = ['balloon_red', 'balloon_blue', 'balloon_yellow', 'gift'];
for (let i = 0; i < finalCount; i++) { for (let i = 0; i < finalCount; i++) {
let symbol = document.createElement('div'); let symbol = document.createElement('div');
const randomItem = activeItems[Math.floor(Math.random() * activeItems.length)]; const randomImage = birthdayImages[Math.floor(Math.random() * birthdayImages.length)];
const randomItem = randomImage.split('/').pop().split('.')[0]; // Extracts "balloon_blue"
symbol.className = `birthday-symbol birthday-${randomItem}`; symbol.className = `birthday-symbol birthday-${randomItem}`;
// Create inner div for sway
let innerDiv = document.createElement('div');
innerDiv.className = 'birthday-inner';
let img = document.createElement('img'); let img = document.createElement('img');
img.src = `../Seasonals/Resources/birthday_images/${randomItem}.png`; img.src = randomImage;
img.onerror = function() { img.onerror = function() {
this.style.display = 'none'; symbol.remove(); // Remove element completely on error
this.parentElement.innerHTML = getBirthdayEmojiFallback(randomItem);
}; };
symbol.appendChild(img); innerDiv.appendChild(img);
// Sway wrapper
let swayWrapper = document.createElement('div');
swayWrapper.className = 'birthday-sway';
const swayDuration = Math.random() * 3 + 3; // 3-6s per cycle
swayWrapper.style.animationDuration = `${swayDuration}s`;
swayWrapper.style.animationDelay = `-${Math.random() * 5}s`;
const swayAmount = Math.random() * 60 + 20; // 20-80px
const direction = Math.random() > 0.5 ? 1 : -1;
swayWrapper.style.setProperty('--sway-amount', `${swayAmount * direction}px`);
swayWrapper.appendChild(innerDiv);
symbol.appendChild(swayWrapper);
const leftPos = Math.random() * 95; const leftPos = Math.random() * 95;
const delaySeconds = Math.random() * 10;
// Far away effect
const depth = Math.random();
// MARK: balloon size
const scale = 0.85 + depth * 0.3; // 0.85 to 1.15
const zIndex = Math.floor(depth * 30) + 10;
img.style.transform = `scale(${scale})`;
symbol.style.zIndex = zIndex;
let durationSeconds = 9; let durationSeconds = 9;
if (useRandomDuration) { if (useRandomDuration) {
durationSeconds = Math.random() * 5 + 7; // 7 to 12 seconds // Far strings climb slower
durationSeconds = (1 - depth) * 6 + 7 + Math.random() * 4;
}
// Negative delay correctly scatters them initially across the screen vertically
// avoiding them all popping up at bottom edge together
const delaySeconds = -(Math.random() * durationSeconds);
const isBalloon = randomItem.startsWith('balloon');
if (isBalloon) {
// Sway animation is now handled natively by the GIF motion.
// Interaction to pop is handled visually by the GIF, but we can still remove it on hover
innerDiv.addEventListener('mouseenter', function(e) {
if (!this.classList.contains('popped')) {
this.classList.add('popped');
this.style.animation = 'birthday-pop 0.2s ease-out forwards';
this.style.pointerEvents = 'none'; // avoid re-triggering
// Create confetti burst at balloon's screen position
const rect = this.getBoundingClientRect();
const cx = rect.left + rect.width / 2;
// explosion height
const cy = rect.top + rect.height * -0.05;
// Ensure the burst container is appended to the main document body or the birthday container
createBalloonPopConfetti(document.body, cx, cy, balloonColors[randomItem]);
}
});
// Reset the balloon when it reappears at the bottom of the screen
symbol.addEventListener('animationiteration', function(e) {
// Ignore bubbling events from the inner sway animation
if (e.animationName === 'birthday-rise' || e.target === symbol) {
if (innerDiv.classList.contains('popped')) {
innerDiv.classList.remove('popped');
innerDiv.style.animation = '';
innerDiv.style.pointerEvents = 'auto';
}
}
});
} }
const startRot = (Math.random() * 20) - 10; // -10 to +10 spread const startRot = (Math.random() * 20) - 10; // -10 to +10 spread
symbol.style.setProperty('--start-rot', `${startRot}deg`); symbol.style.setProperty('--start-rot', `${startRot}deg`);
symbol.style.setProperty('--x-pos', `${leftPos}vw`);
symbol.style.left = `${leftPos}vw`;
symbol.style.animationDuration = `${durationSeconds}s`; symbol.style.animationDuration = `${durationSeconds}s`;
symbol.style.animationDelay = `${delaySeconds}s`; symbol.style.animationDelay = `${delaySeconds}s`;
@@ -108,33 +254,73 @@ function createBirthday() {
} }
// Party Confetti // Party Confetti
const confettiColors = ['#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#808080', '#ffffff', '#000000']; const baseConfettiCount = config.ConfettiCount !== undefined ? config.ConfettiCount : 60;
const confettiCount = isMobile ? 40 : 80; const confettiCount = isMobile ? Math.floor(baseConfettiCount / 2) : baseConfettiCount;
const allColors = ['#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#808080', '#ffffff', '#000000'];
for (let i = 0; i < confettiCount; i++) { for (let i = 0; i < confettiCount; i++) {
let confetti = document.createElement('div'); const wrapper = document.createElement('div');
confetti.className = 'birthday-confetti'; wrapper.classList.add('birthday-confetti-wrapper');
const color = confettiColors[Math.floor(Math.random() * confettiColors.length)]; // Use carnival.js 3D advanced fluttering logic
let swayWrapper = document.createElement('div');
swayWrapper.classList.add('birthday-sway');
wrapper.appendChild(swayWrapper);
const confetti = document.createElement('div');
confetti.classList.add('birthday-confetti');
const color = allColors[Math.floor(Math.random() * allColors.length)];
confetti.style.backgroundColor = color; confetti.style.backgroundColor = color;
// Shape assignments
const shape = Math.random();
if (shape > 0.8) confetti.classList.add('circle');
else if (shape > 0.6) confetti.classList.add('square');
else if (shape > 0.4) confetti.classList.add('triangle');
else confetti.classList.add('rect'); // default
// Sizing
if (!confetti.classList.contains('circle') && !confetti.classList.contains('square') && !confetti.classList.contains('triangle')) {
const width = Math.random() * 3 + 4; // 4-7px
const height = Math.random() * 5 + 8; // 8-13px
confetti.style.width = `${width}px`;
confetti.style.height = `${height}px`;
} else if (confetti.classList.contains('circle') || confetti.classList.contains('square')) {
const size = Math.random() * 5 + 5; // 5-10px
confetti.style.width = `${size}px`;
confetti.style.height = `${size}px`;
}
const duration = Math.random() * 5 + 5;
const delay = -Math.random() * duration; // Spawn fully integrated across screen width/height
const leftPos = Math.random() * 100; wrapper.style.setProperty('--x-pos', `${Math.random() * 100}vw`);
const delaySeconds = Math.random() * 8; wrapper.style.animationDelay = `${delay}s`;
const duration = Math.random() * 3 + 4; wrapper.style.animationDuration = `${duration}s`;
// Sway handling
const swayDuration = Math.random() * 2 + 3; // 3-5s per cycle
swayWrapper.style.animationDuration = `${swayDuration}s`;
swayWrapper.style.animationDelay = `-${Math.random() * 5}s`;
const swayAmount = Math.random() * 70 + 30; // 30-100px
const direction = Math.random() > 0.5 ? 1 : -1;
swayWrapper.style.setProperty('--sway-amount', `${swayAmount * direction}px`);
// 3D Flutter Rotation
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));
const rotDir = Math.random() > 0.5 ? 1 : -1;
confetti.style.setProperty('--rot-dir', `${rotDir * 360}deg`);
confetti.style.left = `${leftPos}vw`; swayWrapper.appendChild(confetti);
confetti.style.animationDuration = `${duration}s`; container.appendChild(wrapper);
confetti.style.animationDelay = `${delaySeconds}s`;
container.appendChild(confetti);
} }
} }
function getBirthdayEmojiFallback(type) { /* Removed fallback logic */
if (type.startsWith('balloon')) return '🎈';
if (type === 'gift') return '🎁';
return '';
}
function initializeBirthday() { function initializeBirthday() {
if (!birthday) return; if (!birthday) return;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

View File

@@ -1,138 +1,112 @@
.christmas-container { .christmas-container {
display: block; display: block;
position: fixed; position: fixed;
overflow: hidden; overflow: hidden;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none; pointer-events: none;
z-index: 10; z-index: 10;
contain: layout paint; contain: layout paint;
} }
.christmas { .christmas {
position: fixed; position: fixed;
z-index: 15; z-index: 15;
top: 0; top: 0;
will-change: transform; will-change: transform;
translate: 0 -10vh; translate: 0 -10vh;
font-size: 1em; font-size: 1em;
color: #fff; color: #fff;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
text-shadow: 0 0 5px #000; text-shadow: 0 0 5px #000;
user-select: none; user-select: none;
cursor: default; cursor: default;
-webkit-user-select: none; animation-name: christmas-fall, christmas-shake;
-webkit-animation-name: christmas-fall, christmas-shake; animation-duration: 10s, 3s;
-webkit-animation-duration: 10s, 3s; animation-timing-function: linear, ease-in-out;
-webkit-animation-timing-function: linear, ease-in-out; animation-iteration-count: infinite, infinite;
-webkit-animation-iteration-count: infinite, infinite; }
animation-name: christmas-fall, christmas-shake;
animation-duration: 10s, 3s;
animation-timing-function: linear, ease-in-out;
animation-iteration-count: infinite, infinite; @keyframes christmas-fall {
} 0% {
translate: 0 -10vh;
@-webkit-keyframes christmas-fall { }
0% {
translate: 0 -10vh; 100% {
} translate: 0 110vh;
}
100% { }
translate: 0 110vh;
} @keyframes christmas-shake {
} 0%,
100% {
@-webkit-keyframes christmas-shake { transform: translateX(0);
}
0%,
100% { 50% {
transform: translateX(0); transform: translateX(80px);
} }
}
50% {
transform: translateX(80px); .christmas:nth-of-type(0) {
} left: 0%;
} animation-delay: 0s, 0s;
}
@keyframes christmas-fall {
0% { .christmas:nth-of-type(1) {
translate: 0 -10vh; left: 10%;
} animation-delay: 1s, 1s;
}
100% {
translate: 0 110vh; .christmas:nth-of-type(2) {
} left: 20%;
} animation-delay: 6s, 0.5s;
}
@keyframes christmas-shake {
0%, .christmas:nth-of-type(3) {
100% { left: 30%;
transform: translateX(0); animation-delay: 4s, 2s;
} }
50% { .christmas:nth-of-type(4) {
transform: translateX(80px); left: 40%;
} animation-delay: 2s, 2s;
} }
.christmas:nth-of-type(0) { .christmas:nth-of-type(5) {
left: 0%; left: 50%;
animation-delay: 0s, 0s; animation-delay: 8s, 3s;
} }
.christmas:nth-of-type(1) { .christmas:nth-of-type(6) {
left: 10%; left: 60%;
animation-delay: 1s, 1s; animation-delay: 6s, 2s;
} }
.christmas:nth-of-type(2) { .christmas:nth-of-type(7) {
left: 20%; left: 70%;
animation-delay: 6s, 0.5s; animation-delay: 2.5s, 1s;
} }
.christmas:nth-of-type(3) { .christmas:nth-of-type(8) {
left: 30%; left: 80%;
animation-delay: 4s, 2s; animation-delay: 1s, 0s;
} }
.christmas:nth-of-type(4) { .christmas:nth-of-type(9) {
left: 40%; left: 90%;
animation-delay: 2s, 2s; animation-delay: 3s, 1.5s;
} }
.christmas:nth-of-type(5) { .christmas:nth-of-type(10) {
left: 50%; left: 25%;
animation-delay: 8s, 3s; animation-delay: 2s, 0s;
} }
.christmas:nth-of-type(6) { .christmas:nth-of-type(11) {
left: 60%; left: 65%;
animation-delay: 6s, 2s;
}
.christmas:nth-of-type(7) {
left: 70%;
animation-delay: 2.5s, 1s;
}
.christmas:nth-of-type(8) {
left: 80%;
animation-delay: 1s, 0s;
}
.christmas:nth-of-type(9) {
left: 90%;
animation-delay: 3s, 1.5s;
}
.christmas:nth-of-type(10) {
left: 25%;
animation-delay: 2s, 0s;
}
.christmas:nth-of-type(11) {
left: 65%;
animation-delay: 4s, 2.5s;
animation-delay: 4s, 2.5s; animation-delay: 4s, 2.5s;

View File

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

View File

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

View File

@@ -197,6 +197,8 @@ function animateRabbit(rabbit) {
rabbit.style.transition = 'none'; rabbit.style.transition = 'none';
const transformScale = startFromLeft ? 'scaleX(-1)' : ''; const transformScale = startFromLeft ? 'scaleX(-1)' : '';
// Fix bounding box center-of-gravity shift when graphic is flipped
rabbit.style.transformOrigin = startFromLeft ? '59% 50%' : '50% 50%';
rabbit.style.transform = `translateX(${currentX}vw) ${transformScale}`; rabbit.style.transform = `translateX(${currentX}vw) ${transformScale}`;
const loopDurationMs = jumpDurationMs + pauseDurationMs; const loopDurationMs = jumpDurationMs + pauseDurationMs;
@@ -211,10 +213,10 @@ function animateRabbit(rabbit) {
if (!startTime) { if (!startTime) {
startTime = timestamp; startTime = timestamp;
// resetting gif, forces the browser to restart the GIF from the first frame (crucial for syncing hops with movement) // resetting gif, appending a timestamp cache-buster forces the browser
// to reload and start the GIF strictly from the first frame.
const currSrc = rabbit.src.split('?')[0]; const currSrc = rabbit.src.split('?')[0];
rabbit.src = ''; rabbit.src = currSrc + '?t=' + Date.now();
rabbit.src = currSrc;
} }
const elapsed = timestamp - startTime; const elapsed = timestamp - startTime;
@@ -243,6 +245,7 @@ function animateRabbit(rabbit) {
isAnimating = false; isAnimating = false;
rabbitTimeout = setTimeout(() => { rabbitTimeout = setTimeout(() => {
if (!document.body.contains(rabbit)) return;
animateRabbit(document.querySelector('#rabbit')); animateRabbit(document.querySelector('#rabbit'));
}, restTime); }, restTime);
return; return;

View File

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

View File

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

View File

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

View File

@@ -162,6 +162,7 @@ function startFireworks() {
} }
fireworksInterval = setInterval(() => { fireworksInterval = setInterval(() => {
if (!document.body.contains(fireworkContainer)) { clearInterval(fireworksInterval); return; }
const randomCount = Math.floor(Math.random() * maxFireworks) + minFireworks; const randomCount = Math.floor(Math.random() * maxFireworks) + minFireworks;
for (let i = 0; i < randomCount; i++) { for (let i = 0; i < randomCount; i++) {
setTimeout(() => { setTimeout(() => {

View File

@@ -61,12 +61,12 @@ function createFriday13(container) {
cat.parentNode.removeChild(cat); cat.parentNode.removeChild(cat);
} }
// Respawn with random delay between 5 to 25 seconds // Respawn with random delay between 5 to 25 seconds
setTimeout(spawnCat, Math.random() * 20000 + 5000); setTimeout(() => { if (document.body.contains(container)) spawnCat(); }, Math.random() * 20000 + 5000);
}, (catWalkDurationSeconds * 1000) + 500); // Wait for duration + 500ms safety margin }, (catWalkDurationSeconds * 1000) + 500); // Wait for duration + 500ms safety margin
} }
// Initial spawn with random delay // Initial spawn with random delay
setTimeout(spawnCat, Math.random() * 5000); setTimeout(() => { if (document.body.contains(container)) spawnCat(); }, Math.random() * 5000);
} }
function initializeFriday13() { function initializeFriday13() {

View File

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

View File

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

View File

@@ -182,14 +182,18 @@ function createSpider(container) {
setTimeout(() => { setTimeout(() => {
wrapper.remove(); wrapper.remove();
setTimeout(() => createSpider(container), Math.random() * 5000 + 1000); if (document.body.contains(container)) {
setTimeout(() => createSpider(container), Math.random() * 5000 + 1000);
}
}, 500); }, 500);
}); });
wrapper.addEventListener('animationend', () => { wrapper.addEventListener('animationend', () => {
if (isRetreating) return; if (isRetreating) return;
wrapper.remove(); wrapper.remove();
setTimeout(() => createSpider(container), Math.random() * 5000 + 1000); if (document.body.contains(container)) {
setTimeout(() => createSpider(container), Math.random() * 5000 + 1000);
}
}); });
container.appendChild(wrapper); container.appendChild(wrapper);
@@ -223,7 +227,9 @@ function createMouse(container) {
mouse.addEventListener('animationend', () => { mouse.addEventListener('animationend', () => {
mouse.remove(); mouse.remove();
setTimeout(() => createMouse(container), Math.random() * 4000 + 2000); if (document.body.contains(container)) {
setTimeout(() => createMouse(container), Math.random() * 4000 + 2000);
}
}); });
container.appendChild(mouse); container.appendChild(mouse);

View File

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

View File

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

View File

@@ -36,8 +36,12 @@ observer.observe(document.body, {
function createMarioDay(container) { function createMarioDay(container) {
// MARK: Mario's running speed across the screen
const marioSpeedSeconds = 18;
const wrapper = document.createElement('div'); const wrapper = document.createElement('div');
wrapper.className = 'mario-wrapper'; wrapper.className = 'mario-wrapper';
wrapper.style.animationDuration = `${marioSpeedSeconds}s`;
const mario = document.createElement('img'); const mario = document.createElement('img');
mario.className = 'mario-runner'; mario.className = 'mario-runner';
@@ -47,15 +51,16 @@ function createMarioDay(container) {
container.appendChild(wrapper); container.appendChild(wrapper);
// Periodically throw out an 8-bit coin // Periodically throw out an 8-bit coin
setInterval(() => { const intervalId = setInterval(() => {
if (!document.querySelector('.marioday-container')) return; if (!document.body.contains(container)) { clearInterval(intervalId); return; }
if (container.style.display === 'none') return;
const coin = document.createElement('div'); const coin = document.createElement('div');
coin.className = 'mario-coin'; coin.className = 'mario-coin';
// Grab Mario's current screen position to lock the coin's X coordinate // Grab Mario's current screen position to lock the coin's X coordinate
const marioRect = wrapper.getBoundingClientRect(); const marioRect = wrapper.getBoundingClientRect();
coin.style.left = `${marioRect.left + 16}px`; coin.style.left = `${marioRect.left + 16}px`;
coin.style.bottom = '35px'; // Adjust for wrapper's bottom offset coin.style.bottom = '35px'; // bottom offset
container.appendChild(coin); container.appendChild(coin);
setTimeout(() => coin.remove(), 2000); setTimeout(() => coin.remove(), 2000);

View File

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

View File

@@ -136,6 +136,7 @@ function createElements() {
for(let i=0; i<maxTrails; i++) trails.push(new Trail()); for(let i=0; i<maxTrails; i++) trails.push(new Trail());
function loop() { function loop() {
if (!document.body.contains(container)) { clearInterval(window.matrixInterval); return; }
if (isHidden) return; // Pause drawing when hidden if (isHidden) return; // Pause drawing when hidden
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = 'bold ' + fontSize + 'px monospace'; ctx.font = 'bold ' + fontSize + 'px monospace';

View File

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

View File

@@ -12,11 +12,52 @@
.olympia-symbol { .olympia-symbol {
position: absolute; position: absolute;
top: -10vh; top: 0;
animation: olympia-fall linear infinite;
font-size: 3rem;
opacity: 0.95; opacity: 0.95;
text-shadow: 0 0 10px rgba(255,255,255,0.2); text-shadow: 0 0 10px rgba(255,255,255,0.2);
z-index: 40;
translate: 0 -10vh;
}
.olympia-flame {
position: absolute;
bottom: 0vh;
z-index: 50;
pointer-events: none;
transform-origin: bottom center;
}
.olympia-ring-css {
position: relative;
width: 40px;
height: 40px;
}
.olympia-ring-css::before {
content: '';
position: absolute;
top: 0;
translate: -50% -50%;
width: 30px;
height: 30px;
border: 5px solid #0081C8; /* Default blue ring */
border-radius: 50%;
}
.olympia-ring-css[style*="--ring-color"]::before {
border-color: var(--ring-color);
}
.olympia-symbol {
position: absolute;
top: 0;
opacity: 0.95;
text-shadow: 0 0 10px rgba(255,255,255,0.2);
z-index: 40;
translate: 0 -10vh;
}
.olympia-inner {
will-change: transform;
display: inline-block;
animation: olympia-sway linear infinite alternate;
} }
.olympia-symbol img { .olympia-symbol img {
@@ -26,46 +67,76 @@
object-fit: contain; object-fit: contain;
} }
.olympia-confetti-wrapper {
position: fixed;
z-index: 15;
top: 0;
will-change: transform;
animation-name: olympia-fall;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
.olympia-confetti-sway {
will-change: transform;
animation-name: olympia-confetti-sway;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
animation-direction: alternate;
}
@keyframes olympia-confetti-sway {
0% { transform: translateX(calc(var(--sway-amount, 50px) * -1)); }
100% { transform: translateX(var(--sway-amount, 50px)); }
}
.olympia-confetti { .olympia-confetti {
position: absolute;
top: -5vh;
width: 8px; width: 8px;
height: 16px; height: 16px;
opacity: 0.85; background-color: rgb(0, 0, 0);
animation: olympia-confetti-fall linear infinite; will-change: transform;
border-radius: 4px; /* slightly rounder confetti */ animation-name: olympia-confetti-flutter;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
.olympia-confetti.circle {
width: 8px;
height: 8px;
border-radius: 50%;
}
.olympia-confetti.square {
width: 8px;
height: 8px;
}
.olympia-confetti.triangle {
width: 10px;
height: 10px;
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
} }
@keyframes olympia-fall { @keyframes olympia-fall {
0% { 0% { transform: translateY(-10vh); }
transform: translateY(-10vh) rotate(var(--start-rot, 0deg)); 100% { transform: translateY(110vh); }
opacity: 0;
}
10% {
opacity: 1;
}
85% {
opacity: 1;
}
100% {
transform: translateY(110vh) rotate(var(--end-rot, 360deg));
opacity: 0;
}
} }
@keyframes olympia-confetti-fall { @keyframes olympia-sway {
0% { 0% { transform: rotate(-25deg) translateX(-20px); }
transform: translateY(-5vh) rotateX(0deg) rotateY(0deg); 100% { transform: rotate(25deg) translateX(20px); }
opacity: 0; }
}
5% { @keyframes olympia-tumble-3d {
opacity: 1; 0% { transform: rotate3d(calc(var(--rot-x) + 0.001), calc(var(--rot-y) + 0.001), calc(var(--rot-z) + 0.001), 0deg); }
} 100% { transform: rotate3d(calc(var(--rot-x) + 0.001), calc(var(--rot-y) + 0.001), calc(var(--rot-z) + 0.001), 360deg); }
90% { }
opacity: 1;
} @keyframes olympia-confetti-flutter {
100% { 0% {
transform: translateY(105vh) rotateX(720deg) rotateY(360deg); transform: rotate3d(var(--rx, 1), var(--ry, 1), var(--rz, 0), 0deg);
opacity: 0; }
} 100% {
transform: rotate3d(var(--rx, 1), var(--ry, 1), var(--rz, 0), var(--rot-dir, 360deg));
}
} }

View File

@@ -60,72 +60,189 @@ function createOlympia() {
const useRandomDuration = enableDifferentDuration !== false; const useRandomDuration = enableDifferentDuration !== false;
const activeItems = ['gold', 'silver', 'bronze', 'torch']; const activeItems = ['gold', 'silver', 'bronze', 'torch', 'rings_blue', 'rings_yellow', 'rings_black', 'rings_green', 'rings_red'];
for (let i = 0; i < finalCount; i++) { for (let i = 0; i < finalCount; i++) {
let symbol = document.createElement('div'); let symbol = document.createElement('div');
const randomItem = activeItems[Math.floor(Math.random() * activeItems.length)]; const randomItem = activeItems[Math.floor(Math.random() * activeItems.length)];
const isRing = randomItem.startsWith('rings');
const isMedal = ['gold', 'silver', 'bronze'].includes(randomItem);
symbol.className = `olympia-symbol olympia-${randomItem}`; symbol.className = `olympia-symbol olympia-${randomItem}`;
let img = document.createElement('img'); // Create inner div for sway/rotation
img.src = `../Seasonals/Resources/olympia_images/${randomItem}.png`; let innerDiv = document.createElement('div');
img.onerror = function() { innerDiv.className = 'olympia-inner';
this.style.display = 'none'; let img = null;
this.parentElement.innerHTML = getOlympiaEmojiFallback(randomItem);
};
symbol.appendChild(img);
const leftPos = Math.random() * 100; if (isRing) {
const delaySeconds = Math.random() * 10; const ringColorMap = {
'rings_blue': '#0081C8',
let durationSeconds = 8; 'rings_yellow': '#FCB131',
if (useRandomDuration) { 'rings_black': '#000000',
durationSeconds = Math.random() * 5 + 6; // 6 to 11 seconds 'rings_green': '#00A651',
'rings_red': '#EE334E'
};
let ringDiv = document.createElement('div');
ringDiv.className = 'olympia-ring-css';
ringDiv.style.setProperty('--ring-color', ringColorMap[randomItem]);
innerDiv.appendChild(ringDiv);
// Add a 3D flip animation for rings and medals
const spinReverse = Math.random() > 0.5 ? 'reverse' : 'normal';
innerDiv.style.animation = `olympia-tumble-3d ${Math.random() * 4 + 4}s linear infinite ${spinReverse}`;
// Random 3D Rotation Axis for Tumbling
innerDiv.style.setProperty('--rot-x', (Math.random() * 2 - 1).toFixed(2));
innerDiv.style.setProperty('--rot-y', (Math.random() * 2 - 1).toFixed(2));
innerDiv.style.setProperty('--rot-z', (Math.random() * 2 - 1).toFixed(2));
} else {
img = document.createElement('img');
let imgName = randomItem;
if (isMedal) {
imgName = `${randomItem}_coin.gif`;
} else {
imgName = `${randomItem}.png`;
}
img.src = `../Seasonals/Resources/olympic_assets/${imgName}`;
img.onerror = function() {
symbol.remove();
};
innerDiv.appendChild(img);
if (isMedal) {
innerDiv.style.animation = `olympia-flip-3d ${Math.random() * 4 + 3}s linear infinite`;
} else {
// Torch sways, medals flip
const swayDur = Math.random() * 2 + 2; // 2 to 4s
const swayDir = Math.random() > 0.5 ? 'normal' : 'reverse';
innerDiv.style.animation = `olympia-sway ${swayDur}s ease-in-out infinite alternate ${swayDir}`;
}
} }
const startRot = Math.random() * 360; symbol.appendChild(innerDiv);
symbol.style.setProperty('--start-rot', `${startRot}deg`);
symbol.style.setProperty('--end-rot', `${startRot + (Math.random() > 0.5 ? 360 : -360)}deg`);
symbol.style.left = `${leftPos}vw`; const leftPos = Math.random() * 95;
symbol.style.animationDuration = `${durationSeconds}s`; const delaySeconds = Math.random() * 10;
// Depth logic for medals and rings
const depth = Math.random();
const scale = 0.8 + depth * 0.4; // 0.8 to 1.2
const zIndex = Math.floor(depth * 30) + 10;
if (img) {
img.style.transform = `scale(${scale})`;
} else {
innerDiv.firstChild.style.transform = `scale(${scale})`;
}
symbol.style.zIndex = zIndex;
let durationSeconds = 8;
if (useRandomDuration) {
durationSeconds = (1 - depth) * 5 + 6 + Math.random() * 4;
}
symbol.style.animation = `olympia-fall ${durationSeconds}s linear infinite`;
symbol.style.animationDelay = `${delaySeconds}s`; symbol.style.animationDelay = `${delaySeconds}s`;
symbol.style.left = `${leftPos}vw`;
container.appendChild(symbol); container.appendChild(symbol);
} }
// Olympic Ring Colors // Olympic Torches (Fixed at bottom corners, symmetrically rotated inward)
// Generate one random inward rotation (10 to 25 deg) for both to share
const sharedTilt = Math.random() * 15 + 10;
const createTorch = (isLeft) => {
const torch = document.createElement('div');
torch.className = 'olympia-flame';
if (isLeft) {
torch.style.left = '5vw';
// Lean right, face normal
torch.style.transform = `rotate(${sharedTilt}deg) scaleX(1)`;
} else {
torch.style.right = '5vw';
// Lean left, mirror image
torch.style.transform = `rotate(-${sharedTilt}deg) scaleX(-1)`;
}
let torchImg = document.createElement('img');
torchImg.src = `../Seasonals/Resources/olympic_assets/torch.gif`;
torchImg.style.height = '25vh';
torchImg.style.objectFit = 'contain';
torchImg.onerror = function() {
this.style.display = 'none';
};
torch.appendChild(torchImg);
container.appendChild(torch);
};
createTorch(true);
createTorch(false);
// Olympic Ring Colors (Carnival Config)
const confettiColors = ['#0081C8', '#FCB131', '#000000', '#00A651', '#EE334E']; const confettiColors = ['#0081C8', '#FCB131', '#000000', '#00A651', '#EE334E'];
const confettiCount = isMobile ? 30 : 60; const confettiCount = isMobile ? 30 : 60;
for (let i = 0; i < confettiCount; i++) { for (let i = 0; i < confettiCount; i++) {
let wrapper = document.createElement('div');
wrapper.className = 'olympia-confetti-wrapper';
let leftPos = Math.random() * 100;
wrapper.style.left = `${leftPos}vw`;
let fallDuration = Math.random() * 3 + 4; // 4 to 7 seconds to fall
wrapper.style.animationDuration = `${fallDuration}s`;
wrapper.style.animationDelay = `-${Math.random() * fallDuration}s`; // Negative delay so it distributes perfectly immediately
let swayWrapper = document.createElement('div');
swayWrapper.className = 'olympia-confetti-sway';
let swayDuration = Math.random() * 2 + 1.5; // 1.5s to 3.5s
swayWrapper.style.animationDuration = `${swayDuration}s`;
let swayAmount = Math.random() * 30 + 30; // 30px to 60px
swayWrapper.style.setProperty('--sway-amount', `${swayAmount}px`);
let initSwayDelay = Math.random() * swayDuration;
swayWrapper.style.animationDelay = `-${initSwayDelay}s`;
let confetti = document.createElement('div'); let confetti = document.createElement('div');
confetti.className = 'olympia-confetti'; confetti.className = 'olympia-confetti';
const color = confettiColors[Math.floor(Math.random() * confettiColors.length)]; const color = confettiColors[Math.floor(Math.random() * confettiColors.length)];
confetti.style.backgroundColor = color; confetti.style.backgroundColor = color;
const leftPos = Math.random() * 100; // Random shape
const delaySeconds = Math.random() * 8; const shape = Math.random();
const duration = Math.random() * 3 + 5; if (shape > 0.66) {
confetti.classList.add('circle');
const size = Math.random() * 5 + 5;
confetti.style.width = `${size}px`;
confetti.style.height = `${size}px`;
} else if (shape > 0.33) {
confetti.classList.add('rect');
const width = Math.random() * 4 + 4;
const height = Math.random() * 5 + 8;
confetti.style.width = `${width}px`;
confetti.style.height = `${height}px`;
} else {
confetti.classList.add('triangle');
}
// Random 3D Rotation for flutter
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));
confetti.style.setProperty('--rot-dir', `${(Math.random() > 0.5 ? 1 : -1) * 360}deg`);
let rotateDuration = Math.random() * 0.8 + 0.4;
confetti.style.animationDuration = `${rotateDuration}s`;
confetti.style.left = `${leftPos}vw`; swayWrapper.appendChild(confetti);
confetti.style.animationDuration = `${duration}s`; wrapper.appendChild(swayWrapper);
confetti.style.animationDelay = `${delaySeconds}s`; container.appendChild(wrapper);
container.appendChild(confetti);
} }
} }
function getOlympiaEmojiFallback(type) {
if (type === 'gold') return '🥇';
if (type === 'silver') return '🥈';
if (type === 'bronze') return '🥉';
if (type === 'torch') return '🔥';
return '';
}
function initializeOlympia() { function initializeOlympia() {
if (!olympia) return; if (!olympia) return;
createOlympia(); createOlympia();

View File

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

View File

@@ -56,9 +56,9 @@ function createOscar(container) {
container.appendChild(carpet); container.appendChild(carpet);
container.appendChild(spotlights); container.appendChild(spotlights);
// Paparazzi flashes with randomized intervals
function flashLoop() { function flashLoop() {
if (!document.querySelector('.oscar-container')) { if (!document.body.contains(container)) return; // Kill the loop if container is removed
if (container.style.display === 'none') {
setTimeout(flashLoop, 1000); // Check again later if hidden setTimeout(flashLoop, 1000); // Check again later if hidden
return; return;
} }

View File

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

View File

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

View File

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

View File

@@ -238,7 +238,7 @@ function animateSanta() {
function startAnimation() { function startAnimation() {
const santaHeight = santa.offsetHeight; const santaHeight = santa.offsetHeight;
if (santaHeight === 0) { if (santaHeight === 0) {
setTimeout(startAnimation, 100); setTimeout(() => { if (document.body.contains(santa)) startAnimation(); }, 100);
return; return;
} }
// console.log('Santa height: ', santaHeight); // console.log('Santa height: ', santaHeight);
@@ -283,7 +283,7 @@ function animateSanta() {
animationFrameIdSanta = requestAnimationFrame(move); animationFrameIdSanta = requestAnimationFrame(move);
} else { } else {
const pause = Math.random() * ((maxSantaRestTime - minSantaRestTime) * 1000) + minSantaRestTime * 1000; const pause = Math.random() * ((maxSantaRestTime - minSantaRestTime) * 1000) + minSantaRestTime * 1000;
setTimeout(animateSanta, pause); setTimeout(() => { if (document.body.contains(santa)) animateSanta(); }, pause);
} }
} }

View File

@@ -1,139 +1,112 @@
.snowflakes { .snowflakes {
display: block; display: block;
position: fixed; position: fixed;
overflow: hidden; overflow: hidden;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none; pointer-events: none;
z-index: 10; z-index: 10;
contain: layout paint; contain: layout paint;
} }
.snowflake { .snowflake {
position: fixed; position: fixed;
z-index: 15; z-index: 15;
top: 0; top: 0;
will-change: transform; will-change: transform;
translate: 0 -10vh; translate: 0 -10vh;
font-size: 1em; font-size: 1em;
color: #fff; color: #fff;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
text-shadow: 0 0 5px #000; text-shadow: 0 0 5px #000;
user-select: none; user-select: none;
-webkit-user-select: none; cursor: default;
cursor: default; animation-name: snowflakes-fall, snowflakes-shake;
-webkit-animation-name: heart-fall, heart-shake; animation-duration: 12s, 3s;
-webkit-animation-duration: 12s, 3s; animation-timing-function: linear, ease-in-out;
-webkit-animation-timing-function: linear, ease-in-out; animation-iteration-count: infinite, infinite;
-webkit-animation-iteration-count: infinite, infinite; }
animation-name: snowflakes-fall, snowflakes-shake;
animation-duration: 12s, 3s;
animation-timing-function: linear, ease-in-out;
animation-iteration-count: infinite, infinite; @keyframes snowflakes-fall {
} 0% {
translate: 0 -10vh;
@-webkit-keyframes snowflakes-fall { }
0% {
translate: 0 -10vh; 100% {
} translate: 0 110vh;
}
100% { }
translate: 0 110vh;
} @keyframes snowflakes-shake {
}
0%,
@-webkit-keyframes snowflakes-shake { 100% {
transform: translateX(0);
0%, }
100% {
transform: translateX(0); 50% {
} transform: translateX(80px);
}
50% { }
transform: translateX(80px);
} .snowflake:nth-of-type(0) {
} left: 0%;
animation-delay: 0s, 0s;
@keyframes snowflakes-fall { }
0% {
translate: 0 -10vh; .snowflake:nth-of-type(1) {
} left: 10%;
animation-delay: 1s, 1s;
100% { }
translate: 0 110vh;
} .snowflake:nth-of-type(2) {
} left: 20%;
animation-delay: 6s, 0.5s;
@keyframes snowflakes-shake { }
0%, .snowflake:nth-of-type(3) {
100% { left: 30%;
transform: translateX(0); animation-delay: 4s, 2s;
} }
50% { .snowflake:nth-of-type(4) {
transform: translateX(80px); left: 40%;
} animation-delay: 2s, 2s;
} }
.snowflake:nth-of-type(0) { .snowflake:nth-of-type(5) {
left: 0%; left: 50%;
animation-delay: 0s, 0s; animation-delay: 8s, 3s;
} }
.snowflake:nth-of-type(1) { .snowflake:nth-of-type(6) {
left: 10%; left: 60%;
animation-delay: 1s, 1s; animation-delay: 6s, 2s;
} }
.snowflake:nth-of-type(2) { .snowflake:nth-of-type(7) {
left: 20%; left: 70%;
animation-delay: 6s, 0.5s; animation-delay: 2.5s, 1s;
} }
.snowflake:nth-of-type(3) { .snowflake:nth-of-type(8) {
left: 30%; left: 80%;
animation-delay: 4s, 2s; animation-delay: 1s, 0s;
} }
.snowflake:nth-of-type(4) { .snowflake:nth-of-type(9) {
left: 40%; left: 90%;
animation-delay: 2s, 2s; animation-delay: 3s, 1.5s;
} }
.snowflake:nth-of-type(5) { .snowflake:nth-of-type(10) {
left: 50%; left: 25%;
animation-delay: 8s, 3s; animation-delay: 2s, 0s;
} }
.snowflake:nth-of-type(6) { .snowflake:nth-of-type(11) {
left: 60%;
animation-delay: 6s, 2s;
}
.snowflake:nth-of-type(7) {
left: 70%;
animation-delay: 2.5s, 1s;
}
.snowflake:nth-of-type(8) {
left: 80%;
animation-delay: 1s, 0s;
}
.snowflake:nth-of-type(9) {
left: 90%;
animation-delay: 3s, 1.5s;
}
.snowflake:nth-of-type(10) {
left: 25%;
animation-delay: 2s, 0s;
}
.snowflake:nth-of-type(11) {
left: 65%;
animation-delay: 4s, 2.5s;
left: 65%; left: 65%;

View File

@@ -1,6 +1,6 @@
const config = window.SeasonalsPluginConfig?.Snowstorm || {}; const config = window.SeasonalsPluginConfig?.Snowstorm || {};
const snowstorm = config.enableSnowstorm !== undefined ? config.EnableSnowstorm : true; // enable/disable snowstorm const snowstorm = config.EnableSnowstorm !== undefined ? config.EnableSnowstorm : true; // enable/disable snowstorm
let snowflakesCount = config.SnowflakesCount || 500; // count of snowflakes (recommended values: 300-600) let snowflakesCount = config.SnowflakesCount || 500; // count of snowflakes (recommended values: 300-600)
const snowflakesCountMobile = config.SnowflakesCountMobile || 250; // count of snowflakes on mobile devices (Warning: High values may affect performance) const snowflakesCountMobile = config.SnowflakesCountMobile || 250; // count of snowflakes on mobile devices (Warning: High values may affect performance)
const snowFallSpeed = config.Speed || 6; // speed of snowfall (recommended values: 4-8) const snowFallSpeed = config.Speed || 6; // speed of snowfall (recommended values: 4-8)

View File

@@ -5,54 +5,97 @@
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
pointer-events: none; pointer-events: none;
z-index: 9999; z-index: 10;
overflow: hidden; overflow: hidden;
contain: strict; contain: strict;
} }
.space-bg-glow {
will-change: transform;
position: absolute;
top: 0; left: 0; width: 100vw; height: 100vh;
background: radial-gradient(circle at 70% 30%, rgba(138, 43, 226, 0.15), transparent 60%),
radial-gradient(circle at 20% 80%, rgba(65, 105, 225, 0.15), transparent 50%);
pointer-events: none;
z-index: 10;
animation: space-nebula-pulse 10s ease-in-out infinite alternate;
}
@keyframes space-nebula-pulse {
0% { opacity: 0.6; }
100% { opacity: 1; }
}
.space-starfield {
position: absolute;
top: 0; left: 0; width: 100vw; height: 100vh;
background: transparent;
z-index: 11;
}
.space-shooting-star {
will-change: opacity;
position: absolute;
width: 250px;
height: 3px;
background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 100%);
border-radius: 50%;
animation: space-shoot 25s linear infinite;
opacity: 0;
z-index: 12;
}
@keyframes space-shoot {
0% { transform: rotate(var(--shoot-angle)) translateX(0); opacity: 0; }
5% { opacity: 1; }
35% { opacity: 1; }
40% { transform: rotate(var(--shoot-angle)) translateX(var(--shoot-distance)); opacity: 0; }
100% { transform: rotate(var(--shoot-angle)) translateX(var(--shoot-distance)); opacity: 0; }
}
.space-symbol { .space-symbol {
position: absolute; position: absolute;
animation-timing-function: linear; animation-timing-function: linear;
animation-iteration-count: infinite; animation-iteration-count: infinite;
font-size: 3rem; font-size: 3rem;
opacity: 0.85; opacity: 0.85;
z-index: 9999; z-index: 20;
} }
.space-symbol img { .space-symbol img {
will-change: transform;
width: 6vh; width: 6vh;
height: auto; height: auto;
max-width: 60px; max-width: 60px;
object-fit: contain; object-fit: contain;
/* Add a slow spin to images */
animation: space-slow-spin var(--rot-dur, 20s) linear infinite; animation: space-slow-spin var(--rot-dur, 20s) linear infinite;
} }
/* Specific elements scaling */ /* Specific elements scaling */
.space-planet1, .space-planet2 { font-size: 4rem; } .space-planet img { width: 8vh; max-width: 80px; }
.space-planet1 img, .space-planet2 img { width: 8vh; max-width: 80px; } .space-astronaut img { width: 10vh; max-width: 100px; }
.space-star { font-size: 2rem; opacity: 0.6; } .space-satellite img { width: 12vh; max-width: 120px; }
.space-star img { width: 3vh; max-width: 30px; } .space-iss img { width: 25vh; max-width: 180px; }
.space-rocket img { width: 12vh; max-width: 120px; }
@keyframes space-drift-right { @keyframes space-drift-right {
0% { 0% { transform: translateX(-10vw) translateY(0) scaleX(-1); }
transform: translateX(0) scaleX(-1); 50% { transform: translateX(60vw) translateY(-30vh) scaleX(-1); }
} 100% { transform: translateX(140vw) translateY(0) scaleX(-1); }
100% {
transform: translateX(120vw) scaleX(-1);
}
} }
@keyframes space-drift-left { @keyframes space-drift-left {
0% { 0% { transform: translateX(10vw) translateY(0); }
transform: translateX(0); 50% { transform: translateX(-60vw) translateY(30vh); }
} 100% { transform: translateX(-140vw) translateY(0); }
100% {
transform: translateX(-120vw);
}
} }
@keyframes space-slow-spin { @keyframes space-slow-spin {
0% { transform: rotate(0deg); } 0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); } 100% { transform: rotate(360deg); }
} }
@keyframes space-star-drift {
from { transform: translateY(0); }
to { transform: translateY(-100vh); }
}

View File

@@ -1,11 +1,41 @@
const config = window.SeasonalsPluginConfig?.Space || {}; const config = window.SeasonalsPluginConfig?.Space || {};
const space = config.EnableSpace !== undefined ? config.EnableSpace : true; const space = config.EnableSpace !== undefined ? config.EnableSpace : true;
const symbolCount = config.SymbolCount || 25; const planetCountConf = config.PlanetCount !== undefined ? config.PlanetCount : 6;
const astronautCountConf = config.AstronautCount !== undefined ? config.AstronautCount : 1;
const satelliteCountConf = config.SatelliteCount !== undefined ? config.SatelliteCount : 4;
const issCountConf = config.IssCount !== undefined ? config.IssCount : 1;
const rocketCountConf = config.RocketCount !== undefined ? config.RocketCount : 1;
const useRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true; const useRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true;
const enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false; const enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false;
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true;
const astronautImages = [
"../Seasonals/Resources/space_assets/astronaut_1.gif"
];
const planetImages = [
"../Seasonals/Resources/space_assets/planet_1.png",
"../Seasonals/Resources/space_assets/planet_2.png",
"../Seasonals/Resources/space_assets/planet_3.png",
"../Seasonals/Resources/space_assets/planet_4.png",
"../Seasonals/Resources/space_assets/planet_5.png",
"../Seasonals/Resources/space_assets/planet_6.png",
"../Seasonals/Resources/space_assets/planet_7.png",
"../Seasonals/Resources/space_assets/planet_8.png",
"../Seasonals/Resources/space_assets/planet_9.png"
];
const satelliteImages = [
"../Seasonals/Resources/space_assets/Satellite_1.gif",
"../Seasonals/Resources/space_assets/Satellite_2.gif"
];
const issImage = "../Seasonals/Resources/space_assets/iss.png";
const rocketImages = [
"../Seasonals/Resources/space_assets/rocket.gif",
"../Seasonals/Resources/space_assets/space-shuttle.png"
]
let msgPrinted = false; let msgPrinted = false;
function toggleSpace() { function toggleSpace() {
@@ -48,71 +78,199 @@ function createSpace() {
document.body.appendChild(container); document.body.appendChild(container);
} }
const standardCount = 15; const standardPlanetCount = 4;
const totalSymbols = symbolCount + standardCount; const standardAstronautCount = 1;
const standardSatelliteCount = 2;
let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches; const standardIssCount = 1;
let finalCount = totalSymbols; const standardRocketCount = 1;
if (isMobile) { let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
finalCount = enableRandomMobile ? totalSymbols : standardCount; let pCount = planetCountConf;
let aCount = astronautCountConf;
let sCount = satelliteCountConf;
let iCount = issCountConf;
let rCount = rocketCountConf;
if (isMobile && !enableRandomMobile) {
pCount = standardPlanetCount;
aCount = standardAstronautCount;
sCount = standardSatelliteCount;
iCount = standardIssCount;
rCount = standardRocketCount;
}
// Add Nebula Glow
const bgGlow = document.createElement('div');
bgGlow.className = 'space-bg-glow';
container.appendChild(bgGlow);
// Add CSS Starfield
const starfield = document.createElement('div');
starfield.className = 'space-starfield';
let boxShadows1 = [];
let boxShadows2 = [];
let boxShadows3 = [];
// Generate random stars for parallax starfield using CSS % / vw sizes for responsiveness
for (let i = 0; i < 150; i++) {
let x = (Math.random() * 100).toFixed(2);
let y = (Math.random() * 100).toFixed(2);
boxShadows1.push(`${x}vw ${y}vh #FFF`);
boxShadows1.push(`${x}vw ${(parseFloat(y) + 100).toFixed(2)}vh #FFF`);
}
for (let i = 0; i < 50; i++) {
let x = (Math.random() * 100).toFixed(2);
let y = (Math.random() * 100).toFixed(2);
boxShadows2.push(`${x}vw ${y}vh #FFF`);
boxShadows2.push(`${x}vw ${(parseFloat(y) + 100).toFixed(2)}vh #FFF`);
}
for (let i = 0; i < 20; i++) {
let x = (Math.random() * 100).toFixed(2);
let y = (Math.random() * 100).toFixed(2);
boxShadows3.push(`${x}vw ${y}vh #FFF`);
boxShadows3.push(`${x}vw ${(parseFloat(y) + 100).toFixed(2)}vh #FFF`);
}
const starLayer1 = document.createElement('div');
starLayer1.style.width = '1px'; starLayer1.style.height = '1px';
starLayer1.style.background = 'transparent';
starLayer1.style.boxShadow = boxShadows1.join(", ");
starLayer1.style.animation = 'space-star-drift 200s linear infinite';
starfield.appendChild(starLayer1);
const starLayer2 = document.createElement('div');
starLayer2.style.width = '2px'; starLayer2.style.height = '2px';
starLayer2.style.background = 'transparent';
starLayer2.style.boxShadow = boxShadows2.join(", ");
starLayer2.style.animation = 'space-star-drift 150s linear infinite';
starfield.appendChild(starLayer2);
const starLayer3 = document.createElement('div');
starLayer3.style.width = '3px'; starLayer3.style.height = '3px';
starLayer3.style.background = 'transparent';
starLayer3.style.boxShadow = boxShadows3.join(", ");
starLayer3.style.animation = 'space-star-drift 100s linear infinite';
starfield.appendChild(starLayer3);
container.appendChild(starfield);
// Shooting stars
const shootingStarCount = isMobile ? 1 : 2; // Less frequent
for (let i = 0; i < shootingStarCount; i++) {
const streak = document.createElement('div');
streak.className = 'space-shooting-star';
// Pick a random tail direction and fall direction to match
const isFromLeft = Math.random() > 0.5;
// Direction angle: random between 15deg-75deg (left) or 105deg-165deg (right)
// so they don't always fall in the exact same quadrant trajectory
let angle = isFromLeft
? Math.random() * 60 + 15
: Math.random() * 60 + 105;
streak.style.setProperty('--shoot-angle', `${angle}deg`);
const topStart = Math.random() * 50;
streak.style.left = isFromLeft ? '-20vw' : '120vw';
streak.style.top = `${topStart}vh`;
// Travel 200 viewport widths exactly along the rotated angle
streak.style.setProperty('--shoot-distance', '200vw');
streak.style.animationDelay = `${Math.random() * 20}s`;
// MARK: Shooting Star Speed
const flightCycleDuration = Math.random() * 10 + 15; // 15-25s
streak.style.animationDuration = `${flightCycleDuration}s`;
container.appendChild(streak);
} }
const useRandomDuration = enableDifferentDuration !== false; const useRandomDuration = enableDifferentDuration !== false;
const activeItems = ['planet1', 'planet2', 'star', 'astronaut', 'rocket']; function createSpaceItem(imageArr, cCount, addedClass) {
for (let i = 0; i < cCount; i++) {
let symbol = document.createElement('div');
const randomImage = imageArr[Math.floor(Math.random() * imageArr.length)];
symbol.className = `space-symbol ${addedClass}`;
for (let i = 0; i < finalCount; i++) { let img = document.createElement('img');
let symbol = document.createElement('div'); img.src = randomImage;
img.onerror = function() {
const randomItem = activeItems[Math.floor(Math.random() * activeItems.length)]; this.style.display = 'none';
symbol.className = `space-symbol space-${randomItem}`; };
symbol.appendChild(img);
let img = document.createElement('img'); const topPos = Math.random() * 90; // 0 to 90vh
img.src = `../Seasonals/Resources/space_images/${randomItem}.png`;
img.onerror = function() { // Zero gravity sizes / speeds
this.style.display = 'none'; const depth = Math.random();
this.parentElement.innerHTML = getSpaceEmojiFallback(randomItem); // Make background elements (depth close to 0) much smaller than foreground
}; const distanceScale = 0.15 + (depth * 0.85); // 0.15 to 1.0
symbol.appendChild(img);
symbol.style.zIndex = Math.floor(depth * 30) + 20;
const topPos = Math.random() * 90; // 0 to 90vh let durationSeconds = 30; // Very slow
const delaySeconds = Math.random() * 10; if (useRandomDuration) {
durationSeconds = (1 - depth) * 40 + 30 + Math.random() * 10 - 5;
let durationSeconds = 15; }
if (useRandomDuration) {
durationSeconds = Math.random() * 15 + 15; // 15 to 30 seconds for slow drift // Randomly pick direction: left-to-right OR right-to-left
const goRight = Math.random() > 0.5;
const baseTransformScale = goRight ? 'scaleX(-1)' : 'scaleX(1)';
if (goRight) {
symbol.style.animationName = 'space-drift-right';
symbol.style.left = '-20vw';
symbol.style.right = 'auto';
} else {
symbol.style.animationName = 'space-drift-left';
symbol.style.right = '-20vw';
symbol.style.left = 'auto';
}
symbol.style.top = `${topPos}vh`;
symbol.style.animationDuration = `${durationSeconds}s`;
// Negative delay correctly scatters them initially across the screen
// so they don't all appear to spawn from the edge at the start
const delaySeconds = -(Math.random() * durationSeconds);
symbol.style.animationDelay = `${delaySeconds}s`;
// Slow rotation inside inner div
const rotationDiv = document.createElement('div');
const rotDur = Math.random() * 20 + 20; // 20-40s spin
const spinReverse = Math.random() > 0.5 ? 'reverse' : 'normal';
rotationDiv.style.animation = `space-slow-spin ${rotDur}s linear infinite ${spinReverse}`;
// Apply final static scaling and facing to inner image directly
img.style.transform = `scale(${distanceScale}) ${baseTransformScale}`;
rotationDiv.appendChild(img);
symbol.appendChild(rotationDiv);
// Swap to a random image from the pool every time it completes an orbit (disappears)
if (imageArr.length > 1) {
// The animation delay pushes the initial cycle, so we use setInterval matched to duration
const intervalId = setInterval(() => {
if (!document.body.contains(container)) { clearInterval(intervalId); return; }
// Update only if currently out of bounds to avoid popping
const rect = symbol.getBoundingClientRect();
if (rect.right < 0 || rect.left > window.innerWidth) {
img.src = imageArr[Math.floor(Math.random() * imageArr.length)];
}
}, 2000); // Check occasionally if it's off screen
}
container.appendChild(symbol);
} }
// Randomly pick direction: left-to-right OR right-to-left
const goRight = Math.random() > 0.5;
if (goRight) {
symbol.style.animationName = 'space-drift-right';
symbol.style.left = '-10vw';
symbol.style.transform = 'scaleX(-1)'; // flip some items horizontally if moving right
} else {
symbol.style.animationName = 'space-drift-left';
symbol.style.right = '-10vw';
}
symbol.style.top = `${topPos}vh`;
symbol.style.animationDuration = `${durationSeconds}s`;
symbol.style.animationDelay = `${delaySeconds}s`;
// Add a slow rotation
symbol.style.setProperty('--rot-dur', `${durationSeconds}s`);
container.appendChild(symbol);
} }
}
function getSpaceEmojiFallback(type) { createSpaceItem(planetImages, pCount, 'space-planet');
if (type === 'planet1') return '🪐'; createSpaceItem(astronautImages, aCount, 'space-astronaut');
if (type === 'planet2') return '🌍'; createSpaceItem(satelliteImages, sCount, 'space-satellite');
if (type === 'star') return '⭐'; createSpaceItem([issImage], iCount, 'space-iss');
if (type === 'astronaut') return '👨‍🚀'; createSpaceItem(rocketImages, rCount, 'space-rocket');
if (type === 'rocket') return '🚀';
return '✨';
} }
function initializeSpace() { function initializeSpace() {

View File

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

View File

@@ -5,17 +5,21 @@
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
pointer-events: none; pointer-events: none;
z-index: 9999; z-index: 10;
overflow: hidden; overflow: hidden;
contain: strict; contain: strict;
} }
.sports-symbol { .sports-symbol {
position: absolute; position: absolute;
top: -10vh; top: 0;
animation: sports-fall linear infinite;
font-size: 3rem; /* Fallback emoji size */
opacity: 0.9; opacity: 0.9;
z-index: 40;
translate: 0 -10vh;
}
.sports-inner {
display: inline-block;
} }
.sports-symbol img { .sports-symbol img {
@@ -26,13 +30,28 @@
} }
.sports-confetti { .sports-confetti {
will-change: transform, opacity;
position: absolute; position: absolute;
top: -5vh; top: 0;
width: 10px; width: 10px;
height: 15px; height: 15px;
opacity: 0.8; opacity: 0.8;
animation: sports-confetti-fall linear infinite; animation: sports-confetti-fall linear infinite;
border-radius: 2px; border-radius: 2px;
translate: 0 -5vh;
}
.sports-confetti.circle {
border-radius: 50%;
}
.sports-confetti.triangle {
width: 0 !important;
height: 0 !important;
background-color: transparent !important;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-bottom: 10px solid var(--shape-color, #FFCC00);
} }
.sports-turf { .sports-turf {
@@ -46,26 +65,60 @@
z-index: 10; z-index: 10;
} }
@keyframes sports-fall { @keyframes sports-bounce {
0% { 0% {
transform: translateY(-10vh) rotate(var(--start-rot, 0deg)); transform: translateY(-10vh);
opacity: 0; opacity: 0;
animation-timing-function: ease-in;
} }
10% { 5% {
opacity: 1; opacity: 1;
} }
85% { 30% {
transform: translateY(85vh);
animation-timing-function: ease-out;
} /* hit ground, start bouncing up */
50% {
transform: translateY(40vh);
animation-timing-function: ease-in;
} /* peak of bounce, start falling */
70% {
transform: translateY(85vh);
animation-timing-function: ease-out;
} /* hit ground, bounce up */
85% {
transform: translateY(70vh);
animation-timing-function: ease-in;
} /* smaller peak */
95% {
opacity: 1; opacity: 1;
} }
100% { 100% {
transform: translateY(110vh) rotate(var(--end-rot, 360deg)); transform: translateY(110vh);
opacity: 0; opacity: 0;
} }
} }
@keyframes sports-fall {
0% { transform: translateY(-10vh); opacity: 0; }
5% { opacity: 1; }
90% { opacity: 1; }
100% { transform: translateY(110vh); opacity: 0; }
}
@keyframes sports-sway {
0% { transform: rotate(-15deg) translateX(-10px); }
100% { transform: rotate(15deg) translateX(10px); }
}
@keyframes sports-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(var(--spin-rot, 360deg)); }
}
@keyframes sports-confetti-fall { @keyframes sports-confetti-fall {
0% { 0% {
transform: translateY(-5vh) rotateX(0deg) rotateY(0deg); transform: translateY(-5vh) rotate3d(var(--rx), var(--ry), var(--rz), 0deg);
opacity: 0; opacity: 0;
} }
5% { 5% {
@@ -75,7 +128,25 @@
opacity: 1; opacity: 1;
} }
100% { 100% {
transform: translateY(105vh) rotateX(720deg) rotateY(360deg); transform: translateY(105vh) rotate3d(var(--rx), var(--ry), var(--rz), var(--rot-dir));
opacity: 0; opacity: 0;
} }
} }
@keyframes sports-arc-x-right {
0% { transform: translateX(0); }
100% { transform: translateX(130vw); }
}
@keyframes sports-arc-x-left {
0% { transform: translateX(0); }
100% { transform: translateX(-130vw); }
}
@keyframes sports-arc-y {
0% { transform: translateY(110vh) scale(0.5) rotate(-30deg); opacity: 0; animation-timing-function: ease-out; }
5% { opacity: 1; }
50% { transform: translateY(10vh) scale(1.5) rotate(0deg); animation-timing-function: ease-in; }
95% { opacity: 1; }
100% { transform: translateY(110vh) scale(0.5) rotate(30deg); opacity: 0; }
}

View File

@@ -1,10 +1,29 @@
const config = window.SeasonalsPluginConfig?.Sports || {}; const config = window.SeasonalsPluginConfig?.Sports || {};
const sports = config.EnableSports !== undefined ? config.EnableSports : true; const sports = config.EnableSports !== undefined ? config.EnableSports : true;
const symbolCount = config.SymbolCount || 25; const symbolCount = config.SymbolCount || 5;
const useRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true; const useRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true;
const enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false; const enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false;
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true;
const enableTrophy = config.EnableTrophy !== undefined ? config.EnableTrophy : false;
// Pre-declare and manage image assets
const SPORTS_ASSETS = {
badminton: ['badminton_1', 'badminton_2'],
baseball: ['baseball_1', 'baseball_2'],
basketball: ['basketball_1', 'basketball_2'],
billiard: Array.from({length: 14}, (_, i) => `billiard_ball_${i + 1}`),
bowling: ['bowling_1', 'bowling_2'],
football: Array.from({length: 5}, (_, i) => `football_${i + 1}`),
golf: ['golf_ball_1', 'golf_ball_2'],
rugby: ['rugby_ball_1', 'rugby_ball_2'],
table_tennis: ['table_tennis_ball_1', 'table_tennis_ball_2'],
tennis: ['tennis_ball_1', 'tennis_ball_2'],
volleyball: ['volleyball_1', 'volleyball_2'],
waterball: ['waterball_1', 'waterball_2']
};
const turfColorHex = config.TurfColor || '#228b22';
let msgPrinted = false; let msgPrinted = false;
@@ -48,44 +67,54 @@ function createSports() {
document.body.appendChild(container); document.body.appendChild(container);
} }
// Create a turf/grass overlay at the bottom // Parse turf color config
// Create a turf/grass overlay at the bottom using the provided hex
const turf = document.createElement('div'); const turf = document.createElement('div');
turf.className = 'sports-turf'; turf.className = 'sports-turf';
// Using hex with transparency (e.g., 4D = 30%, CC = 80%)
turf.style.background = `linear-gradient(180deg, transparent 0%, ${turfColorHex}4D 30%, ${turfColorHex}CC 100%)`;
container.appendChild(turf); container.appendChild(turf);
const standardCount = 15;
const totalSymbols = symbolCount + standardCount;
let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches; let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
let finalCount = totalSymbols; let ballsPerCategory = symbolCount;
if (isMobile) { if (isMobile && !enableRandomMobile) {
finalCount = enableRandomMobile ? totalSymbols : standardCount; ballsPerCategory = Math.min(symbolCount, 3);
} }
const useRandomDuration = enableDifferentDuration !== false; const useRandomDuration = enableDifferentDuration !== false;
// Standard sports items to spawn // Map standard sports balls to spawn based on category configuration
const activeItems = ['soccer', 'football', 'yellow_card', 'red_card', 'trophy']; const rawSportsBalls = config.SportsBalls || 'football,basketball,tennis,volleyball';
const chosenCategories = rawSportsBalls.split(',').map(s => s.trim()).filter(s => s !== '');
// Create falling sports items const createBall = (randomItem) => {
for (let i = 0; i < finalCount; i++) {
let symbol = document.createElement('div'); let symbol = document.createElement('div');
// Randomly pick an item
const randomItem = activeItems[Math.floor(Math.random() * activeItems.length)];
symbol.className = `sports-symbol sports-${randomItem}`; symbol.className = `sports-symbol sports-${randomItem}`;
// Create inner div for spinning rotation
let innerDiv = document.createElement('div');
innerDiv.className = 'sports-inner';
// Try load image // Try load image
let img = document.createElement('img'); let img = document.createElement('img');
img.src = `../Seasonals/Resources/sports_images/${randomItem}.png`; img.src = `../Seasonals/Resources/sport_assets/${randomItem}.png`;
img.onerror = function() { img.onerror = function() {
this.style.display = 'none'; // hide broken image icon symbol.remove();
this.parentElement.innerHTML = getEmojiFallback(randomItem); // inject emoji fallback
}; };
symbol.appendChild(img); innerDiv.appendChild(img);
// Balls should bounce infinitely
symbol.style.animationName = 'sports-bounce';
symbol.style.animationIterationCount = 'infinite';
innerDiv.style.animationName = 'sports-spin';
innerDiv.style.animationIterationCount = 'infinite';
innerDiv.style.animationTimingFunction = 'linear';
symbol.appendChild(innerDiv);
const leftPos = Math.random() * 100; const leftPos = Math.random() * 95;
const delaySeconds = Math.random() * 10; const delaySeconds = Math.random() * 10;
let durationSeconds = 8; let durationSeconds = 8;
@@ -93,16 +122,93 @@ function createSports() {
durationSeconds = Math.random() * 4 + 6; // 6 to 10 seconds durationSeconds = Math.random() * 4 + 6; // 6 to 10 seconds
} }
// Add a random slight rotation difference // Add a random spin
const startRot = Math.random() * 360; const spinRot = (Math.random() > 0.5 ? 360 : -360) + "deg";
symbol.style.setProperty('--start-rot', `${startRot}deg`); innerDiv.style.setProperty('--spin-rot', spinRot);
symbol.style.setProperty('--end-rot', `${startRot + (Math.random() > 0.5 ? 360 : -360)}deg`);
// Duration for the spin should be different from fall to look natural
const spinDuration = Math.random() * 2 + 2;
innerDiv.style.animationDuration = `${spinDuration}s`;
symbol.style.left = `${leftPos}vw`; symbol.style.left = `${leftPos}vw`;
symbol.style.animationDuration = `${durationSeconds}s`; symbol.style.animationDuration = `${durationSeconds}s`;
symbol.style.animationDelay = `${delaySeconds}s`; symbol.style.animationDelay = `${delaySeconds}s`;
container.appendChild(symbol); container.appendChild(symbol);
};
// Create falling sports balls
chosenCategories.forEach(category => {
let variants = SPORTS_ASSETS[category];
if (!variants) variants = [category]; // Legacy fallback
for (let i = 0; i < ballsPerCategory; i++) {
// Pick a random variant
const randomItem = variants[Math.floor(Math.random() * variants.length)];
createBall(randomItem);
}
});
// Create the periodic flying trophy arc
function launchTrophy() {
if (!document.querySelector('.sports-container')) return;
const flyFromLeft = Math.random() > 0.5;
let trophySymbol = document.createElement('div');
trophySymbol.className = "sports-symbol sports-trophy-wrapper";
let trophyInner = document.createElement('div');
trophyInner.className = "sports-inner sports-trophy-inner";
let trophyImg = document.createElement('img');
trophyImg.src = `../Seasonals/Resources/sport_assets/trophy.gif`;
// Randomly scale trophy slightly larger
trophyImg.style.transform = `scale(${Math.random() * 0.5 + 0.8})`;
trophyImg.onerror = function() {
this.style.display = 'none';
};
trophyInner.appendChild(trophyImg);
trophySymbol.appendChild(trophyInner);
if (flyFromLeft) {
trophySymbol.style.animationName = "sports-arc-x-right";
trophySymbol.style.left = "-15vw";
} else {
trophySymbol.style.animationName = "sports-arc-x-left";
trophySymbol.style.left = "115vw";
}
trophyInner.style.animationName = "sports-arc-y";
// Appearance timing
const arcDuration = 6 + Math.random() * 2;
trophySymbol.style.animationDuration = `${arcDuration}s`;
trophyInner.style.animationDuration = `${arcDuration}s`;
// Prevent looping for the trophy
trophySymbol.style.animationIterationCount = "1";
trophyInner.style.animationIterationCount = "1";
trophySymbol.style.animationFillMode = "forwards";
trophyInner.style.animationFillMode = "forwards";
container.appendChild(trophySymbol);
// Remove node after animation completes
setTimeout(() => {
if (trophySymbol && trophySymbol.parentNode) {
trophySymbol.parentNode.removeChild(trophySymbol);
}
}, arcDuration * 1000 + 500);
// Schedule the next trophy
setTimeout(() => { if (document.body.contains(container)) launchTrophy(); }, Math.random() * 20000 + 10000); // Wait 10-30s until next trophy
}
// Launch initial trophy after a short delay
if (enableTrophy) {
setTimeout(() => { if (document.body.contains(container)) launchTrophy(); }, Math.random() * 5000 + 2000);
} }
// Add Germany Colored confetti (Black, Red, Gold) // Add Germany Colored confetti (Black, Red, Gold)
@@ -116,6 +222,23 @@ function createSports() {
const color = confettiColors[Math.floor(Math.random() * confettiColors.length)]; const color = confettiColors[Math.floor(Math.random() * confettiColors.length)];
confetti.style.backgroundColor = color; confetti.style.backgroundColor = color;
// Random shape generator for varied confetti
const shape = Math.random();
if (shape > 0.66) {
confetti.classList.add('circle');
const size = Math.random() * 5 + 5; // 5-10px
confetti.style.width = `${size}px`;
confetti.style.height = `${size}px`;
} else if (shape > 0.33) {
confetti.classList.add('rect');
const width = Math.random() * 4 + 4; // 4-8px
const height = Math.random() * 5 + 8; // 8-13px
confetti.style.width = `${width}px`;
confetti.style.height = `${height}px`;
} else {
confetti.classList.add('triangle');
}
const leftPos = Math.random() * 100; const leftPos = Math.random() * 100;
const delaySeconds = Math.random() * 8; const delaySeconds = Math.random() * 8;
const duration = Math.random() * 3 + 4; // 4 to 7 seconds const duration = Math.random() * 3 + 4; // 4 to 7 seconds
@@ -123,19 +246,18 @@ function createSports() {
confetti.style.left = `${leftPos}vw`; confetti.style.left = `${leftPos}vw`;
confetti.style.animationDuration = `${duration}s`; confetti.style.animationDuration = `${duration}s`;
confetti.style.animationDelay = `${delaySeconds}s`; confetti.style.animationDelay = `${delaySeconds}s`;
// Random 3D Rotation for flutter
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));
confetti.style.setProperty('--rot-dir', `${(Math.random() > 0.5 ? 1 : -1) * 360}deg`);
container.appendChild(confetti); container.appendChild(confetti);
} }
} }
function getEmojiFallback(type) { /* Removed legacy fallback logic */
if (type === 'soccer') return '⚽';
if (type === 'football') return '🏈';
if (type === 'yellow_card') return '🟨';
if (type === 'red_card') return '🟥';
if (type === 'trophy') return '🏆';
return '';
}
function initializeSports() { function initializeSports() {
if (!sports) return; if (!sports) return;

View File

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

View File

@@ -295,7 +295,7 @@ function createBird(container) {
wrapper.addEventListener('animationend', (e) => { wrapper.addEventListener('animationend', (e) => {
if (e.animationName.includes('fly-')) { if (e.animationName.includes('fly-')) {
wrapper.remove(); wrapper.remove();
createBird(container); if (document.body.contains(container)) createBird(container);
} }
}); });
@@ -338,7 +338,7 @@ function createButterfly(container) {
wrapper.addEventListener('animationend', (e) => { wrapper.addEventListener('animationend', (e) => {
if (e.animationName.includes('fly-')) { if (e.animationName.includes('fly-')) {
wrapper.remove(); wrapper.remove();
createButterfly(container); if (document.body.contains(container)) createButterfly(container);
} }
}); });
@@ -385,7 +385,7 @@ function createBee(container) {
wrapper.addEventListener('animationend', (e) => { wrapper.addEventListener('animationend', (e) => {
if (e.animationName.includes('fly-')) { if (e.animationName.includes('fly-')) {
wrapper.remove(); wrapper.remove();
createBee(container); if (document.body.contains(container)) createBee(container);
} }
}); });
@@ -428,7 +428,7 @@ function createLadybugGif(container) {
wrapper.addEventListener('animationend', (e) => { wrapper.addEventListener('animationend', (e) => {
if (e.animationName.includes('walk-')) { if (e.animationName.includes('walk-')) {
wrapper.remove(); wrapper.remove();
createLadybugGif(container); if (document.body.contains(container)) createLadybugGif(container);
} }
}); });

View File

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

View File

@@ -256,7 +256,7 @@
<option value="matrix">Matrix</option> <option value="matrix">Matrix</option>
<option value="pride">Pride</option> <option value="pride">Pride</option>
<option value="rain">Rain</option> <option value="rain">Rain</option>
<option value="storm">Storm (⚠Epilepsy Warning)</option> <option value="storm">Storm (⚠Epilepsy Warning⚠️)</option>
<option value="frost">Frost / Ice</option> <option value="frost">Frost / Ice</option>
<option value="filmnoir">Film-Noir</option> <option value="filmnoir">Film-Noir</option>
<option value="oscar">Oscar Awards</option> <option value="oscar">Oscar Awards</option>
@@ -265,8 +265,8 @@
<option value="oktoberfest">Oktoberfest</option> <option value="oktoberfest">Oktoberfest</option>
<option value="friday13">Friday the 13th</option> <option value="friday13">Friday the 13th</option>
<option value="eid">Eid al-Fitr</option> <option value="eid">Eid al-Fitr</option>
<option value="sports">Sports / Football</option> <option value="sports">Sports</option>
<option value="olympia">Olympia / Games</option> <option value="olympia">Olympia</option>
<option value="space">Space / Sci-Fi</option> <option value="space">Space / Sci-Fi</option>
<option value="underwater">Underwater</option> <option value="underwater">Underwater</option>
<option value="birthday">Birthday</option> <option value="birthday">Birthday</option>
@@ -511,11 +511,11 @@
const container = document.querySelector('.seasonals-container'); const container = document.querySelector('.seasonals-container');
container.className = `seasonals-container ${containerClass}`; container.className = `seasonals-container ${containerClass}`;
// Inject CSS // Inject CSS with cache-buster
if (cssFile) { if (cssFile) {
const link = document.createElement('link'); const link = document.createElement('link');
link.rel = 'stylesheet'; link.rel = 'stylesheet';
link.href = cssFile; link.href = cssFile + '?v=' + new Date().getTime();
link.setAttribute('data-seasonal', 'true'); link.setAttribute('data-seasonal', 'true');
link.onerror = () => console.error(`[Test Site] Failed to load CSS: ${cssFile}`); link.onerror = () => console.error(`[Test Site] Failed to load CSS: ${cssFile}`);
document.head.appendChild(link); document.head.appendChild(link);
@@ -525,7 +525,7 @@
if (jsFile) { if (jsFile) {
setTimeout(async () => { setTimeout(async () => {
try { try {
const response = await fetch(jsFile); const response = await fetch(jsFile + '?v=' + new Date().getTime());
if (!response.ok) throw new Error(`HTTP ${response.status}`); if (!response.ok) throw new Error(`HTTP ${response.status}`);
const code = await response.text(); const code = await response.text();

View File

@@ -35,6 +35,7 @@
} }
.underwater-seaweed { .underwater-seaweed {
will-change: transform, opacity;
position: absolute; position: absolute;
bottom: -1vh; bottom: -1vh;
font-size: 4rem; font-size: 4rem;
@@ -52,6 +53,7 @@
} }
.underwater-bubble { .underwater-bubble {
will-change: transform;
position: absolute; position: absolute;
bottom: -5vh; bottom: -5vh;
border-radius: 50%; border-radius: 50%;
@@ -62,16 +64,29 @@
z-index: 40; z-index: 40;
} }
@keyframes underwater-swim-right { @keyframes underwater-traverse-right {
0% { transform: translateX(0) translateY(0) scaleX(-1); } 0% { left: -25vw; }
50% { transform: translateX(65vw) translateY(-5vh) scaleX(-1); } 100% { left: 125vw; }
100% { transform: translateX(130vw) translateY(0) scaleX(-1); }
} }
@keyframes underwater-swim-left { @keyframes underwater-traverse-left {
0% { transform: translateX(0) translateY(0); } 0% { left: 125vw; }
50% { transform: translateX(-65vw) translateY(5vh); } 100% { left: -25vw; }
100% { transform: translateX(-130vw) translateY(0); } }
@keyframes underwater-traverse-up {
0% { top: 0; translate: 0 120vh; }
100% { top: 0; translate: 0 -20vh; }
}
@keyframes underwater-traverse-down {
0% { top: 0; translate: 0 -20vh; }
100% { top: 0; translate: 0 120vh; }
}
@keyframes underwater-sway-y {
0% { transform: translateY(-2vh); }
100% { transform: translateY(2vh); }
} }
@keyframes underwater-sway { @keyframes underwater-sway {
@@ -86,3 +101,32 @@
90% { opacity: 0; } 90% { opacity: 0; }
100% { transform: translateY(-110vh) translateX(10px); opacity: 0; } 100% { transform: translateY(-110vh) translateX(10px); opacity: 0; }
} }
.underwater-god-rays {
will-change: transform;
position: absolute;
top: 0;
left: -50vw;
width: 200vw;
height: 200vh;
background: repeating-linear-gradient(
15deg,
rgba(255, 255, 255, 0.02) 0px,
rgba(255, 255, 255, 0.05) 100px,
transparent 100px,
transparent 300px
);
animation: god-rays-sway 20s ease-in-out infinite alternate;
pointer-events: none;
z-index: 12;
transform-origin: top center;
mix-blend-mode: overlay;
filter: blur(5px);
translate: 0 -50vh;
}
@keyframes god-rays-sway {
0% { transform: rotate(-3deg) translateX(-5%); opacity: 0.4; }
50% { opacity: 0.8; }
100% { transform: rotate(3deg) translateX(5%); opacity: 0.4; }
}

View File

@@ -5,6 +5,78 @@ const symbolCount = config.SymbolCount || 15;
const useRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true; const useRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true;
const enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false; const enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false;
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true;
const enableLightRays = config.EnableLightRays !== undefined ? config.EnableLightRays : true;
const seaweedCount = config.SeaweedCount !== undefined ? config.SeaweedCount : 50;
// Entity counts configured
const fishCount = config.FishCount !== undefined ? config.FishCount : 15;
const seahorseCount = config.SeahorseCount !== undefined ? config.SeahorseCount : 3;
const jellyfishCount = config.JellyfishCount !== undefined ? config.JellyfishCount : 3;
const turtleCount = config.TurtleCount !== undefined ? config.TurtleCount : 1;
const crabCount = config.CrabCount !== undefined ? config.CrabCount : 2;
const starfishCount = config.StarfishCount !== undefined ? config.StarfishCount : 2;
const shellCount = config.ShellCount !== undefined ? config.ShellCount : 2;
const seaweeds = [
"../Seasonals/Resources/underwater_assets/seaweed_1.gif",
"../Seasonals/Resources/underwater_assets/seaweed_2.gif"
];
// Statics for bottom
const crabImages = [
"../Seasonals/Resources/underwater_assets/crab_1.gif",
"../Seasonals/Resources/underwater_assets/crab_2.gif",
"../Seasonals/Resources/underwater_assets/crab_3.gif"
];
const starfishImages = [
"../Seasonals/Resources/underwater_assets/starfish_1.gif",
"../Seasonals/Resources/underwater_assets/starfish_2.gif"
];
const shellImages = [
"../Seasonals/Resources/underwater_assets/shell_1.gif"
];
const fishImages = [
"../Seasonals/Resources/underwater_assets/fish_1.gif",
"../Seasonals/Resources/underwater_assets/fish_2.gif",
"../Seasonals/Resources/underwater_assets/fish_3.gif",
"../Seasonals/Resources/underwater_assets/fish_5.gif",
"../Seasonals/Resources/underwater_assets/fish_6.gif",
"../Seasonals/Resources/underwater_assets/fish_7.png",
"../Seasonals/Resources/underwater_assets/fish_8.png",
"../Seasonals/Resources/underwater_assets/fish_9.png",
"../Seasonals/Resources/underwater_assets/fish_10.png",
"../Seasonals/Resources/underwater_assets/fish_11.png",
"../Seasonals/Resources/underwater_assets/fish_12.png",
"../Seasonals/Resources/underwater_assets/fish_13.png",
"../Seasonals/Resources/underwater_assets/fish_14.png",
"../Seasonals/Resources/underwater_assets/fish_15.png"
];
const seahorsesImages = [
"../Seasonals/Resources/underwater_assets/seahorse_1.gif",
"../Seasonals/Resources/underwater_assets/seahorse_2.gif"
];
const turtleImages = [
"../Seasonals/Resources/underwater_assets/turtle.gif"
];
const jellyfishImages = [
"../Seasonals/Resources/underwater_assets/jellyfish_1.gif",
"../Seasonals/Resources/underwater_assets/jellyfish_2.gif"
];
// MARK: Base sizes for all creatures (in vh)
const seahorseSize = 8;
const turtleSize = 14;
const jellyfishSize = 18;
const fishSize = 8;
const crabSize = 4;
const starfishSize = 4;
const shellSize = 7;
let msgPrinted = false; let msgPrinted = false;
@@ -46,100 +118,197 @@ function createUnderwater() {
container.className = 'underwater-container'; container.className = 'underwater-container';
container.setAttribute("aria-hidden", "true"); container.setAttribute("aria-hidden", "true");
document.body.appendChild(container); document.body.appendChild(container);
} else {
container.innerHTML = ''; // Prevent infinite duplication on theme reload!
} }
// Deep blue overlay // Deep blue overlay
const bg = document.createElement('div'); const bg = document.createElement('div');
bg.className = 'underwater-bg'; bg.className = 'underwater-bg';
container.appendChild(bg); container.appendChild(bg);
const standardCount = 8;
const totalSymbols = symbolCount + standardCount;
let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches; // Light Rays (God Rays)
let finalCount = totalSymbols; if (enableLightRays) {
const rays = document.createElement('div');
if (isMobile) { rays.className = 'underwater-god-rays';
finalCount = enableRandomMobile ? totalSymbols : standardCount; container.appendChild(rays);
} }
const useRandomDuration = enableDifferentDuration !== false; const useRandomDuration = enableDifferentDuration !== false;
let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
// Seaweed swaying at the bottom
for (let i = 0; i < 4; i++) { // Seaweed swaying at the bottom (evenly distributed based on count)
const activeSeaweedCount = Math.max(1, seaweedCount);
const seaweedSpacing = 95 / activeSeaweedCount;
for (let i = 0; i < seaweedCount; i++) {
let seaweed = document.createElement('div'); let seaweed = document.createElement('div');
seaweed.className = 'underwater-seaweed'; seaweed.className = 'underwater-seaweed';
seaweed.style.left = `${10 + (i * 25)}vw`; seaweed.style.position = 'absolute';
// MARK: Distance from the bottom edge for the seaweed
seaweed.style.bottom = '-18px';
let offset = (Math.random() * seaweedSpacing) - (seaweedSpacing / 2);
seaweed.style.left = `max(0vw, min(95vw, calc(${(i * seaweedSpacing)}vw + ${offset}vw)))`;
seaweed.style.animationDelay = `-${Math.random() * 5}s`; seaweed.style.animationDelay = `-${Math.random() * 5}s`;
// Randomly flip // Random parallax scale for seaweed depth
if (Math.random() > 0.5) { const depth = Math.random();
seaweed.style.transform = 'scaleX(-1)'; const scale = 0.5 + depth * 0.7; // 0.5 to 1.2
} const blur = depth < 0.3 ? `blur(2px)` : 'none';
seaweed.style.filter = blur;
let flip = Math.random() > 0.5 ? 'scaleX(-1)' : 'scaleX(1)';
seaweed.style.transform = `scale(${scale}) ${flip}`;
seaweed.style.zIndex = depth < 0.5 ? '15' : '30';
let img = document.createElement('img'); // Mix Emojis and GIFs
img.src = '../Seasonals/Resources/underwater_images/seaweed.png'; if (Math.random() > 0.4) {
img.onerror = function() { let img = document.createElement('img');
this.style.display = 'none'; img.src = seaweeds[Math.floor(Math.random() * seaweeds.length)];
this.parentElement.innerHTML = '🌿'; img.onerror = function() {
this.parentElement.style.fontSize = '3rem'; this.style.display = 'none';
this.parentElement.style.bottom = '0'; };
this.parentElement.style.transformOrigin = 'bottom center'; seaweed.appendChild(img);
}; } else {
seaweed.appendChild(img); seaweed.innerHTML = '🌿';
seaweed.style.fontSize = '3rem';
seaweed.style.bottom = '0';
seaweed.style.transformOrigin = 'bottom center';
}
container.appendChild(seaweed); container.appendChild(seaweed);
} }
const activeItems = ['fish_orange', 'fish_blue', 'jellyfish', 'turtle']; // Static Bottom Creatures logic
function spawnStatic(imageArray, maxCount, baseSize) {
// Evaluate an actual count between 1 and maxCount if random symbols are enabled
const actualCount = (useRandomSymbols && maxCount > 0) ? Math.floor(Math.random() * maxCount) + 1 : maxCount;
for (let i = 0; i < actualCount; i++) {
let creature = document.createElement('div');
creature.className = 'underwater-static-bottom';
creature.style.position = 'absolute';
creature.style.bottom = '5px';
creature.style.left = `${Math.random() * 95}vw`;
creature.style.zIndex = '20'; // In between seaweed layers
for (let i = 0; i < finalCount; i++) { let img = document.createElement('img');
let symbol = document.createElement('div'); img.src = imageArray[Math.floor(Math.random() * imageArray.length)];
img.style.height = `${baseSize}vh`;
// Random scale variance and flip
const scale = 0.7 + Math.random() * 0.5; // 0.7 to 1.2 x baseSize
const flip = Math.random() > 0.5 ? 'scaleX(-1)' : 'scaleX(1)';
img.style.transform = `scale(${scale}) ${flip}`;
img.onerror = function() {
this.style.display = 'none';
};
creature.appendChild(img);
container.appendChild(creature);
}
}
spawnStatic(crabImages, crabCount, crabSize);
spawnStatic(starfishImages, starfishCount, starfishSize);
spawnStatic(shellImages, shellCount, shellSize);
// Swimmers logic
function spawnSwimmerLoop(imageArray, maxCount, baseSize, typeName) {
if (maxCount <= 0) return;
let spawnLimit = isMobile ? (enableRandomMobile ? maxCount : Math.floor(maxCount / 2)) : maxCount;
const randomItem = activeItems[Math.floor(Math.random() * activeItems.length)]; // Randomize the actual amount spawned up to the limit
symbol.className = `underwater-symbol underwater-${randomItem}`; const actualCount = (useRandomSymbols && spawnLimit > 0) ? Math.floor(Math.random() * spawnLimit) + 1 : spawnLimit;
for (let i = 0; i < actualCount; i++) {
// Spawn immediately but use negative delay to distribute them across the screen!
spawnSingleSwimmer(imageArray, baseSize, typeName);
}
}
function spawnSingleSwimmer(imageArray, baseSize, typeName) {
if (!document.querySelector('.underwater-container')) return;
let symbol = document.createElement('div');
symbol.className = `underwater-symbol`;
const randomImage = imageArray[Math.floor(Math.random() * imageArray.length)];
let img = document.createElement('img'); let img = document.createElement('img');
img.src = `../Seasonals/Resources/underwater_images/${randomItem}.png`; img.src = randomImage;
img.style.height = `${baseSize}vh`;
img.style.width = 'auto';
img.style.maxWidth = 'none';
img.onerror = function() { img.onerror = function() {
this.style.display = 'none'; this.style.display = 'none';
this.parentElement.innerHTML = getUnderwaterEmojiFallback(randomItem);
}; };
symbol.appendChild(img);
const topPos = 10 + Math.random() * 80; // 10 to 90vh const depth = Math.random();
const delaySeconds = Math.random() * 10; const distanceScale = 0.4 + (depth * 0.8);
const blurAmount = depth < 0.4 ? (1 - depth) * 3 : 0;
const opacity = 0.4 + (depth * 0.5);
let durationSeconds = 15; symbol.style.opacity = `${opacity}`;
if (useRandomDuration) { symbol.style.filter = `blur(${blurAmount}px)`;
durationSeconds = Math.random() * 10 + 15; // 15 to 25 seconds slow swimming symbol.style.zIndex = Math.floor(depth * 30) + 10;
}
symbol.style.animationIterationCount = 'infinite';
// Randomly pick direction: left-to-right OR right-to-left let durationSeconds = (1 - depth) * 20 + 15 + Math.random() * 5;
const goRight = Math.random() > 0.5; if (!useRandomDuration) durationSeconds = 20;
if (goRight) {
symbol.style.animationName = 'underwater-swim-right'; // Apply a negative delay on spawn so they start mid-screen scattered
symbol.style.left = '-10vw'; const startDelay = -(Math.random() * durationSeconds);
// Animate based on type
if (typeName === 'jellyfish') {
const goUp = Math.random() > 0.5;
symbol.style.animationName = goUp ? 'underwater-traverse-up' : 'underwater-traverse-down';
symbol.style.left = `${Math.random() * 90}vw`;
const flip = Math.random() > 0.5 ? 'scaleX(-1)' : 'scaleX(1)';
symbol.style.transform = `scale(${distanceScale}) ${flip}`;
durationSeconds *= 0.8;
symbol.style.animationDuration = `${durationSeconds}s`;
symbol.style.animationDelay = `${startDelay}s`;
symbol.appendChild(img);
} else { } else {
symbol.style.animationName = 'underwater-swim-left'; const goRight = Math.random() > 0.5;
symbol.style.right = '-10vw'; const directionScale = goRight ? 'scaleX(-1)' : 'scaleX(1)';
symbol.style.transform = 'scaleX(-1)'; // flip fish to face left
symbol.style.animationName = goRight ? 'underwater-traverse-right' : 'underwater-traverse-left';
symbol.style.animationDelay = `${startDelay}s`;
const rotationDiv = document.createElement('div');
let swayDur = Math.random() * 2 + 2;
if (typeName === 'seahorse') swayDur *= 1.5;
else if (typeName === 'turtle') swayDur *= 2;
rotationDiv.style.animation = `underwater-sway-y ${swayDur}s ease-in-out infinite alternate`;
// Random internal sway to prevent synchronized wiggling
rotationDiv.style.animationDelay = `-${Math.random() * 5}s`;
// Apply flip scale directly to the image inside rotationDiv
img.style.transform = `scale(${distanceScale}) ${directionScale}`;
rotationDiv.appendChild(img);
symbol.appendChild(rotationDiv);
symbol.style.top = `${Math.random() * 80 + 5}vh`;
symbol.style.animationDuration = `${durationSeconds}s`;
} }
symbol.style.top = `${topPos}vh`;
symbol.style.animationDuration = `${durationSeconds}s`;
symbol.style.animationDelay = `${delaySeconds}s`;
// Small vertical sway
const swimSway = document.createElement('div');
swimSway.style.animation = `underwater-sway ${Math.random() * 2 + 3}s ease-in-out infinite`;
swimSway.appendChild(symbol.cloneNode(true));
symbol.innerHTML = '';
symbol.appendChild(swimSway);
container.appendChild(symbol); container.appendChild(symbol);
} }
// Bubbles // Start swimmer loops
spawnSwimmerLoop(fishImages, fishCount, fishSize, 'fish');
spawnSwimmerLoop(seahorsesImages, seahorseCount, seahorseSize, 'seahorse');
spawnSwimmerLoop(jellyfishImages, jellyfishCount, jellyfishSize, 'jellyfish');
spawnSwimmerLoop(turtleImages, turtleCount, turtleSize, 'turtle');
const bubbleCount = isMobile ? 15 : 30; const bubbleCount = isMobile ? 15 : 30;
for (let i = 0; i < bubbleCount; i++) { for (let i = 0; i < bubbleCount; i++) {
@@ -163,13 +332,7 @@ function createUnderwater() {
} }
} }
function getUnderwaterEmojiFallback(type) {
if (type === 'fish_orange') return '🐠';
if (type === 'fish_blue') return '🐟';
if (type === 'jellyfish') return '🪼';
if (type === 'turtle') return '🐢';
return '🫧';
}
function initializeUnderwater() { function initializeUnderwater() {
if (!underwater) return; if (!underwater) return;

View File

@@ -8,9 +8,17 @@
"category": "General", "category": "General",
"imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/raw/branch/main/logo.png", "imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/raw/branch/main/logo.png",
"versions": [ "versions": [
{
"version": "2.0.0.1",
"changelog": "- feat: add many themes\n- fix: improve performance",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v2.0.0.1/Jellyfin.Plugin.Seasonals.zip",
"checksum": "2eb9ce92f3aa89d133ad09cd874b7c85",
"timestamp": "2026-02-27T03:27:02Z"
},
{ {
"version": "1.7.2.0", "version": "1.7.2.0",
"changelog": "- feat: add Pi Day, Pride, Rain, and Storm themes\n- fix: improve performance", "changelog": "- feat: add Pi Day, Pride, Rain, and Storm themes\n- fix: improve performance",
"targetAbi": "10.11.0.0", "targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.7.2.0/Jellyfin.Plugin.Seasonals.zip", "sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.7.2.0/Jellyfin.Plugin.Seasonals.zip",
"checksum": "34c8426c48bd7d470c3e8dc7f02f86da", "checksum": "34c8426c48bd7d470c3e8dc7f02f86da",