Compare commits
14 Commits
c6d04b9b3b
...
v2.0.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef15857533 | ||
|
|
19b21ba94f | ||
|
|
8f322fd6cf | ||
|
|
bdc7d2e325 | ||
|
|
8afe397c23 | ||
|
|
30c29d440f | ||
|
|
69adc64a44 | ||
|
|
b0fae10aa1 | ||
|
|
cee4dae769 | ||
|
|
f9aeeadccf | ||
|
|
fc35fcd3c4 | ||
|
|
6a83981e1d | ||
|
|
540d7f9baa | ||
|
|
a162b30bcd |
@@ -32,7 +32,7 @@ public class PluginConfiguration : BasePluginConfiguration
|
||||
Summer = new SummerOptions();
|
||||
CherryBlossom = new CherryBlossomOptions();
|
||||
Carnival = new CarnivalOptions();
|
||||
PiDay = new PiDayOptions();
|
||||
Matrix = new MatrixOptions();
|
||||
Eurovision = new EurovisionOptions();
|
||||
Storm = new StormOptions();
|
||||
Pride = new PrideOptions();
|
||||
@@ -97,7 +97,7 @@ public class PluginConfiguration : BasePluginConfiguration
|
||||
public SummerOptions Summer { get; set; }
|
||||
public CherryBlossomOptions CherryBlossom { get; set; }
|
||||
public CarnivalOptions Carnival { get; set; }
|
||||
public PiDayOptions PiDay { get; set; }
|
||||
public MatrixOptions Matrix { get; set; }
|
||||
public EurovisionOptions Eurovision { get; set; }
|
||||
public StormOptions Storm { get; set; }
|
||||
public PrideOptions Pride { get; set; }
|
||||
@@ -277,14 +277,15 @@ public class CherryBlossomOptions
|
||||
public bool EnableDifferentDuration { get; set; } = true;
|
||||
}
|
||||
|
||||
public class PiDayOptions
|
||||
public class MatrixOptions
|
||||
{
|
||||
public int SymbolCount { get; set; } = 50;
|
||||
public bool EnablePiDay { get; set; } = true;
|
||||
public bool EnableRandomPiDay { get; set; } = true;
|
||||
public bool EnableRandomPiDayMobile { get; set; } = false;
|
||||
public int SymbolCount { get; set; } = 25;
|
||||
public bool EnableMatrix { get; set; } = true;
|
||||
public bool EnableRandomMatrix { get; set; } = true;
|
||||
public bool EnableRandomMatrixMobile { get; set; } = false;
|
||||
public bool EnableDifferentDuration { get; set; } = true;
|
||||
public bool EnablePiDayBackground { get; set; } = false;
|
||||
public bool EnableMatrixBackground { get; set; } = false;
|
||||
public string MatrixChars { get; set; } = "0123456789";
|
||||
}
|
||||
|
||||
public class EurovisionOptions
|
||||
@@ -381,11 +382,14 @@ public class SpookyOptions
|
||||
|
||||
public class SportsOptions
|
||||
{
|
||||
public int SymbolCount { get; set; } = 25;
|
||||
public int SymbolCount { get; set; } = 5;
|
||||
public bool EnableSports { get; set; } = true;
|
||||
public bool EnableRandomSymbols { get; set; } = true;
|
||||
public bool EnableRandomSymbolsMobile { get; set; } = false;
|
||||
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
|
||||
@@ -399,7 +403,11 @@ public class OlympiaOptions
|
||||
|
||||
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 EnableRandomSymbols { get; set; } = true;
|
||||
public bool EnableRandomSymbolsMobile { get; set; } = false;
|
||||
@@ -413,13 +421,24 @@ public class UnderwaterOptions
|
||||
public bool EnableRandomSymbols { get; set; } = true;
|
||||
public bool EnableRandomSymbolsMobile { get; set; } = false;
|
||||
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 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 EnableRandomSymbols { get; set; } = true;
|
||||
public bool EnableRandomSymbolsMobile { get; set; } = false;
|
||||
public bool EnableDifferentDuration { get; set; } = true;
|
||||
public bool EnableGarland { get; set; } = true;
|
||||
}
|
||||
|
||||
@@ -70,24 +70,23 @@
|
||||
<option value="carnival">Carnival (Confetti)</option>
|
||||
<option value="cherryblossom">Cherry Blossom</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="eurovision">Eurovision (Dancing Notes)</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="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="filmnoir">Film-Noir (Classic B&W Cinema)</option>
|
||||
<option value="marioday">Mario Day (March 10)</option>
|
||||
<option value="starwars">Star Wars Day (May 4th)</option>
|
||||
<option value="oktoberfest">Oktoberfest</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="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>
|
||||
<div class="fieldDescription">The season to display if automation is disabled or no "Auto Selection" rule matches the current date.</div>
|
||||
</div>
|
||||
@@ -123,7 +122,7 @@
|
||||
<!-- Advanced Tab -->
|
||||
<div id="seasonals-advanced" class="seasonals-tab-content" style="display: none;">
|
||||
<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>
|
||||
<details>
|
||||
<summary>Autumn</summary>
|
||||
@@ -151,7 +150,7 @@
|
||||
<div class="inputContainer">
|
||||
<label class="inputLabel" for="AutumnLeafCount">Leaf Count</label>
|
||||
<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 class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label class="emby-checkbox-label">
|
||||
@@ -371,7 +370,7 @@
|
||||
<hr style="max-width: 800px; margin: 1em 0;">
|
||||
|
||||
<details>
|
||||
<summary>Spooky Theme</summary>
|
||||
<summary>Spooky</summary>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="EnableSpooky" name="EnableSpooky" type="checkbox" is="emby-checkbox" />
|
||||
@@ -403,6 +402,47 @@
|
||||
</details>
|
||||
<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>
|
||||
<summary>Hearts</summary>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
@@ -894,6 +934,72 @@
|
||||
<span>Enable Different Duration</span>
|
||||
</label>
|
||||
</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>
|
||||
<hr style="max-width: 800px; margin: 1em 0;">
|
||||
|
||||
@@ -954,9 +1060,29 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<label class="inputLabel" for="SpaceSymbolCount">Symbol Count</label>
|
||||
<input is="emby-input" type="number" id="SpaceSymbolCount" name="SpaceSymbolCount" />
|
||||
<div class="fieldDescription">Number of additional symbols displayed (if enabled).</div>
|
||||
<label class="inputLabel" for="PlanetCount">Planet Count</label>
|
||||
<input is="emby-input" type="number" id="PlanetCount" name="PlanetCount" />
|
||||
<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 class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label class="emby-checkbox-label">
|
||||
@@ -976,6 +1102,13 @@
|
||||
</label>
|
||||
<div class="fieldDescription">Enable the underwater theme effects in general (e.g. for automation).</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">
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="EnableRandomSymbolsUnderwater" name="EnableRandomSymbolsUnderwater" type="checkbox" is="emby-checkbox" />
|
||||
@@ -989,9 +1122,38 @@
|
||||
</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 additional symbols displayed (if enabled).</div>
|
||||
<label class="inputLabel" for="UnderwaterSeaweedCount">Seaweed Count</label>
|
||||
<input is="emby-input" type="number" id="UnderwaterSeaweedCount" name="UnderwaterSeaweedCount" />
|
||||
<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 class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label class="emby-checkbox-label">
|
||||
@@ -1011,6 +1173,13 @@
|
||||
</label>
|
||||
<div class="fieldDescription">Enable the birthday theme effects in general (e.g. for automation).</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">
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="EnableRandomSymbolsBirthday" name="EnableRandomSymbolsBirthday" type="checkbox" is="emby-checkbox" />
|
||||
@@ -1028,6 +1197,11 @@
|
||||
<input is="emby-input" type="number" id="BirthdaySymbolCount" name="BirthdaySymbolCount" />
|
||||
<div class="fieldDescription">Number of additional symbols displayed (if enabled).</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">
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="EnableDifferentDurationBirthday" name="EnableDifferentDurationBirthday" type="checkbox" is="emby-checkbox" />
|
||||
@@ -1093,46 +1267,51 @@
|
||||
<hr style="max-width: 800px; margin: 1em 0;">
|
||||
|
||||
<details>
|
||||
<summary>Pi-Day</summary>
|
||||
<summary>Matrix</summary>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="EnablePiDay" name="EnablePiDay" type="checkbox" is="emby-checkbox" />
|
||||
<span>Enable Pi-Day Seasonal</span>
|
||||
<input id="EnableMatrix" name="EnableMatrix" type="checkbox" is="emby-checkbox" />
|
||||
<span>Enable Matrix Seasonal</span>
|
||||
</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 class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="EnableRandomPiDay" name="EnableRandomPiDay" type="checkbox" is="emby-checkbox" />
|
||||
<span>Enable Additional Random Pi Symbols</span>
|
||||
<input id="EnableRandomMatrix" name="EnableRandomMatrix" type="checkbox" is="emby-checkbox" />
|
||||
<span>Enable Additional Random Matrix Symbols</span>
|
||||
</label>
|
||||
<div class="fieldDescription">Displays additional digital rain elements.</div>
|
||||
</div>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="EnableRandomPiDayMobile" name="EnableRandomPiDayMobile" type="checkbox" is="emby-checkbox" />
|
||||
<span>Enable Additional Random Pi Symbols on Mobile</span>
|
||||
<input id="EnableRandomMatrixMobile" name="EnableRandomMatrixMobile" type="checkbox" is="emby-checkbox" />
|
||||
<span>Enable Additional Random Matrix Symbols on Mobile</span>
|
||||
</label>
|
||||
<div class="fieldDescription">Displays additional digital rain elements on mobile devices. Warning: High values may affect performance.</div>
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<label class="inputLabel" for="PiDaySymbolCount">Symbol Count</label>
|
||||
<input is="emby-input" type="number" id="PiDaySymbolCount" name="PiDaySymbolCount" />
|
||||
<label class="inputLabel" for="MatrixSymbolCount">Symbol Count</label>
|
||||
<input is="emby-input" type="number" id="MatrixSymbolCount" name="MatrixSymbolCount" />
|
||||
<div class="fieldDescription">Number of additional digital rain columns (if enabled).</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">
|
||||
<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>
|
||||
</label>
|
||||
<div class="fieldDescription">Randomize the digital rain falling speed.</div>
|
||||
</div>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<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>
|
||||
</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>
|
||||
</details>
|
||||
<hr style="max-width: 800px; margin: 1em 0;">
|
||||
@@ -1200,6 +1379,7 @@
|
||||
|
||||
<details>
|
||||
<summary>Storm</summary>
|
||||
<div><p>⚠️ Epilepsy warning ⚠️</p></div>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="EnableStorm" name="EnableStorm" type="checkbox" is="emby-checkbox" />
|
||||
@@ -1310,7 +1490,7 @@
|
||||
<hr style="max-width: 800px; margin: 1em 0;">
|
||||
|
||||
<details>
|
||||
<summary>Zuckerfest (Eid)</summary>
|
||||
<summary>Eid al-Fitr (Sugar Feast)</summary>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="EnableEid" name="EnableEid" type="checkbox" is="emby-checkbox" />
|
||||
@@ -1319,7 +1499,6 @@
|
||||
</div>
|
||||
</details>
|
||||
</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;">
|
||||
<i class="material-icons" style="color: #00a4dc; font-size: 24px;">info</i>
|
||||
<div>
|
||||
@@ -1502,7 +1681,7 @@
|
||||
' <option value="cherryblossom">Cherry Blossom</option>' +
|
||||
' <option value="earthday">Earth Day</option>' +
|
||||
' <option value="eurovision">Eurovision</option>' +
|
||||
' <option value="piday">Pi-Day</option>' +
|
||||
' <option value="matrix">Matrix</option>' +
|
||||
' <option value="pride">Pride</option>' +
|
||||
' <option value="rain">Rain</option>' +
|
||||
' <option value="storm">Storm (Epilepsy Warning!)</option>' +
|
||||
@@ -1514,7 +1693,8 @@
|
||||
' <option value="starwars">Star Wars Day</option>' +
|
||||
' <option value="oktoberfest">Oktoberfest</option>' +
|
||||
' <option value="friday13">Friday the 13th</option>' +
|
||||
' <option value="eid">Eid al-Fitr</option>' +
|
||||
' <option value="eidalfitr">Eid al-Fitr</option>' +
|
||||
' <option value="spooky">Spooky</option>' +
|
||||
' <option value="legacyhalloween">Legacy Halloween</option>' +
|
||||
' </select>' +
|
||||
' </div>' +
|
||||
@@ -1661,21 +1841,60 @@
|
||||
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;
|
||||
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;
|
||||
|
||||
// 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;
|
||||
document.querySelector('#UnderwaterSeaweedCount').value = config.Underwater.SeaweedCount !== undefined ? config.Underwater.SeaweedCount : 30;
|
||||
document.querySelector('#UnderwaterFishCount').value = config.Underwater.FishCount !== undefined ? config.Underwater.FishCount : 15;
|
||||
document.querySelector('#UnderwaterSeahorseCount').value = config.Underwater.SeahorseCount !== undefined ? config.Underwater.SeahorseCount : 3;
|
||||
document.querySelector('#UnderwaterJellyfishCount').value = config.Underwater.JellyfishCount !== undefined ? config.Underwater.JellyfishCount : 3;
|
||||
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;
|
||||
|
||||
// Birthday
|
||||
document.querySelector('#EnableBirthday').checked = config.Birthday.EnableBirthday || false;
|
||||
document.querySelector('#EnableGarland').checked = config.Birthday.EnableGarland !== false;
|
||||
document.querySelector('#EnableRandomSymbolsBirthday').checked = config.Birthday.EnableRandomSymbols || false;
|
||||
document.querySelector('#EnableRandomSymbolsMobileBirthday').checked = config.Birthday.EnableRandomSymbolsMobile || false;
|
||||
document.querySelector('#EnableDifferentDurationBirthday').checked = config.Birthday.EnableDifferentDuration || false;
|
||||
document.querySelector('#BirthdaySymbolCount').value = config.Birthday.SymbolCount || 25;
|
||||
document.querySelector('#BirthdayConfettiCount').value = config.Birthday.ConfettiCount || 60;
|
||||
|
||||
// Sports
|
||||
if (!config.Sports) config.Sports = { EnableSports: true, SymbolCount: 25, EnableRandomSymbols: true, EnableRandomSymbolsMobile: false, EnableDifferentDuration: true };
|
||||
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;
|
||||
|
||||
// Load Checkboxes
|
||||
const savedBallsString = config.Sports.SportsBalls || 'football,basketball,tennis,volleyball';
|
||||
const savedBalls = savedBallsString.split(',');
|
||||
document.querySelectorAll('.sport-ball-cb').forEach(cb => {
|
||||
// Support for both new category string and legacy filename strings
|
||||
cb.checked = savedBalls.some(b => b === cb.value || b.startsWith(cb.value + '_'));
|
||||
});
|
||||
|
||||
// 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;
|
||||
|
||||
// Halloween
|
||||
document.querySelector('#EnableHalloween').checked = config.Halloween.EnableHalloween;
|
||||
@@ -1781,13 +2000,14 @@
|
||||
document.querySelector('#EurovisionColors').value = config.Eurovision.EurovisionColors;
|
||||
document.querySelector('#EurovisionGlowSize').value = config.Eurovision.EurovisionGlowSize;
|
||||
|
||||
// Pi-Day
|
||||
document.querySelector('#EnablePiDay').checked = config.PiDay.EnablePiDay;
|
||||
document.querySelector('#PiDaySymbolCount').value = config.PiDay.SymbolCount;
|
||||
document.querySelector('#EnableRandomPiDay').checked = config.PiDay.EnableRandomPiDay;
|
||||
document.querySelector('#EnableRandomPiDayMobile').checked = config.PiDay.EnableRandomPiDayMobile;
|
||||
document.querySelector('#EnableDifferentDurationPiDay').checked = config.PiDay.EnableDifferentDuration;
|
||||
document.querySelector('#EnablePiDayBackground').checked = config.PiDay.EnablePiDayBackground !== undefined ? config.PiDay.EnablePiDayBackground : false;
|
||||
// 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;
|
||||
|
||||
// Pride
|
||||
document.querySelector('#EnablePride').checked = config.Pride.EnablePride;
|
||||
@@ -1815,6 +2035,25 @@
|
||||
document.querySelector('#StormRainSpeed').value = config.Storm.RainSpeed;
|
||||
document.querySelector('#StormEnableLightning').checked = config.Storm.EnableLightning;
|
||||
|
||||
// Underwater
|
||||
config.Underwater = config.Underwater || {};
|
||||
document.querySelector('#EnableUnderwater').checked = config.Underwater.EnableUnderwater !== false;
|
||||
document.querySelector('#EnableUnderwaterLightRays').checked = config.Underwater.EnableLightRays !== false;
|
||||
document.querySelector('#UnderwaterSymbolCount').value = config.Underwater.SymbolCount || 15;
|
||||
document.querySelector('#EnableRandomSymbolsUnderwater').checked = config.Underwater.EnableRandomSymbols !== false;
|
||||
document.querySelector('#EnableRandomSymbolsMobileUnderwater').checked = config.Underwater.EnableRandomSymbolsMobile === true;
|
||||
document.querySelector('#EnableDifferentDurationUnderwater').checked = config.Underwater.EnableDifferentDuration !== false;
|
||||
|
||||
// Birthday
|
||||
config.Birthday = config.Birthday || {};
|
||||
document.querySelector('#EnableBirthday').checked = config.Birthday.EnableBirthday !== false;
|
||||
document.querySelector('#EnableGarland').checked = config.Birthday.EnableGarland !== false;
|
||||
document.querySelector('#BirthdaySymbolCount').value = config.Birthday.SymbolCount || 25;
|
||||
document.querySelector('#BirthdayConfettiCount').value = config.Birthday.ConfettiCount || 60;
|
||||
document.querySelector('#EnableRandomSymbolsBirthday').checked = config.Birthday.EnableRandomSymbols !== false;
|
||||
document.querySelector('#EnableRandomSymbolsMobileBirthday').checked = config.Birthday.EnableRandomSymbolsMobile === true;
|
||||
document.querySelector('#EnableDifferentDurationBirthday').checked = config.Birthday.EnableDifferentDuration !== false;
|
||||
|
||||
Dashboard.hideLoadingMsg();
|
||||
});
|
||||
});
|
||||
@@ -1867,6 +2106,15 @@
|
||||
config.Sports.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomSymbolsMobileSports').checked;
|
||||
config.Sports.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationSports').checked;
|
||||
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
|
||||
if (!config.Olympia) config.Olympia = {};
|
||||
@@ -1882,23 +2130,37 @@
|
||||
config.Space.EnableRandomSymbols = document.querySelector('#EnableRandomSymbolsSpace').checked;
|
||||
config.Space.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomSymbolsMobileSpace').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
|
||||
if (!config.Underwater) config.Underwater = {};
|
||||
config.Underwater.EnableUnderwater = document.querySelector('#EnableUnderwater').checked;
|
||||
config.Underwater.EnableUnderwaterLightRays = document.querySelector('#EnableUnderwaterLightRays').checked;
|
||||
config.Underwater.EnableRandomSymbols = document.querySelector('#EnableRandomSymbolsUnderwater').checked;
|
||||
config.Underwater.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomSymbolsMobileUnderwater').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
|
||||
if (!config.Birthday) config.Birthday = {};
|
||||
config.Birthday.EnableBirthday = document.querySelector('#EnableBirthday').checked;
|
||||
config.Birthday.EnableGarland = document.querySelector('#EnableGarland').checked;
|
||||
config.Birthday.EnableRandomSymbols = document.querySelector('#EnableRandomSymbolsBirthday').checked;
|
||||
config.Birthday.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomSymbolsMobileBirthday').checked;
|
||||
config.Birthday.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationBirthday').checked;
|
||||
config.Birthday.SymbolCount = parseInt(document.querySelector('#BirthdaySymbolCount').value);
|
||||
config.Birthday.ConfettiCount = parseInt(document.querySelector('#BirthdayConfettiCount').value);
|
||||
|
||||
// Snowflakes
|
||||
config.Snowflakes.SnowflakeCount = parseInt(document.querySelector('#SnowflakesCount').value);
|
||||
@@ -2041,13 +2303,14 @@
|
||||
config.Eurovision.EurovisionColors = document.querySelector('#EurovisionColors').value;
|
||||
config.Eurovision.EurovisionGlowSize = parseInt(document.querySelector('#EurovisionGlowSize').value);
|
||||
|
||||
// Pi-Day
|
||||
config.PiDay.EnablePiDay = document.querySelector('#EnablePiDay').checked;
|
||||
config.PiDay.SymbolCount = parseInt(document.querySelector('#PiDaySymbolCount').value);
|
||||
config.PiDay.EnableRandomPiDay = document.querySelector('#EnableRandomPiDay').checked;
|
||||
config.PiDay.EnableRandomPiDayMobile = document.querySelector('#EnableRandomPiDayMobile').checked;
|
||||
config.PiDay.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationPiDay').checked;
|
||||
config.PiDay.EnablePiDayBackground = document.querySelector('#EnablePiDayBackground').checked;
|
||||
// Matrix
|
||||
config.Matrix.EnableMatrix = document.querySelector('#EnableMatrix').checked;
|
||||
config.Matrix.SymbolCount = parseInt(document.querySelector('#MatrixSymbolCount').value);
|
||||
config.Matrix.MatrixChars = document.querySelector('#MatrixChars').value;
|
||||
config.Matrix.EnableRandomMatrix = document.querySelector('#EnableRandomMatrix').checked;
|
||||
config.Matrix.EnableRandomMatrixMobile = document.querySelector('#EnableRandomMatrixMobile').checked;
|
||||
config.Matrix.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationMatrix').checked;
|
||||
config.Matrix.EnableMatrixBackground = document.querySelector('#EnableMatrixBackground').checked;
|
||||
|
||||
// Pride
|
||||
config.Pride.EnablePride = document.querySelector('#EnablePride').checked;
|
||||
@@ -2068,6 +2331,14 @@
|
||||
config.Storm.RainSpeed = parseFloat(document.querySelector('#StormRainSpeed').value);
|
||||
config.Storm.EnableLightning = document.querySelector('#StormEnableLightning').checked;
|
||||
|
||||
// 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;
|
||||
|
||||
// New Themes
|
||||
config.Frost.EnableFrost = document.querySelector('#EnableFrost').checked;
|
||||
config.FilmNoir.EnableFilmNoir = document.querySelector('#EnableFilmNoir').checked;
|
||||
@@ -2152,6 +2423,29 @@
|
||||
config.Eurovision.EurovisionColors = document.querySelector('#EurovisionColors').value;
|
||||
config.Eurovision.EurovisionGlowSize = parseInt(document.querySelector('#EurovisionGlowSize').value);
|
||||
|
||||
// Birthday
|
||||
config.Birthday.EnableBirthday = document.querySelector('#EnableBirthday').checked;
|
||||
config.Birthday.EnableGarland = document.querySelector('#EnableGarland').checked;
|
||||
config.Birthday.SymbolCount = parseInt(document.querySelector('#BirthdaySymbolCount').value);
|
||||
config.Birthday.ConfettiCount = parseInt(document.querySelector('#BirthdayConfettiCount').value);
|
||||
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;
|
||||
|
||||
// Pi-Day
|
||||
config.PiDay.EnablePiDay = document.querySelector('#EnablePiDay').checked;
|
||||
config.PiDay.SymbolCount = parseInt(document.querySelector('#PiDaySymbolCount').value);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<!-- <TreatWarningsAsErrors>false</TreatWarningsAsErrors> -->
|
||||
<Title>Jellyfin Seasonals Plugin</Title>
|
||||
<Authors>CodeDevMLH</Authors>
|
||||
<Version>1.7.2.0</Version>
|
||||
<Version>2.0.0.0</Version>
|
||||
<RepositoryUrl>https://github.com/CodeDevMLH/Jellyfin-Seasonals</RepositoryUrl>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -10,81 +10,144 @@
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
.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 {
|
||||
position: absolute;
|
||||
bottom: -10vh; /* balloons rise from bottom */
|
||||
animation: birthday-rise linear infinite;
|
||||
font-size: 3rem;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
animation: birthday-rise linear infinite forwards;
|
||||
opacity: 0.95;
|
||||
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; /* Allow hover over the actual item */
|
||||
cursor: crosshair;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* MARK: Balloon Size */
|
||||
.birthday-symbol img {
|
||||
width: 6vh;
|
||||
width: 18vh;
|
||||
height: auto;
|
||||
max-width: 60px;
|
||||
max-width: 100px;
|
||||
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 {
|
||||
position: absolute;
|
||||
top: -5vh;
|
||||
width: 8px;
|
||||
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;
|
||||
height: 10px;
|
||||
opacity: 0.9;
|
||||
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;
|
||||
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
|
||||
}
|
||||
|
||||
@keyframes birthday-rise {
|
||||
0% {
|
||||
transform: translateY(10vh) rotate(var(--start-rot, 0deg));
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 1;
|
||||
}
|
||||
90% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-110vh) rotate(calc(var(--start-rot, 0deg) * -1));
|
||||
opacity: 0;
|
||||
}
|
||||
0% { transform: translate3d(var(--x-pos, 0vw), 110vh, 0) rotate(var(--start-rot, 0deg)); opacity: 0; }
|
||||
10% { opacity: 1; }
|
||||
90% { opacity: 1; }
|
||||
100% { transform: translate3d(var(--x-pos, 0vw), -20vh, 0) rotate(calc(var(--start-rot, 0deg) * -1)); opacity: 0; }
|
||||
}
|
||||
|
||||
@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% {
|
||||
transform: translateY(-5vh) rotateX(0deg) rotateY(0deg) rotateZ(0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
5% {
|
||||
opacity: 1;
|
||||
}
|
||||
90% {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(105vh) rotateX(720deg) rotateY(360deg) rotateZ(180deg);
|
||||
transform: translateY(calc(var(--burst-y) + 150px)); /* Gravity pull downwards */
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,37 @@
|
||||
const config = window.SeasonalsPluginConfig?.Birthday || {};
|
||||
|
||||
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 enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false;
|
||||
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;
|
||||
|
||||
function toggleBirthday() {
|
||||
@@ -39,6 +65,72 @@ observer.observe(document.body, {
|
||||
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() {
|
||||
const container = document.querySelector('.birthday-container') || document.createElement('div');
|
||||
|
||||
@@ -48,17 +140,7 @@ function createBirthday() {
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
// Spawn Birthday Cake at the bottom
|
||||
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);
|
||||
// Cake and Garland have been removed
|
||||
|
||||
const standardCount = 15;
|
||||
const totalSymbols = symbolCount + standardCount;
|
||||
@@ -72,35 +154,87 @@ function createBirthday() {
|
||||
|
||||
const useRandomDuration = enableDifferentDuration !== false;
|
||||
|
||||
// We'll treat balloons and gifts as rising symbols
|
||||
const activeItems = ['balloon_red', 'balloon_blue', 'balloon_yellow', 'gift'];
|
||||
// Arrays moved to top of file
|
||||
|
||||
for (let i = 0; i < finalCount; i++) {
|
||||
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}`;
|
||||
|
||||
// Create inner div for sway
|
||||
let innerDiv = document.createElement('div');
|
||||
innerDiv.className = 'birthday-inner';
|
||||
|
||||
let img = document.createElement('img');
|
||||
img.src = `../Seasonals/Resources/birthday_images/${randomItem}.png`;
|
||||
img.src = randomImage;
|
||||
img.onerror = function() {
|
||||
this.style.display = 'none';
|
||||
this.parentElement.innerHTML = getBirthdayEmojiFallback(randomItem);
|
||||
symbol.remove(); // Remove element completely on error
|
||||
};
|
||||
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 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;
|
||||
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]);
|
||||
}
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
const startRot = (Math.random() * 20) - 10; // -10 to +10 spread
|
||||
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.animationDelay = `${delaySeconds}s`;
|
||||
|
||||
@@ -108,33 +242,73 @@ function createBirthday() {
|
||||
}
|
||||
|
||||
// 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 confettiCount = isMobile ? 40 : 80;
|
||||
const baseConfettiCount = config.ConfettiCount !== undefined ? config.ConfettiCount : 60;
|
||||
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++) {
|
||||
let confetti = document.createElement('div');
|
||||
confetti.className = 'birthday-confetti';
|
||||
const wrapper = document.createElement('div');
|
||||
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;
|
||||
|
||||
// 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;
|
||||
const delaySeconds = Math.random() * 8;
|
||||
const duration = Math.random() * 3 + 4;
|
||||
wrapper.style.setProperty('--x-pos', `${Math.random() * 100}vw`);
|
||||
wrapper.style.animationDelay = `${delay}s`;
|
||||
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`;
|
||||
confetti.style.animationDuration = `${duration}s`;
|
||||
confetti.style.animationDelay = `${delaySeconds}s`;
|
||||
|
||||
container.appendChild(confetti);
|
||||
swayWrapper.appendChild(confetti);
|
||||
container.appendChild(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
function getBirthdayEmojiFallback(type) {
|
||||
if (type.startsWith('balloon')) return '🎈';
|
||||
if (type === 'gift') return '🎁';
|
||||
return '';
|
||||
}
|
||||
/* Removed fallback logic */
|
||||
|
||||
function initializeBirthday() {
|
||||
if (!birthday) return;
|
||||
|
||||
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
BIN
Jellyfin.Plugin.Seasonals/Web/birthday_assets/balloon_violet.gif
Normal file
|
After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 108 KiB |
@@ -36,8 +36,12 @@ observer.observe(document.body, {
|
||||
|
||||
|
||||
function createMarioDay(container) {
|
||||
// MARK: Mario's running speed across the screen
|
||||
const marioSpeedSeconds = 18;
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'mario-wrapper';
|
||||
wrapper.style.animationDuration = `${marioSpeedSeconds}s`;
|
||||
|
||||
const mario = document.createElement('img');
|
||||
mario.className = 'mario-runner';
|
||||
@@ -55,7 +59,7 @@ function createMarioDay(container) {
|
||||
// Grab Mario's current screen position to lock the coin's X coordinate
|
||||
const marioRect = wrapper.getBoundingClientRect();
|
||||
coin.style.left = `${marioRect.left + 16}px`;
|
||||
coin.style.bottom = '35px'; // Adjust for wrapper's bottom offset
|
||||
coin.style.bottom = '35px'; // bottom offset
|
||||
|
||||
container.appendChild(coin);
|
||||
setTimeout(() => coin.remove(), 2000);
|
||||
|
||||
@@ -13,10 +13,48 @@
|
||||
.olympia-symbol {
|
||||
position: absolute;
|
||||
top: -10vh;
|
||||
animation: olympia-fall linear infinite;
|
||||
font-size: 3rem;
|
||||
opacity: 0.95;
|
||||
text-shadow: 0 0 10px rgba(255,255,255,0.2);
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
.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: 50%; left: 50%;
|
||||
transform: 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: -10vh;
|
||||
opacity: 0.95;
|
||||
text-shadow: 0 0 10px rgba(255,255,255,0.2);
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
.olympia-inner {
|
||||
display: inline-block;
|
||||
animation: olympia-sway linear infinite alternate;
|
||||
}
|
||||
|
||||
.olympia-symbol img {
|
||||
@@ -26,46 +64,76 @@
|
||||
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 {
|
||||
position: absolute;
|
||||
top: -5vh;
|
||||
width: 8px;
|
||||
height: 16px;
|
||||
opacity: 0.85;
|
||||
animation: olympia-confetti-fall linear infinite;
|
||||
border-radius: 4px; /* slightly rounder confetti */
|
||||
background-color: rgb(0, 0, 0);
|
||||
will-change: transform;
|
||||
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 {
|
||||
0% {
|
||||
transform: translateY(-10vh) rotate(var(--start-rot, 0deg));
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 1;
|
||||
}
|
||||
85% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(110vh) rotate(var(--end-rot, 360deg));
|
||||
opacity: 0;
|
||||
}
|
||||
0% { transform: translateY(-10vh); }
|
||||
100% { transform: translateY(110vh); }
|
||||
}
|
||||
|
||||
@keyframes olympia-confetti-fall {
|
||||
0% {
|
||||
transform: translateY(-5vh) rotateX(0deg) rotateY(0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
5% {
|
||||
opacity: 1;
|
||||
}
|
||||
90% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(105vh) rotateX(720deg) rotateY(360deg);
|
||||
opacity: 0;
|
||||
}
|
||||
@keyframes olympia-sway {
|
||||
0% { transform: rotate(-25deg) translateX(-20px); }
|
||||
100% { transform: rotate(25deg) translateX(20px); }
|
||||
}
|
||||
|
||||
@keyframes olympia-tumble-3d {
|
||||
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); }
|
||||
}
|
||||
|
||||
@keyframes olympia-confetti-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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,72 +60,189 @@ function createOlympia() {
|
||||
|
||||
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++) {
|
||||
let symbol = document.createElement('div');
|
||||
|
||||
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}`;
|
||||
|
||||
let img = document.createElement('img');
|
||||
img.src = `../Seasonals/Resources/olympia_images/${randomItem}.png`;
|
||||
img.onerror = function() {
|
||||
this.style.display = 'none';
|
||||
this.parentElement.innerHTML = getOlympiaEmojiFallback(randomItem);
|
||||
};
|
||||
symbol.appendChild(img);
|
||||
// Create inner div for sway/rotation
|
||||
let innerDiv = document.createElement('div');
|
||||
innerDiv.className = 'olympia-inner';
|
||||
let img = null;
|
||||
|
||||
const leftPos = Math.random() * 100;
|
||||
const delaySeconds = Math.random() * 10;
|
||||
|
||||
let durationSeconds = 8;
|
||||
if (useRandomDuration) {
|
||||
durationSeconds = Math.random() * 5 + 6; // 6 to 11 seconds
|
||||
if (isRing) {
|
||||
const ringColorMap = {
|
||||
'rings_blue': '#0081C8',
|
||||
'rings_yellow': '#FCB131',
|
||||
'rings_black': '#000000',
|
||||
'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.style.setProperty('--start-rot', `${startRot}deg`);
|
||||
symbol.style.setProperty('--end-rot', `${startRot + (Math.random() > 0.5 ? 360 : -360)}deg`);
|
||||
symbol.appendChild(innerDiv);
|
||||
|
||||
symbol.style.left = `${leftPos}vw`;
|
||||
symbol.style.animationDuration = `${durationSeconds}s`;
|
||||
const leftPos = Math.random() * 95;
|
||||
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.left = `${leftPos}vw`;
|
||||
|
||||
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 confettiCount = isMobile ? 30 : 60;
|
||||
|
||||
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');
|
||||
confetti.className = 'olympia-confetti';
|
||||
|
||||
const color = confettiColors[Math.floor(Math.random() * confettiColors.length)];
|
||||
confetti.style.backgroundColor = color;
|
||||
|
||||
const leftPos = Math.random() * 100;
|
||||
const delaySeconds = Math.random() * 8;
|
||||
const duration = Math.random() * 3 + 5;
|
||||
// Random shape
|
||||
const shape = Math.random();
|
||||
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`;
|
||||
confetti.style.animationDuration = `${duration}s`;
|
||||
confetti.style.animationDelay = `${delaySeconds}s`;
|
||||
|
||||
container.appendChild(confetti);
|
||||
swayWrapper.appendChild(confetti);
|
||||
wrapper.appendChild(swayWrapper);
|
||||
container.appendChild(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
function getOlympiaEmojiFallback(type) {
|
||||
if (type === 'gold') return '🥇';
|
||||
if (type === 'silver') return '🥈';
|
||||
if (type === 'bronze') return '🥉';
|
||||
if (type === 'torch') return '🔥';
|
||||
return '';
|
||||
}
|
||||
|
||||
function initializeOlympia() {
|
||||
if (!olympia) return;
|
||||
createOlympia();
|
||||
|
||||
@@ -10,13 +10,54 @@
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
.space-bg-glow {
|
||||
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 {
|
||||
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 {
|
||||
position: absolute;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
font-size: 3rem;
|
||||
opacity: 0.85;
|
||||
z-index: 9999;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.space-symbol img {
|
||||
@@ -29,30 +70,30 @@
|
||||
}
|
||||
|
||||
/* Specific elements scaling */
|
||||
.space-planet1, .space-planet2 { font-size: 4rem; }
|
||||
.space-planet1 img, .space-planet2 img { width: 8vh; max-width: 80px; }
|
||||
.space-star { font-size: 2rem; opacity: 0.6; }
|
||||
.space-star img { width: 3vh; max-width: 30px; }
|
||||
.space-planet img { width: 8vh; max-width: 80px; }
|
||||
.space-astronaut img { width: 10vh; max-width: 100px; }
|
||||
.space-satellite img { width: 12vh; max-width: 120px; }
|
||||
.space-iss img { width: 25vh; max-width: 180px; }
|
||||
.space-rocket img { width: 12vh; max-width: 120px; }
|
||||
|
||||
@keyframes space-drift-right {
|
||||
0% {
|
||||
transform: translateX(0) scaleX(-1);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(120vw) scaleX(-1);
|
||||
}
|
||||
0% { transform: translateX(-10vw) translateY(0) scaleX(-1); }
|
||||
50% { transform: translateX(60vw) translateY(-30vh) scaleX(-1); }
|
||||
100% { transform: translateX(140vw) translateY(0) scaleX(-1); }
|
||||
}
|
||||
|
||||
@keyframes space-drift-left {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-120vw);
|
||||
}
|
||||
0% { transform: translateX(10vw) translateY(0); }
|
||||
50% { transform: translateX(-60vw) translateY(30vh); }
|
||||
100% { transform: translateX(-140vw) translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes space-slow-spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes space-star-drift {
|
||||
from { transform: translateY(0); }
|
||||
to { transform: translateY(-100vh); }
|
||||
}
|
||||
|
||||
@@ -1,11 +1,41 @@
|
||||
const config = window.SeasonalsPluginConfig?.Space || {};
|
||||
|
||||
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 enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false;
|
||||
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;
|
||||
|
||||
function toggleSpace() {
|
||||
@@ -48,71 +78,198 @@ function createSpace() {
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
const standardCount = 15;
|
||||
const totalSymbols = symbolCount + standardCount;
|
||||
|
||||
let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
|
||||
let finalCount = totalSymbols;
|
||||
const standardPlanetCount = 4;
|
||||
const standardAstronautCount = 1;
|
||||
const standardSatelliteCount = 2;
|
||||
const standardIssCount = 1;
|
||||
const standardRocketCount = 1;
|
||||
|
||||
if (isMobile) {
|
||||
finalCount = enableRandomMobile ? totalSymbols : standardCount;
|
||||
let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
|
||||
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 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 symbol = document.createElement('div');
|
||||
|
||||
const randomItem = activeItems[Math.floor(Math.random() * activeItems.length)];
|
||||
symbol.className = `space-symbol space-${randomItem}`;
|
||||
let img = document.createElement('img');
|
||||
img.src = randomImage;
|
||||
img.onerror = function() {
|
||||
this.style.display = 'none';
|
||||
};
|
||||
symbol.appendChild(img);
|
||||
|
||||
let img = document.createElement('img');
|
||||
img.src = `../Seasonals/Resources/space_images/${randomItem}.png`;
|
||||
img.onerror = function() {
|
||||
this.style.display = 'none';
|
||||
this.parentElement.innerHTML = getSpaceEmojiFallback(randomItem);
|
||||
};
|
||||
symbol.appendChild(img);
|
||||
const topPos = Math.random() * 90; // 0 to 90vh
|
||||
|
||||
// Zero gravity sizes / speeds
|
||||
const depth = Math.random();
|
||||
// Make background elements (depth close to 0) much smaller than foreground
|
||||
const distanceScale = 0.15 + (depth * 0.85); // 0.15 to 1.0
|
||||
|
||||
symbol.style.zIndex = Math.floor(depth * 30) + 20;
|
||||
|
||||
const topPos = Math.random() * 90; // 0 to 90vh
|
||||
const delaySeconds = Math.random() * 10;
|
||||
|
||||
let durationSeconds = 15;
|
||||
if (useRandomDuration) {
|
||||
durationSeconds = Math.random() * 15 + 15; // 15 to 30 seconds for slow drift
|
||||
let durationSeconds = 30; // Very slow
|
||||
if (useRandomDuration) {
|
||||
durationSeconds = (1 - depth) * 40 + 30 + Math.random() * 10 - 5;
|
||||
}
|
||||
|
||||
// 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
|
||||
setInterval(() => {
|
||||
// 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) {
|
||||
if (type === 'planet1') return '🪐';
|
||||
if (type === 'planet2') return '🌍';
|
||||
if (type === 'star') return '⭐';
|
||||
if (type === 'astronaut') return '👨🚀';
|
||||
if (type === 'rocket') return '🚀';
|
||||
return '✨';
|
||||
createSpaceItem(planetImages, pCount, 'space-planet');
|
||||
createSpaceItem(astronautImages, aCount, 'space-astronaut');
|
||||
createSpaceItem(satelliteImages, sCount, 'space-satellite');
|
||||
createSpaceItem([issImage], iCount, 'space-iss');
|
||||
createSpaceItem(rocketImages, rCount, 'space-rocket');
|
||||
}
|
||||
|
||||
function initializeSpace() {
|
||||
|
||||
@@ -13,9 +13,12 @@
|
||||
.sports-symbol {
|
||||
position: absolute;
|
||||
top: -10vh;
|
||||
animation: sports-fall linear infinite;
|
||||
font-size: 3rem; /* Fallback emoji size */
|
||||
opacity: 0.9;
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
.sports-inner {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.sports-symbol img {
|
||||
@@ -35,6 +38,19 @@
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.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 {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
@@ -46,26 +62,60 @@
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@keyframes sports-fall {
|
||||
@keyframes sports-bounce {
|
||||
0% {
|
||||
transform: translateY(-10vh) rotate(var(--start-rot, 0deg));
|
||||
transform: translateY(-10vh);
|
||||
opacity: 0;
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
10% {
|
||||
5% {
|
||||
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;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(110vh) rotate(var(--end-rot, 360deg));
|
||||
transform: translateY(110vh);
|
||||
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 {
|
||||
0% {
|
||||
transform: translateY(-5vh) rotateX(0deg) rotateY(0deg);
|
||||
transform: translateY(-5vh) rotate3d(var(--rx), var(--ry), var(--rz), 0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
5% {
|
||||
@@ -75,7 +125,25 @@
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(105vh) rotateX(720deg) rotateY(360deg);
|
||||
transform: translateY(105vh) rotate3d(var(--rx), var(--ry), var(--rz), var(--rot-dir));
|
||||
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; }
|
||||
}
|
||||
|
||||
@@ -1,10 +1,29 @@
|
||||
const config = window.SeasonalsPluginConfig?.Sports || {};
|
||||
|
||||
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 enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false;
|
||||
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;
|
||||
|
||||
@@ -48,44 +67,54 @@ function createSports() {
|
||||
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');
|
||||
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);
|
||||
|
||||
const standardCount = 15;
|
||||
const totalSymbols = symbolCount + standardCount;
|
||||
|
||||
let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
|
||||
let finalCount = totalSymbols;
|
||||
let ballsPerCategory = symbolCount;
|
||||
|
||||
if (isMobile) {
|
||||
finalCount = enableRandomMobile ? totalSymbols : standardCount;
|
||||
if (isMobile && !enableRandomMobile) {
|
||||
ballsPerCategory = Math.min(symbolCount, 3);
|
||||
}
|
||||
|
||||
const useRandomDuration = enableDifferentDuration !== false;
|
||||
|
||||
// Standard sports items to spawn
|
||||
const activeItems = ['soccer', 'football', 'yellow_card', 'red_card', 'trophy'];
|
||||
// Map standard sports balls to spawn based on category configuration
|
||||
const rawSportsBalls = config.SportsBalls || 'football,basketball,tennis,volleyball';
|
||||
const chosenCategories = rawSportsBalls.split(',').map(s => s.trim()).filter(s => s !== '');
|
||||
|
||||
// Create falling sports items
|
||||
for (let i = 0; i < finalCount; i++) {
|
||||
const createBall = (randomItem) => {
|
||||
let symbol = document.createElement('div');
|
||||
|
||||
// Randomly pick an item
|
||||
const randomItem = activeItems[Math.floor(Math.random() * activeItems.length)];
|
||||
symbol.className = `sports-symbol sports-${randomItem}`;
|
||||
|
||||
// Create inner div for spinning rotation
|
||||
let innerDiv = document.createElement('div');
|
||||
innerDiv.className = 'sports-inner';
|
||||
|
||||
// Try load image
|
||||
let img = document.createElement('img');
|
||||
img.src = `../Seasonals/Resources/sports_images/${randomItem}.png`;
|
||||
img.src = `../Seasonals/Resources/sport_assets/${randomItem}.png`;
|
||||
img.onerror = function() {
|
||||
this.style.display = 'none'; // hide broken image icon
|
||||
this.parentElement.innerHTML = getEmojiFallback(randomItem); // inject emoji fallback
|
||||
symbol.remove();
|
||||
};
|
||||
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;
|
||||
|
||||
let durationSeconds = 8;
|
||||
@@ -93,16 +122,93 @@ function createSports() {
|
||||
durationSeconds = Math.random() * 4 + 6; // 6 to 10 seconds
|
||||
}
|
||||
|
||||
// Add a random slight rotation difference
|
||||
const startRot = Math.random() * 360;
|
||||
symbol.style.setProperty('--start-rot', `${startRot}deg`);
|
||||
symbol.style.setProperty('--end-rot', `${startRot + (Math.random() > 0.5 ? 360 : -360)}deg`);
|
||||
// Add a random spin
|
||||
const spinRot = (Math.random() > 0.5 ? 360 : -360) + "deg";
|
||||
innerDiv.style.setProperty('--spin-rot', spinRot);
|
||||
|
||||
// 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.animationDuration = `${durationSeconds}s`;
|
||||
symbol.style.animationDelay = `${delaySeconds}s`;
|
||||
|
||||
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(launchTrophy, Math.random() * 20000 + 10000); // Wait 10-30s until next trophy
|
||||
}
|
||||
|
||||
// Launch initial trophy after a short delay
|
||||
if (enableTrophy) {
|
||||
setTimeout(launchTrophy, Math.random() * 5000 + 2000);
|
||||
}
|
||||
|
||||
// Add Germany Colored confetti (Black, Red, Gold)
|
||||
@@ -116,6 +222,23 @@ function createSports() {
|
||||
const color = confettiColors[Math.floor(Math.random() * confettiColors.length)];
|
||||
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 delaySeconds = Math.random() * 8;
|
||||
const duration = Math.random() * 3 + 4; // 4 to 7 seconds
|
||||
@@ -123,19 +246,18 @@ function createSports() {
|
||||
confetti.style.left = `${leftPos}vw`;
|
||||
confetti.style.animationDuration = `${duration}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);
|
||||
}
|
||||
}
|
||||
|
||||
function getEmojiFallback(type) {
|
||||
if (type === 'soccer') return '⚽';
|
||||
if (type === 'football') return '🏈';
|
||||
if (type === 'yellow_card') return '🟨';
|
||||
if (type === 'red_card') return '🟥';
|
||||
if (type === 'trophy') return '🏆';
|
||||
return '';
|
||||
}
|
||||
/* Removed legacy fallback logic */
|
||||
|
||||
function initializeSports() {
|
||||
if (!sports) return;
|
||||
|
||||
@@ -256,7 +256,7 @@
|
||||
<option value="matrix">Matrix</option>
|
||||
<option value="pride">Pride</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="filmnoir">Film-Noir</option>
|
||||
<option value="oscar">Oscar Awards</option>
|
||||
@@ -265,8 +265,8 @@
|
||||
<option value="oktoberfest">Oktoberfest</option>
|
||||
<option value="friday13">Friday the 13th</option>
|
||||
<option value="eid">Eid al-Fitr</option>
|
||||
<option value="sports">Sports / Football</option>
|
||||
<option value="olympia">Olympia / Games</option>
|
||||
<option value="sports">Sports</option>
|
||||
<option value="olympia">Olympia</option>
|
||||
<option value="space">Space / Sci-Fi</option>
|
||||
<option value="underwater">Underwater</option>
|
||||
<option value="birthday">Birthday</option>
|
||||
@@ -511,11 +511,11 @@
|
||||
const container = document.querySelector('.seasonals-container');
|
||||
container.className = `seasonals-container ${containerClass}`;
|
||||
|
||||
// Inject CSS
|
||||
// Inject CSS with cache-buster
|
||||
if (cssFile) {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = cssFile;
|
||||
link.href = cssFile + '?v=' + new Date().getTime();
|
||||
link.setAttribute('data-seasonal', 'true');
|
||||
link.onerror = () => console.error(`[Test Site] Failed to load CSS: ${cssFile}`);
|
||||
document.head.appendChild(link);
|
||||
@@ -525,7 +525,7 @@
|
||||
if (jsFile) {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const response = await fetch(jsFile);
|
||||
const response = await fetch(jsFile + '?v=' + new Date().getTime());
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
const code = await response.text();
|
||||
|
||||
|
||||
@@ -62,16 +62,29 @@
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
@keyframes underwater-swim-right {
|
||||
0% { transform: translateX(0) translateY(0) scaleX(-1); }
|
||||
50% { transform: translateX(65vw) translateY(-5vh) scaleX(-1); }
|
||||
100% { transform: translateX(130vw) translateY(0) scaleX(-1); }
|
||||
@keyframes underwater-traverse-right {
|
||||
0% { left: -25vw; }
|
||||
100% { left: 125vw; }
|
||||
}
|
||||
|
||||
@keyframes underwater-swim-left {
|
||||
0% { transform: translateX(0) translateY(0); }
|
||||
50% { transform: translateX(-65vw) translateY(5vh); }
|
||||
100% { transform: translateX(-130vw) translateY(0); }
|
||||
@keyframes underwater-traverse-left {
|
||||
0% { left: 125vw; }
|
||||
100% { left: -25vw; }
|
||||
}
|
||||
|
||||
@keyframes underwater-traverse-up {
|
||||
0% { top: 120vh; }
|
||||
100% { top: -20vh; }
|
||||
}
|
||||
|
||||
@keyframes underwater-traverse-down {
|
||||
0% { top: -20vh; }
|
||||
100% { top: 120vh; }
|
||||
}
|
||||
|
||||
@keyframes underwater-sway-y {
|
||||
0% { transform: translateY(-2vh); }
|
||||
100% { transform: translateY(2vh); }
|
||||
}
|
||||
|
||||
@keyframes underwater-sway {
|
||||
@@ -86,3 +99,30 @@
|
||||
90% { opacity: 0; }
|
||||
100% { transform: translateY(-110vh) translateX(10px); opacity: 0; }
|
||||
}
|
||||
|
||||
.underwater-god-rays {
|
||||
position: absolute;
|
||||
top: -50vh;
|
||||
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);
|
||||
}
|
||||
|
||||
@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; }
|
||||
}
|
||||
|
||||
@@ -5,6 +5,78 @@ const symbolCount = config.SymbolCount || 15;
|
||||
const useRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true;
|
||||
const enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false;
|
||||
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;
|
||||
|
||||
@@ -46,100 +118,197 @@ function createUnderwater() {
|
||||
container.className = 'underwater-container';
|
||||
container.setAttribute("aria-hidden", "true");
|
||||
document.body.appendChild(container);
|
||||
} else {
|
||||
container.innerHTML = ''; // Prevent infinite duplication on theme reload!
|
||||
}
|
||||
|
||||
// Deep blue overlay
|
||||
const bg = document.createElement('div');
|
||||
bg.className = 'underwater-bg';
|
||||
container.appendChild(bg);
|
||||
|
||||
const standardCount = 8;
|
||||
const totalSymbols = symbolCount + standardCount;
|
||||
|
||||
let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
|
||||
let finalCount = totalSymbols;
|
||||
|
||||
if (isMobile) {
|
||||
finalCount = enableRandomMobile ? totalSymbols : standardCount;
|
||||
// Light Rays (God Rays)
|
||||
if (enableLightRays) {
|
||||
const rays = document.createElement('div');
|
||||
rays.className = 'underwater-god-rays';
|
||||
container.appendChild(rays);
|
||||
}
|
||||
|
||||
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');
|
||||
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`;
|
||||
|
||||
// Randomly flip
|
||||
if (Math.random() > 0.5) {
|
||||
seaweed.style.transform = 'scaleX(-1)';
|
||||
}
|
||||
// Random parallax scale for seaweed depth
|
||||
const depth = Math.random();
|
||||
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');
|
||||
img.src = '../Seasonals/Resources/underwater_images/seaweed.png';
|
||||
img.onerror = function() {
|
||||
this.style.display = 'none';
|
||||
this.parentElement.innerHTML = '🌿';
|
||||
this.parentElement.style.fontSize = '3rem';
|
||||
this.parentElement.style.bottom = '0';
|
||||
this.parentElement.style.transformOrigin = 'bottom center';
|
||||
};
|
||||
seaweed.appendChild(img);
|
||||
// Mix Emojis and GIFs
|
||||
if (Math.random() > 0.4) {
|
||||
let img = document.createElement('img');
|
||||
img.src = seaweeds[Math.floor(Math.random() * seaweeds.length)];
|
||||
img.onerror = function() {
|
||||
this.style.display = 'none';
|
||||
};
|
||||
seaweed.appendChild(img);
|
||||
} else {
|
||||
seaweed.innerHTML = '🌿';
|
||||
seaweed.style.fontSize = '3rem';
|
||||
seaweed.style.bottom = '0';
|
||||
seaweed.style.transformOrigin = 'bottom center';
|
||||
}
|
||||
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 symbol = document.createElement('div');
|
||||
let img = document.createElement('img');
|
||||
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)];
|
||||
symbol.className = `underwater-symbol underwater-${randomItem}`;
|
||||
// Randomize the actual amount spawned up to the limit
|
||||
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');
|
||||
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() {
|
||||
this.style.display = 'none';
|
||||
this.parentElement.innerHTML = getUnderwaterEmojiFallback(randomItem);
|
||||
};
|
||||
symbol.appendChild(img);
|
||||
|
||||
const topPos = 10 + Math.random() * 80; // 10 to 90vh
|
||||
const delaySeconds = Math.random() * 10;
|
||||
const depth = Math.random();
|
||||
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;
|
||||
if (useRandomDuration) {
|
||||
durationSeconds = Math.random() * 10 + 15; // 15 to 25 seconds slow swimming
|
||||
}
|
||||
symbol.style.opacity = `${opacity}`;
|
||||
symbol.style.filter = `blur(${blurAmount}px)`;
|
||||
symbol.style.zIndex = Math.floor(depth * 30) + 10;
|
||||
|
||||
symbol.style.animationIterationCount = 'infinite';
|
||||
|
||||
// Randomly pick direction: left-to-right OR right-to-left
|
||||
const goRight = Math.random() > 0.5;
|
||||
if (goRight) {
|
||||
symbol.style.animationName = 'underwater-swim-right';
|
||||
symbol.style.left = '-10vw';
|
||||
let durationSeconds = (1 - depth) * 20 + 15 + Math.random() * 5;
|
||||
if (!useRandomDuration) durationSeconds = 20;
|
||||
|
||||
// Apply a negative delay on spawn so they start mid-screen scattered
|
||||
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 {
|
||||
symbol.style.animationName = 'underwater-swim-left';
|
||||
symbol.style.right = '-10vw';
|
||||
symbol.style.transform = 'scaleX(-1)'; // flip fish to face left
|
||||
const goRight = Math.random() > 0.5;
|
||||
const directionScale = goRight ? 'scaleX(-1)' : 'scaleX(1)';
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
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() {
|
||||
if (!underwater) return;
|
||||
|
||||
@@ -8,9 +8,17 @@
|
||||
"category": "General",
|
||||
"imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/raw/branch/main/logo.png",
|
||||
"versions": [
|
||||
{
|
||||
"version": "2.0.0.0",
|
||||
"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.0/Jellyfin.Plugin.Seasonals.zip",
|
||||
"checksum": "11a55b3af5536ad6b3d281746e61f8e6",
|
||||
"timestamp": "2026-02-27T00:20:15Z"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.7.2.0/Jellyfin.Plugin.Seasonals.zip",
|
||||
"checksum": "34c8426c48bd7d470c3e8dc7f02f86da",
|
||||
|
||||