diff --git a/scripts/homeScreen.css b/scripts/homeScreen.css
index d43a12a..7e2f3c5 100644
--- a/scripts/homeScreen.css
+++ b/scripts/homeScreen.css
@@ -323,6 +323,11 @@ div#discovery-loading-indicator:not(.show)::after {
opacity: 1;
}
}
+/* TV: rail must be fully visible while focused (no hover) */
+.layout-tv #kefin-home-category-rail:focus-within,
+.layout-tv #kefin-home-category-rail:focus-within .kefin-home-category-rail {
+ opacity: 1;
+}
.kefin-home-chrome.hide,
#kefin-home-category-rail.hide,
#kefin-home-category-rail.hide .kefin-home-category-rail {
@@ -603,12 +608,10 @@ html[data-active-page="home"] body[data-seasonal-background="true"] #reactRoot >
html[data-active-page="home"] [data-seasonal-theme="seasonal-halloween"][data-seasonal-background="true"] header.MuiAppBar-root,
html[data-active-page="home"] [data-seasonal-theme="seasonal-halloween"][data-seasonal-background="true"] .skinHeader:not(.osdHeader) .headerTop {
+ position: relative;
box-shadow: 0 3px 12px rgba(180, 0, 0, 0.7) !important;
border-bottom: 2px solid #8b0000;
}
-html[data-active-page="home"] [data-seasonal-theme="seasonal-halloween"][data-seasonal-background="true"] .skinHeader:not(.osdHeader) .headerTop {
- position: relative;
-}
html[data-active-page="home"] [data-seasonal-theme="seasonal-halloween"][data-seasonal-background="true"] header.MuiAppBar-root::after,
html[data-active-page="home"] [data-seasonal-theme="seasonal-halloween"][data-seasonal-background="true"] .skinHeader:not(.osdHeader) .headerTop::after {
@@ -627,36 +630,27 @@ html[data-active-page="home"] [data-seasonal-theme="seasonal-halloween"][data-se
mask-size: 100% 100%;
}
+/* Procedural blood edge (SVG via --halloween-blood-splatter from homeScreen3.js) */
html[data-active-page="home"] [data-seasonal-theme="seasonal-halloween"][data-seasonal-background="true"] header.MuiAppBar-root::before,
html[data-active-page="home"] [data-seasonal-theme="seasonal-halloween"][data-seasonal-background="true"] .skinHeader:not(.osdHeader) .headerTop::before {
content: "";
position: absolute;
- top: 0;
+ top: auto;
+ bottom: 0;
left: 0;
width: 100%;
- height: 100%;
+ height: var(--halloween-blood-band, 10px);
pointer-events: none;
- opacity: 0.88;
- background-image:
- url('data:image/svg+xml;utf8,'),
- url('data:image/svg+xml;utf8,'),
- url('data:image/svg+xml;utf8,'),
- url('data:image/svg+xml;utf8,'),
- url('data:image/svg+xml;utf8,'),
- radial-gradient(ellipse at center, #990000 0%, #990000 50%, transparent 65%),
- radial-gradient(circle, #7a0000 0%, #7a0000 55%, transparent 60%),
- radial-gradient(ellipse at center, #550000 0%, #550000 50%, transparent 65%),
- radial-gradient(circle, #8b0000 0%, #8b0000 55%, transparent 60%),
- radial-gradient(circle, #7a0000 0%, #7a0000 55%, transparent 60%);
+ opacity: var(--halloween-blood-opacity, 0.95);
+ z-index: 1;
+ background-image: var(--halloween-blood-splatter, none);
background-repeat: no-repeat !important;
- background-size:
- 54px 40px,
- 36px 48px,
- 58px 58px,
- 120px 30px, 90px 38px,
- 10px 6px, 6px 6px, 12px 7px, 5px 5px, 8px 8px;
- background-position:
- 7% 2px, 31% 8px, 68% -4px,
- 15% 16px, 82% 6px,
- 21% 4px, 44% 20px, 52% 6px, 76% 24px, 94% 10px;
+ background-size: 100% 100%;
+ background-position: bottom center;
+}
+
+@media (hover: hover) and (pointer: fine) and (min-width: 900px) {
+ #kefin-home-category-rail .kefin-home-category-rail.kefin-home-chrome {
+ pointer-events: auto;
+ }
}
\ No newline at end of file
diff --git a/scripts/homeScreen3.js b/scripts/homeScreen3.js
index 009a086..bc1da9e 100644
--- a/scripts/homeScreen3.js
+++ b/scripts/homeScreen3.js
@@ -385,6 +385,129 @@
observer.observe(sectionEl, { childList: true, subtree: true });
}
+ function mulberry32(seed) {
+ let t = seed >>> 0;
+ return () => {
+ t += 0x6D2B79F5;
+ let r = Math.imul(t ^ (t >>> 15), 1 | t);
+ r ^= r + Math.imul(r ^ (r >>> 7), 61 | r);
+ return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
+ };
+ }
+
+ /**
+ * Dense procedural blood edge for Halloween header (mostly filled, tiny gaps,
+ * uneven height up to 10px). Returns a CSS url(...) data SVG.
+ */
+ function generateHalloweenBloodSplatter({
+ width = 1400,
+ height = 10,
+ dropletCount = 420,
+ clusterCount = 5,
+ gapCount = 4,
+ seed = (Date.now() ^ (Math.random() * 1e9)) >>> 0,
+ } = {}) {
+ const rand = mulberry32(seed);
+
+ const clusters = Array.from({ length: clusterCount }, () => ({
+ x: rand() * width,
+ r: 60 + rand() * 120,
+ weight: 1.4 + rand() * 1.8,
+ tall: rand() < 0.55,
+ }));
+
+ const gaps = Array.from({ length: gapCount }, () => ({
+ x: rand() * width,
+ r: 14 + rand() * 28,
+ }));
+
+ const inGap = (x) => gaps.some((g) => Math.abs(x - g.x) < g.r);
+
+ const sampleCluster = (x) => {
+ let boost = 0;
+ let tall = false;
+ for (const c of clusters) {
+ const d = Math.abs(x - c.x) / c.r;
+ if (d < 1) {
+ const w = c.weight * (1 - d) * (1 - d);
+ boost += w;
+ if (c.tall && w > 0.35) tall = true;
+ }
+ }
+ return { boost, tall };
+ };
+
+ const drops = [];
+ let attempts = 0;
+ while (drops.length < dropletCount && attempts < dropletCount * 16) {
+ attempts += 1;
+ const x = rand() * width;
+ if (inGap(x)) continue;
+
+ const { boost, tall } = sampleCluster(x);
+ const accept = 0.82 + Math.min(0.18, boost * 0.08);
+ if (rand() > accept) continue;
+
+ let maxRise = 3.5 + rand() * 2.5;
+ if (tall) maxRise = height * (0.75 + rand() * 0.25);
+ else if (rand() < 0.06) maxRise = height * (0.7 + rand() * 0.3);
+
+ const y = height - rand() * maxRise;
+ const maxR = tall ? (0.9 + rand() * 1.6) : (0.45 + rand() * 1.1);
+ const r = 0.3 + rand() * maxR;
+ const fill = rand() < 0.3 ? '#990000' : (rand() < 0.55 ? '#8b0000' : '#7a0000');
+
+ if (rand() < 0.16) {
+ drops.push(
+ ``,
+ );
+ } else {
+ drops.push(``);
+ }
+ }
+
+ const svg =
+ ``;
+
+ return {
+ seed,
+ cssUrl: `url("data:image/svg+xml,${encodeURIComponent(svg)}")`,
+ };
+ }
+
+ const HALLOWEEN_BLOOD_STYLE_ID = 'kefin-halloween-blood-styles';
+
+ function clearHalloweenBloodInlinePollution(body = document.body) {
+ if (!body) return;
+ body.style.removeProperty('--halloween-blood-band');
+ body.style.removeProperty('--halloween-blood-opacity');
+ body.style.removeProperty('--halloween-blood-splatter');
+ delete body.dataset.halloweenBloodSeed;
+ try {
+ sessionStorage.removeItem('kefin-halloween-blood-seed');
+ } catch (_) { /* private mode */ }
+ }
+
+ /** Inject procedural blood SVG via a