| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
function hexToRgbStr(hex) { |
| 5 |
hex = (hex || '#000').replace(/^#/, ''); |
| 6 |
if (hex.length === 3) hex = hex.split('').map(function (c) { return c + c; }).join(''); |
| 7 |
var n = parseInt(hex, 16) || 0; |
| 8 |
return [(n >> 16) & 255, (n >> 8) & 255, n & 255].join(','); |
| 9 |
} |
| 10 |
|
| 11 |
/* ── Typography CSS-var helper ─────────────────────────────────────── */ |
| 12 |
function typoCssVarsForEl(typo, prefix, el) { |
| 13 |
if (!typo || typeof typo !== 'object') return; |
| 14 |
var map = { |
| 15 |
family:'font-family', weight:'font-weight', style:'font-style', |
| 16 |
transform:'text-transform', decoration:'text-decoration', |
| 17 |
sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m', sizeUnit:'font-size-unit', |
| 18 |
lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m', lineHeightUnit:'line-height-unit', |
| 19 |
letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m', letterSpacingUnit:'letter-spacing-unit', |
| 20 |
wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m', wordSpacingUnit:'word-spacing-unit' |
| 21 |
}; |
| 22 |
Object.keys(map).forEach(function(k) { |
| 23 |
if (typo[k] !== undefined && typo[k] !== '') { |
| 24 |
var v = typo[k]; |
| 25 |
var css = map[k]; |
| 26 |
if (css.indexOf('size-d') !== -1 || css.indexOf('size-t') !== -1 || css.indexOf('size-m') !== -1 || |
| 27 |
css.indexOf('height-d') !== -1 || css.indexOf('height-t') !== -1 || css.indexOf('height-m') !== -1 || |
| 28 |
css.indexOf('spacing-d') !== -1 || css.indexOf('spacing-t') !== -1 || css.indexOf('spacing-m') !== -1) { |
| 29 |
var unitKey = css.replace(/-[dtm]$/, '-unit'); |
| 30 |
var unit = ''; |
| 31 |
Object.keys(map).forEach(function(k2) { if (map[k2] === unitKey && typo[k2]) unit = typo[k2]; }); |
| 32 |
if (!unit) unit = css.indexOf('size') !== -1 ? 'px' : ''; |
| 33 |
v = v + unit; |
| 34 |
} |
| 35 |
el.style.setProperty(prefix + css, v); |
| 36 |
} |
| 37 |
}); |
| 38 |
} |
| 39 |
|
| 40 |
function initCursorSpotlight(appEl) { |
| 41 |
var raw = appEl.dataset.opts; |
| 42 |
if (!raw) return; |
| 43 |
var opts; |
| 44 |
try { opts = JSON.parse(raw); } catch (e) { return; } |
| 45 |
|
| 46 |
var spotR = opts.spotlightSize || 280; |
| 47 |
var blur = opts.spotlightBlur || 40; |
| 48 |
var dimRgb = hexToRgbStr(opts.dimColor || '#000000'); |
| 49 |
var dimA = opts.dimOpacity || 0.92; |
| 50 |
var bgColor = opts.bgColor || '#0c0a1e'; |
| 51 |
var smooth = opts.followSmooth !== false; |
| 52 |
var idleMode = opts.idleMode || 'center'; |
| 53 |
|
| 54 |
// ── Build section ───────────────────────────────────────────── |
| 55 |
var section = document.createElement('div'); |
| 56 |
section.className = 'bkbg-cs-section'; |
| 57 |
section.style.height = (opts.sectionHeight || 500) + 'px'; |
| 58 |
section.style.borderRadius = (opts.borderRadius || 16) + 'px'; |
| 59 |
section.style.backgroundColor = bgColor; |
| 60 |
section.style.overflow = 'hidden'; |
| 61 |
section.style.position = 'relative'; |
| 62 |
|
| 63 |
// BG image |
| 64 |
if (opts.bgImage) { |
| 65 |
var bgImg = document.createElement('img'); |
| 66 |
bgImg.src = opts.bgImage; |
| 67 |
bgImg.className = 'bkbg-cs-bg'; |
| 68 |
bgImg.alt = ''; |
| 69 |
bgImg.style.opacity = opts.bgImageOpacity || 0.5; |
| 70 |
section.appendChild(bgImg); |
| 71 |
} |
| 72 |
|
| 73 |
// Dim overlay |
| 74 |
var dim = document.createElement('div'); |
| 75 |
dim.className = 'bkbg-cs-dim'; |
| 76 |
section.appendChild(dim); |
| 77 |
|
| 78 |
// Content |
| 79 |
var content = document.createElement('div'); |
| 80 |
content.className = 'bkbg-cs-content'; |
| 81 |
content.style.textAlign = opts.textAlign || 'center'; |
| 82 |
content.style.color = opts.textColor || '#ffffff'; |
| 83 |
|
| 84 |
var heading = document.createElement(opts.tag || 'h2'); |
| 85 |
heading.className = 'bkbg-cs-heading'; |
| 86 |
heading.style.color = opts.textColor || '#ffffff'; |
| 87 |
heading.textContent = opts.heading || 'Move your cursor to reveal'; |
| 88 |
content.appendChild(heading); |
| 89 |
|
| 90 |
if (opts.subtext) { |
| 91 |
var sub = document.createElement('p'); |
| 92 |
sub.className = 'bkbg-cs-subtext'; |
| 93 |
sub.textContent = opts.subtext; |
| 94 |
sub.style.color = opts.textColor || '#ffffff'; |
| 95 |
content.appendChild(sub); |
| 96 |
} |
| 97 |
|
| 98 |
section.appendChild(content); |
| 99 |
|
| 100 |
if (opts.showHint) { |
| 101 |
var hint = document.createElement('div'); |
| 102 |
hint.className = 'bkbg-cs-hint'; |
| 103 |
hint.style.color = opts.accentColor || '#a78bfa'; |
| 104 |
hint.textContent = opts.hintText || 'Move cursor to reveal'; |
| 105 |
section.appendChild(hint); |
| 106 |
} |
| 107 |
|
| 108 |
// Custom cursor dot |
| 109 |
var dot = document.createElement('div'); |
| 110 |
dot.className = 'bkbg-cs-cursor-dot'; |
| 111 |
dot.style.display = 'none'; |
| 112 |
section.appendChild(dot); |
| 113 |
|
| 114 |
appEl.parentNode.replaceChild(section, appEl); |
| 115 |
|
| 116 |
/* Apply typography CSS vars on section */ |
| 117 |
section.style.setProperty('--bkbg-cs-hdg-fs', (opts.fontSize || 48) + 'px'); |
| 118 |
section.style.setProperty('--bkbg-cs-hdg-fw', opts.fontWeight || '800'); |
| 119 |
section.style.setProperty('--bkbg-cs-sub-fs', (opts.subtextSize || 18) + 'px'); |
| 120 |
typoCssVarsForEl(opts.typoHeading, '--bkbg-cs-hdg-', section); |
| 121 |
typoCssVarsForEl(opts.typoSubtext, '--bkbg-cs-sub-', section); |
| 122 |
|
| 123 |
// ── Spotlight position ──────────────────────────────────────── |
| 124 |
var mx = -9999, my = -9999; |
| 125 |
var cx = -9999, cy = -9999; |
| 126 |
|
| 127 |
function buildMask(x, y) { |
| 128 |
if (idleMode === 'full' && x < 0) { |
| 129 |
return 'none'; |
| 130 |
} |
| 131 |
if (idleMode === 'dark' && x < 0) { |
| 132 |
return 'radial-gradient(circle 0px at 50% 50%, transparent 0%, rgba(' + dimRgb + ',' + dimA + ') 1px)'; |
| 133 |
} |
| 134 |
var px = x < 0 ? '50%' : x + 'px'; |
| 135 |
var py = y < 0 ? '50%' : y + 'px'; |
| 136 |
return 'radial-gradient(circle ' + spotR + 'px at ' + px + ' ' + py + ', transparent 0%, rgba(' + dimRgb + ',' + dimA + ') ' + (spotR + blur) + 'px)'; |
| 137 |
} |
| 138 |
|
| 139 |
function applyDim(x, y) { |
| 140 |
dim.style.background = buildMask(x, y); |
| 141 |
dot.style.left = x + 'px'; |
| 142 |
dot.style.top = y + 'px'; |
| 143 |
} |
| 144 |
|
| 145 |
// Initial idle state |
| 146 |
applyDim(-1, -1); |
| 147 |
|
| 148 |
var raf; |
| 149 |
if (smooth) { |
| 150 |
function lerp(a, b, t) { return a + (b - a) * t; } |
| 151 |
function loop() { |
| 152 |
cx = lerp(cx < -1000 ? mx : cx, mx, 0.15); |
| 153 |
cy = lerp(cy < -1000 ? my : cy, my, 0.15); |
| 154 |
applyDim(cx, cy); |
| 155 |
raf = requestAnimationFrame(loop); |
| 156 |
} |
| 157 |
raf = requestAnimationFrame(loop); |
| 158 |
} |
| 159 |
|
| 160 |
section.addEventListener('mouseenter', function () { |
| 161 |
section.classList.add('bkbg-cs-active'); |
| 162 |
dot.style.display = 'block'; |
| 163 |
}); |
| 164 |
|
| 165 |
section.addEventListener('mouseleave', function () { |
| 166 |
section.classList.remove('bkbg-cs-active'); |
| 167 |
dot.style.display = 'none'; |
| 168 |
mx = -1; my = -1; |
| 169 |
if (!smooth) applyDim(-1, -1); |
| 170 |
}); |
| 171 |
|
| 172 |
section.addEventListener('mousemove', function (e) { |
| 173 |
var rect = section.getBoundingClientRect(); |
| 174 |
mx = e.clientX - rect.left; |
| 175 |
my = e.clientY - rect.top; |
| 176 |
if (!smooth) applyDim(mx, my); |
| 177 |
}); |
| 178 |
} |
| 179 |
|
| 180 |
document.addEventListener('DOMContentLoaded', function () { |
| 181 |
document.querySelectorAll('.bkbg-cs-app').forEach(initCursorSpotlight); |
| 182 |
}); |
| 183 |
if (document.readyState === 'complete' || document.readyState === 'interactive') { |
| 184 |
document.querySelectorAll('.bkbg-cs-app').forEach(initCursorSpotlight); |
| 185 |
} |
| 186 |
})(); |
| 187 |
|