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 = + `` + + drops.join('') + + ``; + + 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