/*! * HDR Web Plugin v0.6.2 * Drop-in pure-black HDR media stage for web pages. */ (function attach(root, factory) { const api = factory(); if (typeof module === 'object' && module.exports) { module.exports = api; return; } root.HDREnhancer = api; if (!root.document) return; const script = root.document.currentScript; const shouldAutoRun = api.shouldAutoRun(script); if (shouldAutoRun) { const scriptOptions = api.readScriptOptions(script); api.ready(() => api.install({ ...scriptOptions, window: root })); } })(typeof globalThis !== 'undefined' ? globalThis : this, function createHDREnhancer() { const PLUGIN_NAME = 'HDR Web Plugin'; const VERSION = '0.6.2'; const STYLE_ID = 'hdr-enhancer-style'; const OVERLAY_ID = 'hdr-enhancer-overlay'; const BADGE_ID = 'hdr-enhancer-badge'; const SCAN_ID = 'hdr-enhancer-scan'; const CANVAS_ID = 'hdr-enhancer-canvas'; const VIDEO_ID = 'hdr-enhancer-video'; const IMAGE_ID = 'hdr-enhancer-image'; const DEFAULT_HDR_VIDEO = 'media/hdr-galaxy-fireworks.mp4'; const DEFAULT_HDR_WEBM = 'media/hdr-galaxy-fireworks.webm'; const DEFAULT_HDR_IMAGE = 'media/hdr-landing-prism.png'; function getWindow(input) { if (input && input.window) return input.window; if (input && input.document) return input; if (typeof window !== 'undefined') return window; return null; } function mediaMatches(target, query) { try { return Boolean(target && target.matchMedia && target.matchMedia(query).matches); } catch (_) { return false; } } function cssSupports(target, property, value) { try { return Boolean(target && target.CSS && target.CSS.supports(property, value)); } catch (_) { return false; } } function detectCapabilities(input) { const target = getWindow(input) || input || {}; const rec2020 = mediaMatches(target, '(color-gamut: rec2020)'); const p3 = mediaMatches(target, '(color-gamut: p3)'); return { dynamicRange: mediaMatches(target, '(dynamic-range: high)') ? 'high' : 'standard', videoDynamicRange: mediaMatches(target, '(video-dynamic-range: high)') ? 'high' : 'standard', colorGamut: rec2020 ? 'rec2020' : p3 ? 'p3' : 'srgb', supportsDisplayP3: cssSupports(target, 'color', 'color(display-p3 1 0 0)'), supportsRec2020: cssSupports(target, 'color', 'color(rec2020 1 0 0)'), supportsRelativeColor: cssSupports(target, 'color', 'color(from white srgb r g b)'), supportsDynamicRangeLimit: cssSupports(target, 'dynamic-range-limit', 'no-limit'), supportsHdrColor: cssSupports(target, 'color', 'hdr-color(color(display-p3 1 1 1) 0, color(rec2100-pq 1 1 1) 2)'), prefersReducedMotion: mediaMatches(target, '(prefers-reduced-motion: reduce)') }; } function clamp(value, min, max) { const number = Number(value); if (!Number.isFinite(number)) return min; return Math.max(min, Math.min(max, number)); } function readDatasetValue(script, name) { if (!script || !script.dataset) return undefined; return script.dataset[name]; } function parseBoolean(value) { if (value === undefined) return undefined; if (value === true || value === 'true' || value === '') return true; if (value === false || value === 'false') return false; return undefined; } function parseNumber(value) { if (value === undefined || value === '') return undefined; const number = Number(value); return Number.isFinite(number) ? number : undefined; } function normalizeQuality(value) { if (value === 'low' || value === 'balanced' || value === 'high') return value; return undefined; } function normalizeMotion(value) { if (value === 'static' || value === 'burst' || value === 'ambient') return value; return undefined; } function normalizeMediaType(value) { if (value === 'video' || value === 'image') return value; return undefined; } function scriptAssetBase(script) { if (!script || !script.src || typeof URL === 'undefined') return undefined; try { return new URL('../', script.src).href; } catch (_) { return undefined; } } function readScriptOptions(script) { const options = {}; const intensity = parseNumber(readDatasetValue(script, 'hdrIntensity')); const animated = parseBoolean(readDatasetValue(script, 'hdrAnimated')); const overlay = parseBoolean(readDatasetValue(script, 'hdrOverlay')); const scanlines = parseBoolean(readDatasetValue(script, 'hdrScanlines')); const badge = parseBoolean(readDatasetValue(script, 'hdrBadge')); const effects = parseBoolean(readDatasetValue(script, 'hdrEffects')); const effect = readDatasetValue(script, 'hdrEffect'); const quality = normalizeQuality(readDatasetValue(script, 'hdrQuality')); const fps = parseNumber(readDatasetValue(script, 'hdrFps')); const maxDpr = parseNumber(readDatasetValue(script, 'hdrMaxDpr')); const motion = normalizeMotion(readDatasetValue(script, 'hdrMotion')); const media = parseBoolean(readDatasetValue(script, 'hdrMedia')); const blackout = parseBoolean(readDatasetValue(script, 'hdrBlackout')); const text = parseBoolean(readDatasetValue(script, 'hdrText')); const textBoost = parseNumber(readDatasetValue(script, 'hdrTextBoost')); const videoSrc = readDatasetValue(script, 'hdrVideoSrc'); const webmSrc = readDatasetValue(script, 'hdrWebmSrc'); const imageSrc = readDatasetValue(script, 'hdrImageSrc'); const textSrc = readDatasetValue(script, 'hdrTextSrc'); const mediaType = normalizeMediaType(readDatasetValue(script, 'hdrMediaType')); const assetBase = scriptAssetBase(script); if (intensity !== undefined) options.intensity = intensity; if (animated !== undefined) options.animated = animated; if (overlay !== undefined) options.overlay = overlay; if (scanlines !== undefined) options.scanlines = scanlines; if (badge !== undefined) options.badge = badge; if (effects !== undefined) options.effects = effects; if (effect !== undefined && effect !== '') options.effect = effect; if (quality !== undefined) options.quality = quality; if (fps !== undefined) options.fps = fps; if (maxDpr !== undefined) options.maxDpr = maxDpr; if (motion !== undefined) options.motion = motion; if (media !== undefined) options.media = media; if (blackout !== undefined) options.blackout = blackout; if (text !== undefined) options.text = text; if (textBoost !== undefined) options.textBoost = textBoost; if (videoSrc !== undefined && videoSrc !== '') options.videoSrc = videoSrc; if (webmSrc !== undefined && webmSrc !== '') options.webmSrc = webmSrc; if (imageSrc !== undefined && imageSrc !== '') options.imageSrc = imageSrc; if (textSrc !== undefined && textSrc !== '') options.textSrc = textSrc; if (mediaType !== undefined) options.mediaType = mediaType; if (assetBase !== undefined) options.assetBase = assetBase; return options; } function shouldAutoRun(script) { const hdrAuto = parseBoolean(readDatasetValue(script, 'hdrAuto')); const legacyAuto = parseBoolean(readDatasetValue(script, 'auto')); if (hdrAuto !== undefined) return hdrAuto; if (legacyAuto !== undefined) return legacyAuto; return true; } function resolveAssetUrl(path, base) { if (!path) return path; if (typeof URL === 'undefined' || !base) return path; try { return new URL(path, base).href; } catch (_) { return path; } } function cssUrl(value) { if (!value) return 'none'; return `url("${String(value).replace(/["\\\n\r\f]/g, '\\$&')}")`; } function createMediaConfig(options, capabilities) { const settings = { media: true, mediaType: 'video', videoSrc: '', webmSrc: '', imageSrc: '', assetBase: '', ...options }; const caps = capabilities || detectCapabilities(); const enabled = settings.media !== false && caps.dynamicRange === 'high'; const mediaType = normalizeMediaType(settings.mediaType) || 'video'; const src = resolveAssetUrl(settings.videoSrc || DEFAULT_HDR_VIDEO, settings.assetBase); const webm = resolveAssetUrl(settings.webmSrc || DEFAULT_HDR_WEBM, settings.assetBase); const image = resolveAssetUrl(settings.imageSrc || DEFAULT_HDR_IMAGE, settings.assetBase); if (mediaType === 'image') { return { enabled, type: 'image', src: image, sources: [ { src: image, type: 'image/png' } ], reason: enabled ? 'hdr-capable' : settings.media === false ? 'disabled' : 'standard-dynamic-range' }; } return { enabled, type: 'video', src, sources: [ { src, type: 'video/mp4; codecs="hvc1"' }, { src: webm, type: 'video/webm; codecs="vp09.02.10.10.01.09.16.09.01"' } ], reason: enabled ? 'hdr-capable' : settings.media === false ? 'disabled' : 'standard-dynamic-range' }; } function createEffectConfig(options, capabilities) { const settings = { intensity: 1, animated: true, effects: true, effect: 'cosmic-fireworks', quality: 'low', motion: 'static', ...options }; const caps = capabilities || detectCapabilities(); const intensity = clamp(settings.intensity, 0.35, 1.85); const reduced = Boolean(caps.prefersReducedMotion) || settings.animated === false; const quality = normalizeQuality(settings.quality) || 'balanced'; const profiles = { low: { dprCap: 1, targetFps: 12, starBase: 30, galaxyBase: 36, burstBase: 14, maxSparks: 54, maxComets: 1, shadowBlur: 6 }, balanced: { dprCap: 1.15, targetFps: 24, starBase: 54, galaxyBase: 68, burstBase: 20, maxSparks: 90, maxComets: 2, shadowBlur: 10 }, high: { dprCap: 1.35, targetFps: 30, starBase: 82, galaxyBase: 98, burstBase: 30, maxSparks: 150, maxComets: 3, shadowBlur: 14 } }; const profile = profiles[quality]; const motion = normalizeMotion(settings.motion) || 'static'; const shouldAnimate = motion === 'ambient' && !reduced; const targetFps = Math.round(clamp(settings.fps || profile.targetFps, 12, 60)); const dprCap = clamp(settings.maxDpr || profile.dprCap, 1, 1.75); const motionScale = reduced ? 0.18 : 1; const burstParticleCount = motion === 'static' ? 0 : Math.round(profile.burstBase + intensity * 10); const maxSparks = motion === 'static' ? 0 : profile.maxSparks; return { enabled: settings.effects !== false, effect: settings.effect || 'cosmic-fireworks', quality, motion, shouldAnimate, intensity, galaxy: settings.effect !== 'fireworks-only', fireworkBursts: settings.effect !== 'galaxy-only', stars: true, motionScale, targetFps: shouldAnimate ? targetFps : 1, dprCap: reduced ? 1 : dprCap, starCount: Math.round(profile.starBase + intensity * 10), galaxyCount: Math.round(profile.galaxyBase + intensity * 14), burstParticleCount, maxSparks, maxComets: motion === 'static' ? 0 : profile.maxComets, shadowBlur: profile.shadowBlur, colorSpace: caps.colorGamut === 'p3' || caps.colorGamut === 'rec2020' ? 'p3' : 'srgb' }; } function createStyleText(options, capabilities) { const settings = { intensity: 1, animated: true, text: true, ...options }; const caps = capabilities || detectCapabilities(); const intensity = clamp(settings.intensity, 0.35, 1.65); const textBoost = clamp(settings.textBoost || 1.9, 1, 4); const bloom = (0.65 * intensity).toFixed(2); const glow = Math.round(34 * intensity); const longGlow = Math.round(96 * intensity); const saturation = (1.08 + intensity * 0.2).toFixed(2); const contrast = (1.02 + intensity * 0.12).toFixed(2); const motionState = settings.animated && !caps.prefersReducedMotion ? 'running' : 'paused'; return ` :root { --hdr-fallback-cyan: #38faff; --hdr-fallback-magenta: #ff3df2; --hdr-fallback-lime: #baff37; --hdr-fallback-amber: #ffd95a; --hdr-fallback-blue: #4d7cff; --hdr-black: #000000; --hdr-panel: rgba(0, 0, 0, 0.78); --hdr-line: rgba(96, 255, 255, 0.26); --hdr-cyan: var(--hdr-fallback-cyan); --hdr-magenta: var(--hdr-fallback-magenta); --hdr-lime: var(--hdr-fallback-lime); --hdr-amber: var(--hdr-fallback-amber); --hdr-blue: var(--hdr-fallback-blue); --hdr-bloom: ${bloom}; --hdr-glow: ${glow}px; --hdr-long-glow: ${longGlow}px; --hdr-text-boost: ${textBoost.toFixed(2)}; --hdr-texture-url: none; } @media (color-gamut: p3) { :root { --hdr-cyan: color(display-p3 0.08 0.98 1); --hdr-magenta: color(display-p3 1 0.12 0.92); --hdr-lime: color(display-p3 0.72 1 0.1); --hdr-amber: color(display-p3 1 0.82 0.22); --hdr-blue: color(display-p3 0.22 0.42 1); --hdr-line: color(display-p3 0.3 1 1 / 0.32); } } @media (color-gamut: rec2020) { :root { --hdr-cyan: color(rec2020 0.12 0.95 1); --hdr-magenta: color(rec2020 0.95 0.1 0.9); --hdr-lime: color(rec2020 0.7 1 0.08); --hdr-amber: color(rec2020 1 0.78 0.1); --hdr-blue: color(rec2020 0.18 0.34 1); } } @supports (dynamic-range-limit: no-limit) { html[data-hdr-boosted="true"], body[data-hdr-enhanced="true"], #${VIDEO_ID}, #${IMAGE_ID}, body[data-hdr-enhanced="true"] :is(.hdr-title, [data-hdr-title], [data-hdr-text="true"]) { dynamic-range-limit: no-limit; } } html[data-hdr-boosted="true"] { background: #000000 !important; color-scheme: dark; } body[data-hdr-enhanced="true"] { background: #000000 !important; color: #eefcff !important; color-scheme: dark; } body[data-hdr-enhanced="true"]::before, body[data-hdr-enhanced="true"]::after { content: none !important; display: none !important; background: none !important; box-shadow: none !important; filter: none !important; } body[data-hdr-enhanced="true"] :is(h1, h2, h3, .hdr-title, [data-hdr-title]) { color: #f8feff !important; text-shadow: 0 0 0.5px #fff, 0 0 calc(var(--hdr-glow) * 0.55) var(--hdr-cyan), 0 0 var(--hdr-long-glow) color-mix(in srgb, var(--hdr-magenta), transparent 30%); } body[data-hdr-enhanced="true"][data-hdr-text="true"] :is(h1, h2, h3, .hdr-title, [data-hdr-title], [data-hdr-text="true"]) { filter: brightness(var(--hdr-text-boost)) saturate(1.24) contrast(1.08); transform: translateZ(0); } @supports (color: hdr-color(color(display-p3 1 1 1) 0, color(rec2100-pq 1 1 1) 2)) { body[data-hdr-enhanced="true"][data-hdr-text="true"] :is(h1, h2, h3, .hdr-title, [data-hdr-title], [data-hdr-text="true"]) { color: hdr-color(color(display-p3 1 1 1) 0, color(rec2100-pq 1 1 1) 2) !important; } } @supports ((-webkit-background-clip: text) or (background-clip: text)) { body[data-hdr-enhanced="true"][data-hdr-text="true"] :is(.hdr-title, [data-hdr-text="true"]) { background-image: var(--hdr-texture-url); background-position: center; background-repeat: no-repeat; background-size: cover; -webkit-background-clip: text; background-clip: text; color: transparent !important; -webkit-text-fill-color: transparent; text-shadow: 0 0 1px rgba(255, 255, 255, 0.9), 0 0 calc(var(--hdr-glow) * 0.72) color-mix(in srgb, var(--hdr-cyan), transparent 8%), 0 0 calc(var(--hdr-long-glow) * 1.12) color-mix(in srgb, var(--hdr-magenta), transparent 12%); } } body[data-hdr-enhanced="true"] :is(a, button, .button, [role="button"], input[type="submit"]) { border-color: color-mix(in srgb, var(--hdr-cyan), transparent 30%) !important; box-shadow: 0 0 calc(var(--hdr-glow) * 0.35) color-mix(in srgb, var(--hdr-cyan), transparent 28%), inset 0 0 18px rgba(255, 255, 255, 0.08); } body[data-hdr-enhanced="true"] :is(a, button, .button, [role="button"], input[type="submit"]):hover { color: #ffffff !important; text-shadow: 0 0 18px var(--hdr-cyan); box-shadow: 0 0 var(--hdr-glow) var(--hdr-cyan), 0 0 var(--hdr-long-glow) color-mix(in srgb, var(--hdr-magenta), transparent 36%); } body[data-hdr-enhanced="true"] :is(article, section, main, aside, header, footer, nav, .card, [class*="card"], [class*="panel"]) { border-color: color-mix(in srgb, var(--hdr-cyan), transparent 76%) !important; } body[data-hdr-enhanced="true"] ::selection { background: var(--hdr-cyan); color: #00050a; } #${OVERLAY_ID}, #${SCAN_ID}, #${CANVAS_ID}, #${VIDEO_ID}, #${IMAGE_ID} { pointer-events: none; position: fixed; inset: 0; z-index: 2147483000; } #${VIDEO_ID}, #${IMAGE_ID} { width: 100vw; height: 100vh; z-index: 0; object-fit: cover; background: #000000; opacity: 1; mix-blend-mode: normal; } :where(body[data-hdr-enhanced="true"] > :not(#${OVERLAY_ID}):not(#${SCAN_ID}):not(#${CANVAS_ID}):not(#${VIDEO_ID}):not(#${IMAGE_ID}):not(#${BADGE_ID})) { position: relative; z-index: 1; } #${CANVAS_ID} { width: 100vw; height: 100vh; z-index: 2147482999; opacity: ${Math.min(0.84, 0.48 + intensity * 0.16).toFixed(2)}; mix-blend-mode: screen; } #${OVERLAY_ID} { mix-blend-mode: screen; opacity: ${Math.min(0.72, 0.34 + intensity * 0.17).toFixed(2)}; background: radial-gradient(circle at var(--hdr-orb-x, 20%) var(--hdr-orb-y, 20%), var(--hdr-cyan), transparent 18rem), radial-gradient(circle at calc(100% - var(--hdr-orb-x, 20%)) 30%, var(--hdr-magenta), transparent 20rem), linear-gradient(115deg, transparent 0%, rgba(255,255,255,0.08) 48%, transparent 58%); filter: blur(18px) saturate(1.25); animation: hdrAurora 8s ease-in-out infinite alternate; animation-play-state: ${motionState}; } #${SCAN_ID} { opacity: ${Math.min(0.38, 0.1 + intensity * 0.12).toFixed(2)}; background: repeating-linear-gradient( 180deg, rgba(255,255,255,0.06) 0, rgba(255,255,255,0.06) 1px, transparent 1px, transparent 7px ); mix-blend-mode: soft-light; animation: hdrScan 5s linear infinite; animation-play-state: ${motionState}; } #${BADGE_ID} { position: fixed; right: 18px; bottom: 18px; z-index: 2147483001; border: 1px solid color-mix(in srgb, var(--hdr-cyan), transparent 30%); border-radius: 999px; padding: 8px 12px; background: rgba(1, 2, 10, 0.72); box-shadow: 0 0 22px color-mix(in srgb, var(--hdr-cyan), transparent 30%), inset 0 0 18px rgba(255, 255, 255, 0.08); color: #f8feff; font: 700 11px/1.2 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; letter-spacing: 0.08em; text-transform: uppercase; backdrop-filter: blur(14px); } #${BADGE_ID} strong { color: var(--hdr-cyan); text-shadow: 0 0 16px var(--hdr-cyan); } @media (dynamic-range: high) { #${OVERLAY_ID} { opacity: ${Math.min(0.86, 0.44 + intensity * 0.2).toFixed(2)}; filter: blur(15px) saturate(1.55) brightness(1.18); } } @media (prefers-reduced-motion: reduce) { #${OVERLAY_ID}, #${SCAN_ID}, body[data-hdr-enhanced="true"] * { animation-duration: 1ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; } } @keyframes hdrAurora { 0% { --hdr-orb-x: 12%; --hdr-orb-y: 22%; transform: translate3d(-1.5%, -1%, 0) scale(1); } 45% { --hdr-orb-x: 62%; --hdr-orb-y: 14%; transform: translate3d(1%, 0.5%, 0) scale(1.04); } 100% { --hdr-orb-x: 38%; --hdr-orb-y: 72%; transform: translate3d(0.8%, 1.4%, 0) scale(1.08); } } @keyframes hdrScan { from { transform: translateY(-7px); } to { transform: translateY(7px); } } `.trim(); } function ready(callback) { const win = getWindow(); if (!win || !win.document) return; if (win.document.readyState === 'loading') { win.document.addEventListener('DOMContentLoaded', callback, { once: true }); return; } callback(); } function appendNode(document, id, className, tagName) { const existing = document.getElementById(id); if (existing) return existing; const node = document.createElement(tagName || 'div'); node.id = id; if (className) node.className = className; node.setAttribute('aria-hidden', 'true'); document.body.appendChild(node); return node; } function appendHdrVideo(document, config) { const video = appendNode(document, VIDEO_ID, 'hdr-enhancer-video', 'video'); video.dataset.hdrMedia = 'true'; video.dataset.hdrType = config.type; video.dataset.hdrSource = config.src; video.muted = true; video.loop = true; video.autoplay = true; video.playsInline = true; video.preload = 'auto'; video.innerHTML = ''; if (typeof video.removeAttribute === 'function') video.removeAttribute('src'); for (const sourceConfig of config.sources) { const source = document.createElement('source'); source.src = sourceConfig.src; source.type = sourceConfig.type; source.setAttribute('src', sourceConfig.src); source.setAttribute('type', sourceConfig.type); video.appendChild(source); } video.setAttribute('muted', ''); video.setAttribute('loop', ''); video.setAttribute('autoplay', ''); video.setAttribute('playsinline', ''); video.setAttribute('preload', 'auto'); video.setAttribute('poster', ''); if (typeof video.load === 'function') { try { video.load(); } catch (_) { // Older embedded browsers may defer loading until playback starts. } } if (typeof video.play === 'function') { try { const playback = video.play(); if (playback && typeof playback.catch === 'function') playback.catch(() => {}); } catch (_) { // Autoplay can still be denied in unusual browser policies; muted video is retried by the browser. } } return video; } function appendHdrImage(document, config) { const image = appendNode(document, IMAGE_ID, 'hdr-enhancer-image', 'img'); image.dataset.hdrMedia = 'true'; image.dataset.hdrType = config.type; image.dataset.hdrSource = config.src; image.src = config.src; image.alt = ''; image.decoding = 'async'; image.loading = 'eager'; image.setAttribute('src', config.src); image.setAttribute('alt', ''); image.setAttribute('decoding', 'async'); image.setAttribute('loading', 'eager'); return image; } function stopVisualEffects(win) { const state = win && win.__hdrEnhancerFx; if (!state) return; if (state.frame && win.cancelAnimationFrame) { win.cancelAnimationFrame(state.frame); } if (state.timer && win.clearTimeout) { win.clearTimeout(state.timer); } if (state.resize && win.removeEventListener) { win.removeEventListener('resize', state.resize); } if (state.visibility && win.document && win.document.removeEventListener) { win.document.removeEventListener('visibilitychange', state.visibility); } delete win.__hdrEnhancerFx; } function startVisualEffects(win, canvas, config) { if (!win || !canvas || !config.enabled || typeof canvas.getContext !== 'function') return; stopVisualEffects(win); let context; try { context = canvas.getContext('2d', { alpha: true, colorSpace: config.colorSpace === 'p3' ? 'display-p3' : 'srgb' }); } catch (_) { context = null; } const ctx = context || canvas.getContext('2d', { alpha: true }); if (!ctx) return; const random = () => Math.random(); const palette = [ [56, 250, 255], [255, 61, 242], [186, 255, 55], [255, 217, 90], [77, 124, 255], [255, 255, 255] ]; const color = (rgb, alpha) => `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${alpha})`; const state = { frame: 0, timer: 0, width: 0, height: 0, ratio: 1, ticks: 0, stars: [], galaxy: [], sparks: [], comets: [] }; state.frameInterval = 1000 / config.targetFps; state.lastRender = 0; state.animate = config.shouldAnimate; state.paused = false; function resize() { const width = Math.max(1, win.innerWidth || canvas.clientWidth || 1280); const height = Math.max(1, win.innerHeight || canvas.clientHeight || 720); const ratio = Math.min(config.dprCap, Math.max(1, win.devicePixelRatio || 1)); state.width = width; state.height = height; state.ratio = ratio; canvas.width = Math.round(width * ratio); canvas.height = Math.round(height * ratio); canvas.style.width = `${width}px`; canvas.style.height = `${height}px`; canvas.dataset.hdrQuality = config.quality; canvas.dataset.hdrMotion = config.motion; canvas.dataset.hdrFps = String(config.targetFps); canvas.dataset.hdrDpr = String(ratio); canvas.dataset.hdrStars = String(config.starCount); canvas.dataset.hdrGalaxy = String(config.galaxyCount); ctx.setTransform(ratio, 0, 0, ratio, 0, 0); state.stars = Array.from({ length: config.starCount }, () => ({ x: random() * width, y: random() * height, r: random() * 1.7 + 0.35, twinkle: random() * Math.PI * 2, rgb: palette[Math.floor(random() * palette.length)] })); state.galaxy = Array.from({ length: config.galaxyCount }, () => ({ radius: random() ** 0.55 * Math.min(width, height) * 0.48, angle: random() * Math.PI * 2, lane: random() > 0.5 ? 1 : -1, size: random() * 2.2 + 0.4, speed: (random() * 0.0007 + 0.00025) * (random() > 0.5 ? 1 : -1), rgb: palette[Math.floor(random() * (palette.length - 1))] })); } function spawnBurst(forceX, forceY) { if (!config.fireworkBursts) return; if (state.sparks.length > config.maxSparks * 0.72) return; const width = state.width; const height = state.height; const x = forceX || width * (0.16 + random() * 0.68); const y = forceY || height * (0.12 + random() * 0.45); const base = palette[Math.floor(random() * palette.length)]; const count = Math.min( config.maxSparks - state.sparks.length, Math.round(config.burstParticleCount * (0.82 + random() * 0.38)) ); for (let i = 0; i < count; i += 1) { const angle = (Math.PI * 2 * i) / count + random() * 0.22; const speed = (1.4 + random() * 5.4) * config.motionScale * config.intensity; state.sparks.push({ x, y, lastX: x, lastY: y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, life: 64 + random() * 54, age: 0, size: 1.1 + random() * 2.4, rgb: random() > 0.28 ? base : palette[Math.floor(random() * palette.length)] }); } } function spawnComet() { if (state.comets.length >= config.maxComets) return; const height = state.height; state.comets.push({ x: -80, y: height * (0.08 + random() * 0.42), vx: (10 + random() * 8) * config.motionScale, vy: (2.4 + random() * 2.8) * config.motionScale, life: 90, age: 0, rgb: palette[Math.floor(random() * palette.length)] }); } function drawGlowCircle(x, y, radius, rgb, alpha) { const gradient = ctx.createRadialGradient(x, y, 0, x, y, radius); gradient.addColorStop(0, color(rgb, alpha)); gradient.addColorStop(0.35, color(rgb, alpha * 0.32)); gradient.addColorStop(1, color(rgb, 0)); ctx.fillStyle = gradient; ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI * 2); ctx.fill(); } function drawBloomPoint(x, y, radius, rgb, alpha, blur) { ctx.shadowColor = color(rgb, Math.min(0.9, alpha + 0.22)); ctx.shadowBlur = blur; ctx.fillStyle = color(rgb, alpha); ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI * 2); ctx.fill(); } function drawFrame(now) { const timestamp = typeof now === 'number' ? now : Date.now(); const raf = win.requestAnimationFrame || ((callback) => win.setTimeout(callback, 16)); if (state.lastRender && timestamp - state.lastRender < state.frameInterval) { state.frame = raf(drawFrame); return; } state.lastRender = timestamp; if (state.paused) { state.frame = raf(drawFrame); return; } const width = state.width; const height = state.height; state.ticks += config.motionScale; ctx.globalCompositeOperation = 'source-over'; ctx.clearRect(0, 0, width, height); ctx.fillStyle = 'rgba(0, 0, 12, 0.12)'; ctx.fillRect(0, 0, width, height); ctx.globalCompositeOperation = 'lighter'; if (config.stars) { for (const star of state.stars) { const pulse = 0.42 + Math.sin(state.ticks * 0.045 + star.twinkle) * 0.36; drawBloomPoint( star.x, star.y, star.r, star.rgb, Math.max(0.26, pulse * 0.7), config.shadowBlur * 0.45 ); } } if (config.galaxy) { const cx = width * 0.58; const cy = height * 0.5; for (const dust of state.galaxy) { dust.angle += dust.speed * state.ticks * config.motionScale; const arm = dust.angle + dust.lane * dust.radius * 0.011 + state.ticks * 0.0025; const x = cx + Math.cos(arm) * dust.radius * 1.34; const y = cy + Math.sin(arm) * dust.radius * 0.38; const alpha = 0.2 + (1 - dust.radius / (Math.min(width, height) * 0.5)) * 0.46; drawBloomPoint(x, y, dust.size, dust.rgb, alpha * 0.78, config.shadowBlur); } drawGlowCircle(cx, cy, Math.min(width, height) * 0.22, [255, 255, 255], 0.22 * config.intensity); drawGlowCircle(cx, cy, Math.min(width, height) * 0.36, [255, 61, 242], 0.11 * config.intensity); } if (config.fireworkBursts && state.ticks % Math.max(22, 54 - config.intensity * 15) < 1) { spawnBurst(); } if (state.ticks % 130 < 1) { spawnComet(); } ctx.lineCap = 'round'; for (let i = state.sparks.length - 1; i >= 0; i -= 1) { const spark = state.sparks[i]; spark.age += 1; spark.lastX = spark.x; spark.lastY = spark.y; spark.x += spark.vx; spark.y += spark.vy; spark.vx *= 0.986; spark.vy = spark.vy * 0.986 + 0.038 * config.motionScale; const left = 1 - spark.age / spark.life; if (left <= 0) { state.sparks.splice(i, 1); continue; } ctx.strokeStyle = color(spark.rgb, left * 0.72); ctx.lineWidth = spark.size * (0.8 + left); ctx.shadowColor = color(spark.rgb, 0.85); ctx.shadowBlur = config.shadowBlur * 0.95; ctx.beginPath(); ctx.moveTo(spark.lastX, spark.lastY); ctx.lineTo(spark.x, spark.y); ctx.stroke(); drawBloomPoint(spark.x, spark.y, spark.size * 1.2, spark.rgb, left * 0.66, config.shadowBlur); } for (let i = state.comets.length - 1; i >= 0; i -= 1) { const comet = state.comets[i]; comet.age += 1; comet.x += comet.vx; comet.y += comet.vy; const left = 1 - comet.age / comet.life; if (left <= 0 || comet.x > width + 220) { state.comets.splice(i, 1); continue; } ctx.strokeStyle = color(comet.rgb, 0.64 * left); ctx.lineWidth = 3.8; ctx.shadowColor = color(comet.rgb, 0.95); ctx.shadowBlur = config.shadowBlur * 1.35; ctx.beginPath(); ctx.moveTo(comet.x - 150, comet.y - 36); ctx.lineTo(comet.x, comet.y); ctx.stroke(); drawGlowCircle(comet.x, comet.y, 18 + 18 * config.intensity, comet.rgb, 0.52 * left); } ctx.shadowBlur = 0; if (state.animate) { state.frame = raf(drawFrame); } else { state.frame = 0; } } resize(); if (config.motion !== 'static') { spawnBurst(state.width * 0.32, state.height * 0.34); } state.resize = resize; if (win.addEventListener) win.addEventListener('resize', resize); state.visibility = () => { state.paused = Boolean(win.document && win.document.hidden); if (!state.paused && state.animate && !state.frame) { state.frame = (win.requestAnimationFrame || ((callback) => win.setTimeout(callback, 16)))(drawFrame); } }; if (win.document && win.document.addEventListener) { win.document.addEventListener('visibilitychange', state.visibility); } win.__hdrEnhancerFx = state; drawFrame(); } function enhance(options) { const settings = { intensity: 1, animated: true, overlay: false, scanlines: false, badge: true, effects: false, effect: 'cosmic-fireworks', media: true, blackout: true, text: true, ...options }; const win = getWindow(settings); const document = win && win.document; if (!document || !document.head || !document.body) { return { active: false, reason: 'missing-document' }; } const capabilities = detectCapabilities(win); const mediaConfig = createMediaConfig(settings, capabilities); let reused = true; let style = document.getElementById(STYLE_ID); if (!style) { reused = false; style = document.createElement('style'); style.id = STYLE_ID; style.textContent = createStyleText(settings, capabilities); document.head.appendChild(style); } document.documentElement.dataset.hdrBoosted = 'true'; document.documentElement.style.setProperty('--hdr-runtime-gamut', capabilities.colorGamut); document.documentElement.style.setProperty( '--hdr-texture-url', cssUrl(resolveAssetUrl(settings.textSrc, settings.assetBase) || mediaConfig.src) ); document.body.dataset.hdrEnhanced = 'true'; document.body.dataset.hdrMode = capabilities.colorGamut; document.body.dataset.hdrEffect = settings.effect; document.body.dataset.hdrMedia = mediaConfig.enabled ? 'true' : 'false'; document.body.dataset.hdrText = settings.text === false ? 'false' : 'true'; if (mediaConfig.enabled && mediaConfig.type === 'image') { appendHdrImage(document, mediaConfig); } if (mediaConfig.enabled && mediaConfig.type === 'video') { appendHdrVideo(document, mediaConfig); } if (settings.effects !== false && !mediaConfig.enabled) { const canvas = appendNode(document, CANVAS_ID, 'hdr-enhancer-canvas', 'canvas'); startVisualEffects(win, canvas, createEffectConfig(settings, capabilities)); } if (settings.overlay) { appendNode(document, OVERLAY_ID, 'hdr-enhancer-overlay'); } if (settings.scanlines) { appendNode(document, SCAN_ID, 'hdr-enhancer-scan'); } if (settings.badge) { const badge = appendNode(document, BADGE_ID, 'hdr-enhancer-badge'); const mediaLabel = mediaConfig.enabled ? 'MEDIA' : 'CAPABLE'; badge.innerHTML = `HDR ${mediaLabel} ${capabilities.dynamicRange.toUpperCase()} / ${capabilities.colorGamut.toUpperCase()}`; } return { active: true, reused, capabilities, media: mediaConfig }; } function install(options) { return enhance(options); } function disable(options) { const win = getWindow(options || {}); const document = win && win.document; if (!document) return { active: false, reason: 'missing-document' }; stopVisualEffects(win); [STYLE_ID, OVERLAY_ID, SCAN_ID, BADGE_ID, CANVAS_ID, VIDEO_ID, IMAGE_ID].forEach((id) => { const node = document.getElementById(id); if (node && node.remove) node.remove(); }); if (document.body) { document.body.removeAttribute('data-hdr-enhanced'); document.body.removeAttribute('data-hdr-mode'); document.body.removeAttribute('data-hdr-effect'); document.body.removeAttribute('data-hdr-media'); document.body.removeAttribute('data-hdr-text'); } if (document.documentElement) { delete document.documentElement.dataset.hdrBoosted; if (document.documentElement.style) { document.documentElement.style.removeProperty('--hdr-runtime-gamut'); document.documentElement.style.removeProperty('--hdr-texture-url'); } } return { active: false }; } function toggle(options) { const win = getWindow(options || {}); const document = win && win.document; if (!document || !document.body) return enhance(options); return document.body.dataset.hdrEnhanced === 'true' ? disable({ window: win }) : enhance(options); } return { pluginName: PLUGIN_NAME, version: VERSION, detectCapabilities, readScriptOptions, shouldAutoRun, createMediaConfig, createEffectConfig, createStyleText, install, enhance, disable, toggle, ready }; });