| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
var typoMap = [ |
| 5 |
['family','font-family'],['weight','font-weight'],['style','font-style'], |
| 6 |
['decoration','text-decoration'],['transform','text-transform'], |
| 7 |
['sizeDesktop','font-size-d'],['sizeTablet','font-size-t'],['sizeMobile','font-size-m'], |
| 8 |
['lineHeightDesktop','line-height-d'],['lineHeightTablet','line-height-t'],['lineHeightMobile','line-height-m'], |
| 9 |
['letterSpacingDesktop','letter-spacing-d'],['letterSpacingTablet','letter-spacing-t'],['letterSpacingMobile','letter-spacing-m'], |
| 10 |
['wordSpacingDesktop','word-spacing-d'],['wordSpacingTablet','word-spacing-t'],['wordSpacingMobile','word-spacing-m'] |
| 11 |
]; |
| 12 |
function typoCssVarsForEl(typo, prefix, el) { |
| 13 |
if (!typo || typeof typo !== 'object') return; |
| 14 |
for (var i = 0; i < typoMap.length; i++) { |
| 15 |
var v = typo[typoMap[i][0]]; |
| 16 |
if (v !== undefined && v !== '' && v !== null) el.style.setProperty(prefix + typoMap[i][1], String(v)); |
| 17 |
} |
| 18 |
} |
| 19 |
|
| 20 |
function lerp(a, b, t) { return a + (b - a) * t; } |
| 21 |
|
| 22 |
document.querySelectorAll('.bkbg-iis-app').forEach(function (app) { |
| 23 |
var o; |
| 24 |
try { o = JSON.parse(app.dataset.opts || '{}'); } catch (e) { return; } |
| 25 |
|
| 26 |
var imageUrl = o.imageUrl || ''; |
| 27 |
var imageAlt = o.imageAlt || ''; |
| 28 |
var imageWidth = o.imageWidth || 480; |
| 29 |
var imageHeight = o.imageHeight || 320; |
| 30 |
var imageRadius = o.imageRadius || 20; |
| 31 |
var sheenColor = o.sheenColor || 'rgba(255,255,255,0.35)'; |
| 32 |
var sheenSize = o.sheenSize || 60; |
| 33 |
var sheenBlur = o.sheenBlur || 40; |
| 34 |
var tiltStrength = o.tiltStrength || 12; |
| 35 |
var tiltShadow = !!o.tiltShadow; |
| 36 |
var shadowColor = o.shadowColor || 'rgba(99,102,241,0.4)'; |
| 37 |
var showCaption = !!o.showCaption; |
| 38 |
var caption = o.caption || ''; |
| 39 |
var captionSize = o.captionSize || 14; |
| 40 |
var captionColor = o.captionColor || '#64748b'; |
| 41 |
var align2 = o.align2 || 'center'; |
| 42 |
var bgColor = o.bgColor || ''; |
| 43 |
|
| 44 |
var reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; |
| 45 |
|
| 46 |
var justifyMap = { left: 'flex-start', center: 'center', right: 'flex-end' }; |
| 47 |
|
| 48 |
if (bgColor) app.style.background = bgColor; |
| 49 |
typoCssVarsForEl(o.captionTypo, '--bkbg-iis-cp-', app); |
| 50 |
|
| 51 |
/* ── Scene wrapper ── */ |
| 52 |
var scene = document.createElement('div'); |
| 53 |
scene.className = 'bkbg-iis-scene'; |
| 54 |
scene.style.justifyContent = justifyMap[align2] || 'center'; |
| 55 |
if (bgColor) scene.style.padding = '32px'; |
| 56 |
|
| 57 |
var wrap = document.createElement('div'); |
| 58 |
wrap.style.maxWidth = imageWidth + 'px'; |
| 59 |
wrap.style.width = '100%'; |
| 60 |
|
| 61 |
/* ── Card ── */ |
| 62 |
var card = document.createElement('div'); |
| 63 |
card.className = 'bkbg-iis-card'; |
| 64 |
card.style.width = '100%'; |
| 65 |
card.style.height = imageHeight + 'px'; |
| 66 |
card.style.borderRadius = imageRadius + 'px'; |
| 67 |
card.style.perspective = '900px'; |
| 68 |
|
| 69 |
/* Image or placeholder */ |
| 70 |
if (imageUrl) { |
| 71 |
var img = document.createElement('img'); |
| 72 |
img.className = 'bkbg-iis-img'; |
| 73 |
img.src = imageUrl; |
| 74 |
img.alt = imageAlt; |
| 75 |
img.style.borderRadius = imageRadius + 'px'; |
| 76 |
card.appendChild(img); |
| 77 |
} else { |
| 78 |
var ph = document.createElement('div'); |
| 79 |
ph.className = 'bkbg-iis-placeholder'; |
| 80 |
ph.style.width = '100%'; |
| 81 |
ph.style.height = imageHeight + 'px'; |
| 82 |
ph.style.borderRadius = imageRadius + 'px'; |
| 83 |
ph.textContent = 'No image selected'; |
| 84 |
card.appendChild(ph); |
| 85 |
} |
| 86 |
|
| 87 |
/* Sheen overlay */ |
| 88 |
var sheen = document.createElement('div'); |
| 89 |
sheen.className = 'bkbg-iis-sheen'; |
| 90 |
var sheenPx = Math.round(Math.min(imageWidth, imageHeight) * sheenSize / 100); |
| 91 |
sheen.style.width = sheenPx + 'px'; |
| 92 |
sheen.style.height = sheenPx + 'px'; |
| 93 |
sheen.style.background = 'radial-gradient(circle, ' + sheenColor + ' 0%, transparent 70%)'; |
| 94 |
sheen.style.filter = 'blur(' + sheenBlur + 'px)'; |
| 95 |
card.appendChild(sheen); |
| 96 |
|
| 97 |
wrap.appendChild(card); |
| 98 |
|
| 99 |
/* Caption */ |
| 100 |
if (showCaption && caption) { |
| 101 |
var cap = document.createElement('p'); |
| 102 |
cap.className = 'bkbg-iis-caption'; |
| 103 |
cap.style.color = captionColor; |
| 104 |
cap.textContent = caption; |
| 105 |
wrap.appendChild(cap); |
| 106 |
} |
| 107 |
|
| 108 |
scene.appendChild(wrap); |
| 109 |
app.appendChild(scene); |
| 110 |
|
| 111 |
if (reducedMotion) return; |
| 112 |
|
| 113 |
/* ── Interaction ── */ |
| 114 |
var targetRX = 0, targetRY = 0; |
| 115 |
var currentRX = 0, currentRY = 0; |
| 116 |
var sheenX = 50, sheenY = 50; |
| 117 |
var rafId = null; |
| 118 |
|
| 119 |
function tick() { |
| 120 |
currentRX = lerp(currentRX, targetRX, 0.1); |
| 121 |
currentRY = lerp(currentRY, targetRY, 0.1); |
| 122 |
|
| 123 |
card.style.transform = 'perspective(900px) rotateX(' + currentRX + 'deg) rotateY(' + currentRY + 'deg)'; |
| 124 |
|
| 125 |
if (tiltShadow) { |
| 126 |
var sdx = -currentRY * 0.5; |
| 127 |
var sdy = currentRX * 0.5; |
| 128 |
card.style.boxShadow = sdx + 'px ' + (sdy + 16) + 'px 40px ' + shadowColor; |
| 129 |
} |
| 130 |
|
| 131 |
sheen.style.left = sheenX + '%'; |
| 132 |
sheen.style.top = sheenY + '%'; |
| 133 |
|
| 134 |
if (Math.abs(currentRX - targetRX) > 0.01 || Math.abs(currentRY - targetRY) > 0.01) { |
| 135 |
rafId = requestAnimationFrame(tick); |
| 136 |
} else { |
| 137 |
rafId = null; |
| 138 |
} |
| 139 |
} |
| 140 |
|
| 141 |
function startTick() { |
| 142 |
if (!rafId) rafId = requestAnimationFrame(tick); |
| 143 |
} |
| 144 |
|
| 145 |
card.addEventListener('mousemove', function (e) { |
| 146 |
var rect = card.getBoundingClientRect(); |
| 147 |
var nx = (e.clientX - rect.left) / rect.width - 0.5; /* -0.5 to 0.5 */ |
| 148 |
var ny = (e.clientY - rect.top) / rect.height - 0.5; |
| 149 |
|
| 150 |
targetRY = nx * tiltStrength; |
| 151 |
targetRX = -ny * tiltStrength; |
| 152 |
sheenX = (e.clientX - rect.left) / rect.width * 100; |
| 153 |
sheenY = (e.clientY - rect.top) / rect.height * 100; |
| 154 |
|
| 155 |
card.classList.add('bkbg-iis-active'); |
| 156 |
startTick(); |
| 157 |
}); |
| 158 |
|
| 159 |
card.addEventListener('mouseleave', function () { |
| 160 |
targetRX = 0; |
| 161 |
targetRY = 0; |
| 162 |
sheenX = 50; |
| 163 |
sheenY = 50; |
| 164 |
card.classList.remove('bkbg-iis-active'); |
| 165 |
startTick(); |
| 166 |
}); |
| 167 |
}); |
| 168 |
}()); |
| 169 |
|