| 1 |
(function () { |
| 2 |
function typoCssVarsForEl(typo, prefix, el) { |
| 3 |
if (!typo || typeof typo !== 'object') return; |
| 4 |
if (typo.family) el.style.setProperty(prefix + 'font-family', "'" + typo.family + "', sans-serif"); |
| 5 |
if (typo.weight) el.style.setProperty(prefix + 'font-weight', typo.weight); |
| 6 |
if (typo.transform) el.style.setProperty(prefix + 'text-transform', typo.transform); |
| 7 |
if (typo.style) el.style.setProperty(prefix + 'font-style', typo.style); |
| 8 |
if (typo.decoration) el.style.setProperty(prefix + 'text-decoration', typo.decoration); |
| 9 |
['size', 'lineHeight', 'letterSpacing', 'wordSpacing'].forEach(function (prop) { |
| 10 |
var unit = typo[prop + 'Unit'] || 'px'; |
| 11 |
var d = typo[prop + 'Desktop'], t = typo[prop + 'Tablet'], m = typo[prop + 'Mobile']; |
| 12 |
var css = { size: 'font-size', lineHeight: 'line-height', letterSpacing: 'letter-spacing', wordSpacing: 'word-spacing' }[prop]; |
| 13 |
if (d !== undefined && d !== '') el.style.setProperty(prefix + css + '-d', d + unit); |
| 14 |
if (t !== undefined && t !== '') el.style.setProperty(prefix + css + '-t', t + unit); |
| 15 |
if (m !== undefined && m !== '') el.style.setProperty(prefix + css + '-m', m + unit); |
| 16 |
}); |
| 17 |
} |
| 18 |
|
| 19 |
function init() { |
| 20 |
document.querySelectorAll('.bkbg-fc-app').forEach(function (appEl) { |
| 21 |
if (appEl.dataset.rendered) return; |
| 22 |
appEl.dataset.rendered = '1'; |
| 23 |
var opts; |
| 24 |
try { opts = JSON.parse(appEl.dataset.opts || '{}'); } catch (e) { opts = {}; } |
| 25 |
var o = Object.assign({ |
| 26 |
cards: [], columns: 3, layout: 'grid', filterStyle: 'buttons', |
| 27 |
allLabel: 'All', showImages: true, showDescriptions: true, |
| 28 |
showLabels: true, showLinks: true, animateFilter: true, |
| 29 |
imageRatio: '3:2', cardRadius: 10, gap: 24, |
| 30 |
paddingTop: 0, paddingBottom: 0, bgColor: '', |
| 31 |
cardBg: '#ffffff', cardBorder: '#e5e7eb', |
| 32 |
titleColor: '#111827', descColor: '#6b7280', |
| 33 |
labelBg: '#ede9fe', labelColor: '#6366f1', linkColor: '#6366f1', |
| 34 |
filterBtnBg: '#f3f4f6', filterBtnColor: '#374151', |
| 35 |
filterBtnActiveBg: '#6366f1', filterBtnActiveColor: '#ffffff', |
| 36 |
accentColor: '#6366f1' |
| 37 |
}, opts); |
| 38 |
|
| 39 |
var cards = o.cards || []; |
| 40 |
if (!cards.length) { appEl.style.display = 'none'; return; } |
| 41 |
|
| 42 |
function mk(tag, cls, style) { |
| 43 |
var n = document.createElement(tag); |
| 44 |
if (cls) n.className = cls; |
| 45 |
if (style) Object.assign(n.style, style); |
| 46 |
return n; |
| 47 |
} |
| 48 |
|
| 49 |
var ratioMap = { '16:9': '56.25%', '4:3': '75%', '3:2': '66.66%', '1:1': '100%' }; |
| 50 |
var imgPadding = ratioMap[o.imageRatio] || '66.66%'; |
| 51 |
|
| 52 |
/* unique categories */ |
| 53 |
var cats = []; |
| 54 |
cards.forEach(function (c) { if (c.category && cats.indexOf(c.category) < 0) cats.push(c.category); }); |
| 55 |
|
| 56 |
/* Section */ |
| 57 |
var section = mk('div', 'bkbg-fc-section bkbg-fcards-wrap', { |
| 58 |
background: o.bgColor || '', |
| 59 |
paddingTop: o.paddingTop + 'px', |
| 60 |
paddingBottom: o.paddingBottom + 'px' |
| 61 |
}); |
| 62 |
typoCssVarsForEl(o.typoTitle, '--bkbg-fcards-tt-', section); |
| 63 |
typoCssVarsForEl(o.typoDesc, '--bkbg-fcards-td-', section); |
| 64 |
typoCssVarsForEl(o.typoFilter, '--bkbg-fcards-tf-', section); |
| 65 |
|
| 66 |
/* filter bar */ |
| 67 |
var filterWrap = mk('div', 'bkbg-fc-filter bkbg-fc-filter--' + o.filterStyle); |
| 68 |
var activeFilter = o.allLabel; |
| 69 |
var cardEls = []; |
| 70 |
|
| 71 |
function applyFilter(cat) { |
| 72 |
activeFilter = cat; |
| 73 |
/* update button states */ |
| 74 |
filterWrap.querySelectorAll('.bkbg-fc-btn').forEach(function (btn) { |
| 75 |
var isActive = btn.dataset.cat === cat; |
| 76 |
btn.style.background = isActive ? o.filterBtnActiveBg : o.filterBtnBg; |
| 77 |
btn.style.color = isActive ? o.filterBtnActiveColor : o.filterBtnColor; |
| 78 |
btn.classList.toggle('active', isActive); |
| 79 |
}); |
| 80 |
|
| 81 |
cardEls.forEach(function (info) { |
| 82 |
var visible = (cat === o.allLabel) || (info.card.category === cat); |
| 83 |
if (o.animateFilter) { |
| 84 |
if (visible) { |
| 85 |
info.el.style.display = ''; |
| 86 |
requestAnimationFrame(function () { info.el.classList.remove('bkbg-fc-fading'); }); |
| 87 |
} else { |
| 88 |
info.el.classList.add('bkbg-fc-fading'); |
| 89 |
setTimeout(function () { |
| 90 |
if (activeFilter !== o.allLabel && info.card.category !== activeFilter) { |
| 91 |
info.el.style.display = 'none'; |
| 92 |
} |
| 93 |
}, 380); |
| 94 |
} |
| 95 |
} else { |
| 96 |
info.el.style.display = visible ? '' : 'none'; |
| 97 |
} |
| 98 |
}); |
| 99 |
} |
| 100 |
|
| 101 |
if (o.filterStyle === 'dropdown') { |
| 102 |
var sel = document.createElement('select'); |
| 103 |
sel.className = 'bkbg-fc-dropdown'; |
| 104 |
Object.assign(sel.style, { |
| 105 |
background: o.filterBtnBg, color: o.filterBtnColor, |
| 106 |
border: '1px solid ' + o.cardBorder, borderRadius: '8px', |
| 107 |
padding: '9px 36px 9px 14px', cursor: 'pointer' |
| 108 |
}); |
| 109 |
var allOpt = document.createElement('option'); |
| 110 |
allOpt.value = o.allLabel; allOpt.textContent = o.allLabel; |
| 111 |
sel.appendChild(allOpt); |
| 112 |
cats.forEach(function (cat) { |
| 113 |
var opt = document.createElement('option'); |
| 114 |
opt.value = cat; opt.textContent = cat; |
| 115 |
sel.appendChild(opt); |
| 116 |
}); |
| 117 |
sel.addEventListener('change', function () { applyFilter(sel.value); }); |
| 118 |
filterWrap.appendChild(sel); |
| 119 |
} else { |
| 120 |
var btnsWrap = mk('div', 'bkbg-fc-btns'); |
| 121 |
var allCats = [o.allLabel].concat(cats); |
| 122 |
allCats.forEach(function (cat) { |
| 123 |
var btn = mk('div', 'bkbg-fc-btn' + (cat === o.allLabel ? ' active' : ''), { |
| 124 |
background: cat === o.allLabel ? o.filterBtnActiveBg : o.filterBtnBg, |
| 125 |
color: cat === o.allLabel ? o.filterBtnActiveColor : o.filterBtnColor, |
| 126 |
borderRadius: o.filterStyle === 'tabs' ? '6px 6px 0 0' : '999px' |
| 127 |
}); |
| 128 |
btn.dataset.cat = cat; |
| 129 |
btn.textContent = cat; |
| 130 |
btn.addEventListener('click', function () { applyFilter(cat); }); |
| 131 |
btnsWrap.appendChild(btn); |
| 132 |
}); |
| 133 |
filterWrap.appendChild(btnsWrap); |
| 134 |
} |
| 135 |
|
| 136 |
section.appendChild(filterWrap); |
| 137 |
|
| 138 |
/* Grid */ |
| 139 |
var colCount = parseInt(o.columns, 10) || 3; |
| 140 |
var gridStyle = { |
| 141 |
display: 'grid', |
| 142 |
gap: o.gap + 'px' |
| 143 |
}; |
| 144 |
gridStyle.gridTemplateColumns = o.layout === 'list' ? '1fr' : ('repeat(' + colCount + ', 1fr)'); |
| 145 |
|
| 146 |
var grid = mk('div', 'bkbg-fc-grid bkbg-fc-' + o.layout, gridStyle); |
| 147 |
|
| 148 |
cards.forEach(function (card) { |
| 149 |
var wrap = mk('div', 'bkbg-fc-card-wrap', { |
| 150 |
background: card.featured ? '' : o.cardBg, |
| 151 |
border: '1px solid ' + (card.featured ? o.accentColor : o.cardBorder), |
| 152 |
borderRadius: o.cardRadius + 'px', |
| 153 |
position: 'relative', |
| 154 |
overflow: 'hidden', |
| 155 |
flexDirection: o.layout === 'list' ? 'row' : 'column' |
| 156 |
}); |
| 157 |
|
| 158 |
if (card.featured) { |
| 159 |
var ftime = mk('div', 'bkbg-fc-featured-bar', { background: o.accentColor }); |
| 160 |
wrap.style.background = o.cardBg; |
| 161 |
wrap.appendChild(ftime); |
| 162 |
} |
| 163 |
|
| 164 |
/* image */ |
| 165 |
if (o.showImages) { |
| 166 |
var imgBox = mk('div', 'bkbg-fc-card-img', { |
| 167 |
paddingTop: o.layout === 'list' ? '' : imgPadding, |
| 168 |
width: o.layout === 'list' ? '200px' : '100%', |
| 169 |
height: o.layout === 'list' ? 'auto' : undefined, |
| 170 |
flexShrink: o.layout === 'list' ? '0' : undefined, |
| 171 |
minHeight: o.layout === 'list' ? '140px' : undefined |
| 172 |
}); |
| 173 |
if (card.imageUrl) { |
| 174 |
var img = document.createElement('img'); |
| 175 |
img.src = card.imageUrl; |
| 176 |
img.alt = card.imageAlt || card.title || ''; |
| 177 |
imgBox.appendChild(img); |
| 178 |
} else { |
| 179 |
var ph = mk('div', 'bkbg-fc-card-placeholder'); |
| 180 |
ph.textContent = 'No image'; |
| 181 |
imgBox.appendChild(ph); |
| 182 |
} |
| 183 |
wrap.appendChild(imgBox); |
| 184 |
} |
| 185 |
|
| 186 |
/* body */ |
| 187 |
var body = mk('div', 'bkbg-fc-card-body'); |
| 188 |
|
| 189 |
/* meta row: category + label */ |
| 190 |
var meta = mk('div', 'bkbg-fc-meta'); |
| 191 |
if (card.category) { |
| 192 |
var catEl = mk('span', 'bkbg-fc-cat', { color: o.descColor }); |
| 193 |
catEl.textContent = card.category; |
| 194 |
meta.appendChild(catEl); |
| 195 |
} |
| 196 |
if (o.showLabels && card.label) { |
| 197 |
var labelEl = mk('span', 'bkbg-fc-label', { background: o.labelBg, color: o.labelColor }); |
| 198 |
labelEl.textContent = card.label; |
| 199 |
meta.appendChild(labelEl); |
| 200 |
} |
| 201 |
body.appendChild(meta); |
| 202 |
|
| 203 |
/* title */ |
| 204 |
var titleEl = mk('h3', 'bkbg-fc-card-title', { color: o.titleColor }); |
| 205 |
titleEl.innerHTML = card.title || ''; |
| 206 |
body.appendChild(titleEl); |
| 207 |
|
| 208 |
/* description */ |
| 209 |
if (o.showDescriptions && card.description) { |
| 210 |
var desc = mk('p', 'bkbg-fc-card-desc', { color: o.descColor }); |
| 211 |
desc.innerHTML = card.description; |
| 212 |
body.appendChild(desc); |
| 213 |
} |
| 214 |
|
| 215 |
/* link */ |
| 216 |
if (o.showLinks && card.link) { |
| 217 |
var lnk = document.createElement('a'); |
| 218 |
lnk.className = 'bkbg-fc-card-link'; |
| 219 |
lnk.href = card.link; |
| 220 |
lnk.style.color = o.linkColor; |
| 221 |
lnk.textContent = (card.linkLabel || 'Learn More') + ' →'; |
| 222 |
body.appendChild(lnk); |
| 223 |
} |
| 224 |
|
| 225 |
wrap.appendChild(body); |
| 226 |
grid.appendChild(wrap); |
| 227 |
cardEls.push({ el: wrap, card: card }); |
| 228 |
}); |
| 229 |
|
| 230 |
section.appendChild(grid); |
| 231 |
appEl.parentNode.insertBefore(section, appEl); |
| 232 |
}); |
| 233 |
} |
| 234 |
|
| 235 |
if (document.readyState !== 'loading') { init(); } |
| 236 |
else { document.addEventListener('DOMContentLoaded', init); } |
| 237 |
})(); |
| 238 |
|