From df29e1269909253e705ca21e6d2ea3c9c3198d41 Mon Sep 17 00:00:00 2001
From: CodeDevMLH <145071728+CodeDevMLH@users.noreply.github.com>
Date: Tue, 24 Feb 2026 19:22:10 +0100
Subject: [PATCH] Add Frost feature: implement CSS and JS for frost effects and
visibility toggle
---
Jellyfin.Plugin.Seasonals/Web/frost.css | 74 ++++++++++++++++++++++++
Jellyfin.Plugin.Seasonals/Web/frost.js | 75 +++++++++++++++++++++++++
2 files changed, 149 insertions(+)
create mode 100644 Jellyfin.Plugin.Seasonals/Web/frost.css
create mode 100644 Jellyfin.Plugin.Seasonals/Web/frost.js
diff --git a/Jellyfin.Plugin.Seasonals/Web/frost.css b/Jellyfin.Plugin.Seasonals/Web/frost.css
new file mode 100644
index 0000000..eeb846c
--- /dev/null
+++ b/Jellyfin.Plugin.Seasonals/Web/frost.css
@@ -0,0 +1,74 @@
+.frost-container {
+ display: block;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: 10;
+ overflow: hidden;
+ contain: strict;
+}
+
+.frost-layer {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ /* A glowing white-blue gradient from edges */
+ background: radial-gradient(ellipse at center, transparent 60%, rgba(180, 220, 255, 0.4) 100%);
+ box-shadow: inset 0 0 60px rgba(200, 230, 255, 0.5), inset 0 0 120px rgba(255, 255, 255, 0.3);
+
+ filter: url('#frost-filter');
+
+ animation: frost-creep 4s ease-out forwards;
+}
+
+/* Subtle repeating star/crystal pattern */
+.frost-crystals {
+ position: absolute;
+ top: -5%;
+ left: -5%;
+ width: 110%;
+ height: 110%;
+ /* Use multi-layered star patterns for a random, crystalline spread */
+ background-image:
+ url('data:image/svg+xml;utf8,'),
+ url('data:image/svg+xml;utf8,'),
+ url('data:image/svg+xml;utf8,');
+ background-repeat: repeat;
+ background-size: 110px 110px, 60px 60px, 30px 30px;
+ background-position: 0 0, 15px 15px, 5px 10px;
+ mix-blend-mode: overlay;
+
+ /* Mask out the center so crystals only appear strongly on the edges */
+ -webkit-mask-image: radial-gradient(ellipse at center, transparent 50%, black 100%);
+ mask-image: radial-gradient(ellipse at center, transparent 50%, black 100%);
+
+ animation: frost-shimmer 6s infinite alternate ease-in-out;
+}
+
+@keyframes frost-creep {
+ 0% {
+ opacity: 0;
+ box-shadow: inset 0 0 10px rgba(200, 230, 255, 0);
+ }
+ 100% {
+ opacity: 1;
+ box-shadow: inset 0 0 60px rgba(200, 230, 255, 0.5), inset 0 0 120px rgba(255, 255, 255, 0.3);
+ }
+}
+
+@keyframes frost-shimmer {
+ 0% {
+ opacity: 0.4;
+ transform: scale(1);
+ }
+ 100% {
+ opacity: 0.8;
+ transform: scale(1.02);
+ }
+}
diff --git a/Jellyfin.Plugin.Seasonals/Web/frost.js b/Jellyfin.Plugin.Seasonals/Web/frost.js
new file mode 100644
index 0000000..dd5e6f9
--- /dev/null
+++ b/Jellyfin.Plugin.Seasonals/Web/frost.js
@@ -0,0 +1,75 @@
+const config = window.SeasonalsPluginConfig?.Frost || {};
+
+const frost = config.EnableFrost !== undefined ? config.EnableFrost : true;
+
+let msgPrinted = false;
+
+function toggleFrost() {
+ const container = document.querySelector('.frost-container');
+ if (!container) return;
+
+ const videoPlayer = document.querySelector('.videoPlayerContainer');
+ const trailerPlayer = document.querySelector('.youtubePlayerContainer');
+ const isDashboard = document.body.classList.contains('dashboardDocument');
+ const hasUserMenu = document.querySelector('#app-user-menu');
+
+ if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) {
+ container.style.display = 'none';
+ if (!msgPrinted) {
+ console.log('Frost hidden');
+ msgPrinted = true;
+ }
+ } else {
+ container.style.display = 'block';
+ if (msgPrinted) {
+ console.log('Frost visible');
+ msgPrinted = false;
+ }
+ }
+}
+
+const observer = new MutationObserver(toggleFrost);
+observer.observe(document.body, {
+ childList: true,
+ subtree: true,
+ attributes: true
+});
+
+function createFrost(container) {
+ const frostLayer = document.createElement('div');
+ frostLayer.className = 'frost-layer';
+
+ const frostCrystals = document.createElement('div');
+ frostCrystals.className = 'frost-crystals';
+
+ // An SVG filter to make things look "frozen"/distorted around the edges
+ const svgFilter = document.createElement('div');
+ svgFilter.innerHTML = `
+
+ `;
+
+ frostLayer.appendChild(frostCrystals);
+ container.appendChild(frostLayer);
+ container.appendChild(svgFilter);
+}
+
+function initializeFrost() {
+ if (!frost) return;
+
+ const container = document.querySelector('.frost-container') || document.createElement("div");
+
+ if (!document.querySelector('.frost-container')) {
+ container.className = "frost-container";
+ container.setAttribute("aria-hidden", "true");
+ document.body.appendChild(container);
+ }
+
+ createFrost(container);
+}
+
+initializeFrost();