| 1 |
(function () { |
| 2 |
var AVATAR_COLORS = ['#6366f1', '#8b5cf6', '#ec4899', '#14b8a6', '#f59e0b', '#ef4444']; |
| 3 |
|
| 4 |
function strHash(s) { |
| 5 |
var h = 0; |
| 6 |
for (var i = 0; i < s.length; i++) { h = (h + s.charCodeAt(i)) | 0; } |
| 7 |
return Math.abs(h); |
| 8 |
} |
| 9 |
|
| 10 |
function buildCard(item, o) { |
| 11 |
var shadow = o.cardShadow === 'sm' ? '0 1px 6px rgba(0,0,0,0.07)' |
| 12 |
: o.cardShadow === 'md' ? '0 4px 16px rgba(0,0,0,0.10)' |
| 13 |
: o.cardShadow === 'lg' ? '0 8px 32px rgba(0,0,0,0.13)' : 'none'; |
| 14 |
|
| 15 |
var card = document.createElement('div'); |
| 16 |
card.className = 'bkbg-tm-card'; |
| 17 |
card.style.cssText = [ |
| 18 |
'width:' + o.cardWidth + 'px', |
| 19 |
'background:' + (o.cardBg || '#fff'), |
| 20 |
'border:1px solid ' + (o.cardBorder || '#e5e7eb'), |
| 21 |
'border-radius:' + o.cardRadius + 'px', |
| 22 |
'padding:' + o.cardPadding + 'px', |
| 23 |
'box-shadow:' + shadow, |
| 24 |
'gap:12px', |
| 25 |
].join(';'); |
| 26 |
|
| 27 |
// Big quote icon in background |
| 28 |
var qIcon = document.createElement('div'); |
| 29 |
qIcon.className = 'bkbg-tm-quote-bg'; |
| 30 |
qIcon.textContent = '\u201C'; |
| 31 |
qIcon.style.color = o.quoteIconColor || '#e0e7ff'; |
| 32 |
card.appendChild(qIcon); |
| 33 |
|
| 34 |
// Stars |
| 35 |
if (o.showStars) { |
| 36 |
var starsDiv = document.createElement('div'); |
| 37 |
starsDiv.className = 'bkbg-tm-stars'; |
| 38 |
for (var s = 1; s <= 5; s++) { |
| 39 |
var star = document.createElement('span'); |
| 40 |
star.textContent = '\u2605'; |
| 41 |
star.style.color = s <= item.rating ? (o.starColor || '#f59e0b') : '#e5e7eb'; |
| 42 |
star.style.fontSize = '14px'; |
| 43 |
star.style.lineHeight = '1'; |
| 44 |
starsDiv.appendChild(star); |
| 45 |
} |
| 46 |
card.appendChild(starsDiv); |
| 47 |
} |
| 48 |
|
| 49 |
// Quote text |
| 50 |
var quoteEl = document.createElement('p'); |
| 51 |
quoteEl.className = 'bkbg-tm-quote'; |
| 52 |
quoteEl.textContent = item.quote; |
| 53 |
quoteEl.style.color = o.quoteColor || '#374151'; |
| 54 |
card.appendChild(quoteEl); |
| 55 |
|
| 56 |
// Author row |
| 57 |
var authorRow = document.createElement('div'); |
| 58 |
authorRow.className = 'bkbg-tm-author-row'; |
| 59 |
authorRow.style.gap = '10px'; |
| 60 |
|
| 61 |
if (o.showAvatar) { |
| 62 |
if (item.avatarUrl) { |
| 63 |
var img = document.createElement('img'); |
| 64 |
img.className = 'bkbg-tm-avatar'; |
| 65 |
img.src = item.avatarUrl; |
| 66 |
img.alt = item.author; |
| 67 |
img.style.width = o.avatarSize + 'px'; |
| 68 |
img.style.height = o.avatarSize + 'px'; |
| 69 |
authorRow.appendChild(img); |
| 70 |
} else { |
| 71 |
var initials = (item.author || 'A').split(' ').map(function (w) { return w[0]; }).slice(0, 2).join('').toUpperCase(); |
| 72 |
var fallback = document.createElement('div'); |
| 73 |
fallback.className = 'bkbg-tm-avatar-fallback'; |
| 74 |
fallback.textContent = initials; |
| 75 |
fallback.style.width = o.avatarSize + 'px'; |
| 76 |
fallback.style.height = o.avatarSize + 'px'; |
| 77 |
fallback.style.fontSize = Math.round(o.avatarSize * 0.38) + 'px'; |
| 78 |
fallback.style.background = AVATAR_COLORS[strHash(item.author || '') % AVATAR_COLORS.length]; |
| 79 |
authorRow.appendChild(fallback); |
| 80 |
} |
| 81 |
} |
| 82 |
|
| 83 |
var info = document.createElement('div'); |
| 84 |
info.className = 'bkbg-tm-author-info'; |
| 85 |
|
| 86 |
var nameEl = document.createElement('div'); |
| 87 |
nameEl.className = 'bkbg-tm-author-name'; |
| 88 |
nameEl.textContent = item.author; |
| 89 |
nameEl.style.color = o.authorColor || '#111827'; |
| 90 |
info.appendChild(nameEl); |
| 91 |
|
| 92 |
var metaParts = []; |
| 93 |
if (o.showRole && item.role) metaParts.push(item.role); |
| 94 |
if (o.showCompany && item.company) metaParts.push(item.company); |
| 95 |
if (metaParts.length) { |
| 96 |
var metaEl = document.createElement('div'); |
| 97 |
metaEl.className = 'bkbg-tm-author-meta'; |
| 98 |
metaEl.textContent = metaParts.join(', '); |
| 99 |
metaEl.style.color = o.roleColor || '#6b7280'; |
| 100 |
info.appendChild(metaEl); |
| 101 |
} |
| 102 |
|
| 103 |
authorRow.appendChild(info); |
| 104 |
card.appendChild(authorRow); |
| 105 |
return card; |
| 106 |
} |
| 107 |
|
| 108 |
function buildTrack(items, o, direction) { |
| 109 |
// Duplicate items for seamless loop |
| 110 |
var allItems = items.concat(items); |
| 111 |
var track = document.createElement('div'); |
| 112 |
track.className = 'bkbg-tm-track dir-' + direction; |
| 113 |
track.style.gap = o.cardGap + 'px'; |
| 114 |
track.style.animationDuration = o.speed + 's'; |
| 115 |
|
| 116 |
allItems.forEach(function (item) { |
| 117 |
track.appendChild(buildCard(item, o)); |
| 118 |
}); |
| 119 |
return track; |
| 120 |
} |
| 121 |
|
| 122 |
function buildRow(items, o, direction) { |
| 123 |
var row = document.createElement('div'); |
| 124 |
row.className = 'bkbg-tm-row'; |
| 125 |
var viewport = document.createElement('div'); |
| 126 |
viewport.className = 'bkbg-tm-viewport' + (o.fadeEdges ? ' fade-edges' : ''); |
| 127 |
var track = buildTrack(items, o, direction); |
| 128 |
viewport.appendChild(track); |
| 129 |
row.appendChild(viewport); |
| 130 |
return row; |
| 131 |
} |
| 132 |
|
| 133 |
function init() { |
| 134 |
document.querySelectorAll('.bkbg-tm-app').forEach(function (appEl) { |
| 135 |
if (appEl.dataset.rendered) return; |
| 136 |
appEl.dataset.rendered = '1'; |
| 137 |
|
| 138 |
var opts; |
| 139 |
try { opts = JSON.parse(appEl.dataset.opts || '{}'); } catch (e) { opts = {}; } |
| 140 |
var o = Object.assign({ |
| 141 |
items: [], |
| 142 |
rows: 1, |
| 143 |
speed: 60, |
| 144 |
direction: 'left', |
| 145 |
pauseOnHover: true, |
| 146 |
fadeEdges: true, |
| 147 |
showStars: true, |
| 148 |
showAvatar: true, |
| 149 |
showRole: true, |
| 150 |
showCompany: true, |
| 151 |
cardWidth: 340, |
| 152 |
cardGap: 20, |
| 153 |
cardRadius: 14, |
| 154 |
cardPadding: 24, |
| 155 |
quoteSize: 15, |
| 156 |
avatarSize: 44, |
| 157 |
paddingY: 40, |
| 158 |
cardShadow: 'sm', |
| 159 |
wrapperBg: '', |
| 160 |
cardBg: '#ffffff', |
| 161 |
cardBorder: '#e5e7eb', |
| 162 |
quoteColor: '#374151', |
| 163 |
starColor: '#f59e0b', |
| 164 |
authorColor: '#111827', |
| 165 |
roleColor: '#6b7280', |
| 166 |
quoteIconColor: '#e0e7ff', |
| 167 |
}, opts); |
| 168 |
|
| 169 |
if (!o.items || !o.items.length) return; |
| 170 |
|
| 171 |
var outer = document.createElement('div'); |
| 172 |
outer.className = 'bkbg-tm-outer'; |
| 173 |
if (o.wrapperBg) outer.style.background = o.wrapperBg; |
| 174 |
outer.style.padding = o.paddingY + 'px 0'; |
| 175 |
|
| 176 |
var rowGap = o.cardGap; |
| 177 |
if (o.rows === 2) { |
| 178 |
outer.style.display = 'flex'; |
| 179 |
outer.style.flexDirection = 'column'; |
| 180 |
outer.style.gap = rowGap + 'px'; |
| 181 |
} |
| 182 |
|
| 183 |
// Row 1 |
| 184 |
outer.appendChild(buildRow(o.items, o, o.direction)); |
| 185 |
|
| 186 |
// Row 2 — opposite direction |
| 187 |
if (o.rows === 2) { |
| 188 |
var dir2 = o.direction === 'left' ? 'right' : 'left'; |
| 189 |
outer.appendChild(buildRow(o.items, o, dir2)); |
| 190 |
} |
| 191 |
|
| 192 |
// Pause on hover |
| 193 |
if (o.pauseOnHover) { |
| 194 |
outer.addEventListener('mouseenter', function () { outer.classList.add('paused'); }); |
| 195 |
outer.addEventListener('mouseleave', function () { outer.classList.remove('paused'); }); |
| 196 |
} |
| 197 |
|
| 198 |
appEl.parentNode.insertBefore(outer, appEl); |
| 199 |
appEl.style.display = 'none'; |
| 200 |
}); |
| 201 |
} |
| 202 |
|
| 203 |
if (document.readyState !== 'loading') { init(); } |
| 204 |
else { document.addEventListener('DOMContentLoaded', init); } |
| 205 |
})(); |
| 206 |
|