| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
function typoCssVarsForEl(typo, prefix, el) { |
| 5 |
if (!typo) return; |
| 6 |
if (typo.family) el.style.setProperty(prefix + 'font-family', "'" + typo.family + "', sans-serif"); |
| 7 |
if (typo.weight) el.style.setProperty(prefix + 'font-weight', typo.weight); |
| 8 |
if (typo.transform) el.style.setProperty(prefix + 'text-transform', typo.transform); |
| 9 |
if (typo.style) el.style.setProperty(prefix + 'font-style', typo.style); |
| 10 |
if (typo.decoration) el.style.setProperty(prefix + 'text-decoration', typo.decoration); |
| 11 |
var su = typo.sizeUnit || 'px'; |
| 12 |
if (typo.sizeDesktop !== '' && typo.sizeDesktop != null) el.style.setProperty(prefix + 'font-size-d', typo.sizeDesktop + su); |
| 13 |
if (typo.sizeTablet !== '' && typo.sizeTablet != null) el.style.setProperty(prefix + 'font-size-t', typo.sizeTablet + su); |
| 14 |
if (typo.sizeMobile !== '' && typo.sizeMobile != null) el.style.setProperty(prefix + 'font-size-m', typo.sizeMobile + su); |
| 15 |
var lhu = typo.lineHeightUnit || 'px'; |
| 16 |
if (typo.lineHeightDesktop !== '' && typo.lineHeightDesktop != null) el.style.setProperty(prefix + 'line-height-d', typo.lineHeightDesktop + lhu); |
| 17 |
if (typo.lineHeightTablet !== '' && typo.lineHeightTablet != null) el.style.setProperty(prefix + 'line-height-t', typo.lineHeightTablet + lhu); |
| 18 |
if (typo.lineHeightMobile !== '' && typo.lineHeightMobile != null) el.style.setProperty(prefix + 'line-height-m', typo.lineHeightMobile + lhu); |
| 19 |
var lsu = typo.letterSpacingUnit || 'px'; |
| 20 |
if (typo.letterSpacingDesktop !== '' && typo.letterSpacingDesktop != null) { el.style.setProperty(prefix + 'letter-spacing-d', typo.letterSpacingDesktop + lsu); el.style.setProperty(prefix + 'letter-spacing', typo.letterSpacingDesktop + lsu); } |
| 21 |
if (typo.letterSpacingTablet !== '' && typo.letterSpacingTablet != null) el.style.setProperty(prefix + 'letter-spacing-t', typo.letterSpacingTablet + lsu); |
| 22 |
if (typo.letterSpacingMobile !== '' && typo.letterSpacingMobile != null) el.style.setProperty(prefix + 'letter-spacing-m', typo.letterSpacingMobile + lsu); |
| 23 |
var wsu = typo.wordSpacingUnit || 'px'; |
| 24 |
if (typo.wordSpacingDesktop !== '' && typo.wordSpacingDesktop != null) { el.style.setProperty(prefix + 'word-spacing-d', typo.wordSpacingDesktop + wsu); el.style.setProperty(prefix + 'word-spacing', typo.wordSpacingDesktop + wsu); } |
| 25 |
if (typo.wordSpacingTablet !== '' && typo.wordSpacingTablet != null) el.style.setProperty(prefix + 'word-spacing-t', typo.wordSpacingTablet + wsu); |
| 26 |
if (typo.wordSpacingMobile !== '' && typo.wordSpacingMobile != null) el.style.setProperty(prefix + 'word-spacing-m', typo.wordSpacingMobile + wsu); |
| 27 |
} |
| 28 |
|
| 29 |
var DAYS_OF_WEEK = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']; |
| 30 |
var ZODIAC = [ |
| 31 |
{ sign:'Capricorn', symbol:'♑', end:[1,19] }, |
| 32 |
{ sign:'Aquarius', symbol:'♒', end:[2,18] }, |
| 33 |
{ sign:'Pisces', symbol:'♓', end:[3,20] }, |
| 34 |
{ sign:'Aries', symbol:'♈', end:[4,19] }, |
| 35 |
{ sign:'Taurus', symbol:'♉', end:[5,20] }, |
| 36 |
{ sign:'Gemini', symbol:'♊', end:[6,20] }, |
| 37 |
{ sign:'Cancer', symbol:'♋', end:[7,22] }, |
| 38 |
{ sign:'Leo', symbol:'♌', end:[8,22] }, |
| 39 |
{ sign:'Virgo', symbol:'♍', end:[9,22] }, |
| 40 |
{ sign:'Libra', symbol:'♎', end:[10,22] }, |
| 41 |
{ sign:'Scorpio', symbol:'♏', end:[11,21] }, |
| 42 |
{ sign:'Sagittarius',symbol:'♐', end:[12,21] }, |
| 43 |
{ sign:'Capricorn', symbol:'♑', end:[12,31] }, |
| 44 |
]; |
| 45 |
|
| 46 |
function getZodiac(month, day) { |
| 47 |
for (var i = 0; i < ZODIAC.length; i++) { |
| 48 |
var z = ZODIAC[i]; |
| 49 |
if (month < z.end[0] || (month === z.end[0] && day <= z.end[1])) return z; |
| 50 |
} |
| 51 |
return ZODIAC[ZODIAC.length - 1]; |
| 52 |
} |
| 53 |
|
| 54 |
function calcAge(birthDate, now) { |
| 55 |
var years = now.getFullYear() - birthDate.getFullYear(); |
| 56 |
var months = now.getMonth() - birthDate.getMonth(); |
| 57 |
var days = now.getDate() - birthDate.getDate(); |
| 58 |
if (days < 0) { |
| 59 |
months--; |
| 60 |
var prevMonth = new Date(now.getFullYear(), now.getMonth(), 0); |
| 61 |
days += prevMonth.getDate(); |
| 62 |
} |
| 63 |
if (months < 0) { years--; months += 12; } |
| 64 |
var totalDays = Math.floor((now - birthDate) / 86400000); |
| 65 |
return { years: years, months: months, days: days, totalDays: totalDays }; |
| 66 |
} |
| 67 |
|
| 68 |
function daysUntilBirthday(birthDate, now) { |
| 69 |
var next = new Date(now.getFullYear(), birthDate.getMonth(), birthDate.getDate()); |
| 70 |
if (next < now) next.setFullYear(now.getFullYear() + 1); |
| 71 |
return Math.ceil((next - now) / 86400000); |
| 72 |
} |
| 73 |
|
| 74 |
function fmt(n) { return n.toLocaleString('en-US'); } |
| 75 |
|
| 76 |
function buildApp(app) { |
| 77 |
var opts = {}; |
| 78 |
try { opts = JSON.parse(app.getAttribute('data-opts') || '{}'); } catch (e) {} |
| 79 |
|
| 80 |
var accent = opts.accentColor || '#6c3fb5'; |
| 81 |
var cardBg = opts.cardBg || '#ffffff'; |
| 82 |
var ageCardBg = opts.ageCardBg || accent; |
| 83 |
var ageCardClr= opts.ageCardColor || '#ffffff'; |
| 84 |
var resultBg = opts.resultBg || '#f5f3ff'; |
| 85 |
var resultBdr = opts.resultBorder || '#ede9fe'; |
| 86 |
var statsBg = opts.statsBg || '#fafafa'; |
| 87 |
var statsBdr = opts.statsBorder || '#e5e7eb'; |
| 88 |
var titleClr = opts.titleColor || '#1e1b4b'; |
| 89 |
var subClr = opts.subtitleColor || '#6b7280'; |
| 90 |
var labelClr = opts.labelColor || '#374151'; |
| 91 |
var cRadius = (opts.cardRadius !== undefined ? opts.cardRadius : 16) + 'px'; |
| 92 |
var aRadius = (opts.ageCardRadius !== undefined ? opts.ageCardRadius : 12) + 'px'; |
| 93 |
var maxW = (opts.maxWidth || 680) + 'px'; |
| 94 |
var ptop = (opts.paddingTop !== undefined ? opts.paddingTop : 60) + 'px'; |
| 95 |
var pbot = (opts.paddingBottom!== undefined ? opts.paddingBottom : 60) + 'px'; |
| 96 |
var titleSz = (opts.titleSize || 28) + 'px'; |
| 97 |
var ageNumSz = (opts.ageNumSize || 52) + 'px'; |
| 98 |
|
| 99 |
/* Card */ |
| 100 |
var card = document.createElement('div'); |
| 101 |
card.className = 'bkbg-age-card'; |
| 102 |
card.style.cssText = [ |
| 103 |
'background:' + cardBg, |
| 104 |
'border-radius:' + cRadius, |
| 105 |
'padding-top:' + ptop, |
| 106 |
'padding-bottom:' + pbot, |
| 107 |
'padding-left:32px', |
| 108 |
'padding-right:32px', |
| 109 |
'max-width:' + maxW, |
| 110 |
'margin:0 auto', |
| 111 |
].join(';'); |
| 112 |
app.innerHTML = ''; |
| 113 |
app.appendChild(card); |
| 114 |
|
| 115 |
/* Typography CSS vars */ |
| 116 |
app.style.setProperty('--bkbg-age-title-sz', titleSz); |
| 117 |
app.style.setProperty('--bkbg-age-num-sz', ageNumSz); |
| 118 |
typoCssVarsForEl(opts.titleTypo, '--bkbg-age-title-', app); |
| 119 |
typoCssVarsForEl(opts.ageNumTypo, '--bkbg-age-num-', app); |
| 120 |
|
| 121 |
/* Title */ |
| 122 |
if (opts.showTitle !== false && opts.title) { |
| 123 |
var h2 = document.createElement('h2'); |
| 124 |
h2.className = 'bkbg-age-title'; |
| 125 |
h2.style.cssText = 'color:' + titleClr + ';text-align:center;margin-top:0;margin-bottom:8px;font-weight:800'; |
| 126 |
h2.textContent = opts.title; |
| 127 |
card.appendChild(h2); |
| 128 |
} |
| 129 |
|
| 130 |
/* Subtitle */ |
| 131 |
if (opts.showSubtitle !== false && opts.subtitle) { |
| 132 |
var sub = document.createElement('p'); |
| 133 |
sub.style.cssText = 'color:' + subClr + ';text-align:center;margin-top:0;margin-bottom:28px'; |
| 134 |
sub.textContent = opts.subtitle; |
| 135 |
card.appendChild(sub); |
| 136 |
} |
| 137 |
|
| 138 |
/* Date input */ |
| 139 |
var dateField = document.createElement('div'); |
| 140 |
dateField.className = 'bkbg-age-date-field'; |
| 141 |
var dateLabel = document.createElement('label'); |
| 142 |
dateLabel.className = 'bkbg-age-date-label'; |
| 143 |
dateLabel.style.color = labelClr; |
| 144 |
dateLabel.textContent = 'Date of Birth'; |
| 145 |
var dateInput = document.createElement('input'); |
| 146 |
dateInput.type = 'date'; |
| 147 |
dateInput.className = 'bkbg-age-date-input'; |
| 148 |
dateInput.max = new Date().toISOString().slice(0, 10); |
| 149 |
dateField.appendChild(dateLabel); |
| 150 |
dateField.appendChild(dateInput); |
| 151 |
card.appendChild(dateField); |
| 152 |
|
| 153 |
/* Results area (hidden until date chosen) */ |
| 154 |
var resultsDiv = document.createElement('div'); |
| 155 |
card.appendChild(resultsDiv); |
| 156 |
|
| 157 |
/* Empty state */ |
| 158 |
var emptyDiv = document.createElement('div'); |
| 159 |
emptyDiv.className = 'bkbg-age-empty'; |
| 160 |
emptyDiv.textContent = 'Enter your birth date above to see your age'; |
| 161 |
card.appendChild(emptyDiv); |
| 162 |
|
| 163 |
function render() { |
| 164 |
var val = dateInput.value; |
| 165 |
if (!val) { resultsDiv.innerHTML = ''; emptyDiv.style.display = 'block'; return; } |
| 166 |
|
| 167 |
var now = new Date(); |
| 168 |
now.setHours(0,0,0,0); |
| 169 |
var birthDate = new Date(val); |
| 170 |
if (isNaN(birthDate.getTime()) || birthDate >= now) { resultsDiv.innerHTML = ''; emptyDiv.style.display = 'block'; return; } |
| 171 |
|
| 172 |
emptyDiv.style.display = 'none'; |
| 173 |
resultsDiv.innerHTML = ''; |
| 174 |
|
| 175 |
var age = calcAge(birthDate, now); |
| 176 |
var untilBd= daysUntilBirthday(birthDate, now); |
| 177 |
var zodiac = getZodiac(birthDate.getMonth() + 1, birthDate.getDate()); |
| 178 |
var bornDay= DAYS_OF_WEEK[birthDate.getDay()]; |
| 179 |
|
| 180 |
/* Age cards */ |
| 181 |
if (opts.showAgeCards !== false) { |
| 182 |
var ageCards = document.createElement('div'); |
| 183 |
ageCards.className = 'bkbg-age-cards'; |
| 184 |
ageCards.style.marginBottom = '20px'; |
| 185 |
[ |
| 186 |
{ val: age.years, label: opts.yearsLabel || 'Years' }, |
| 187 |
{ val: age.months, label: opts.monthsLabel || 'Months' }, |
| 188 |
{ val: age.days, label: opts.daysLabel || 'Days' }, |
| 189 |
].forEach(function (item) { |
| 190 |
var ci = document.createElement('div'); |
| 191 |
ci.className = 'bkbg-age-card-item'; |
| 192 |
ci.style.cssText = 'background:' + ageCardBg + ';border-radius:' + aRadius; |
| 193 |
var numEl = document.createElement('div'); |
| 194 |
numEl.className = 'bkbg-age-num'; |
| 195 |
numEl.style.cssText = 'color:' + ageCardClr; |
| 196 |
numEl.textContent = item.val; |
| 197 |
var lblEl = document.createElement('div'); |
| 198 |
lblEl.className = 'bkbg-age-card-label'; |
| 199 |
lblEl.style.color = ageCardClr; |
| 200 |
lblEl.textContent = item.label; |
| 201 |
ci.appendChild(numEl); |
| 202 |
ci.appendChild(lblEl); |
| 203 |
ageCards.appendChild(ci); |
| 204 |
}); |
| 205 |
resultsDiv.appendChild(ageCards); |
| 206 |
} |
| 207 |
|
| 208 |
/* Birthday countdown */ |
| 209 |
if (opts.showNextBirthday !== false) { |
| 210 |
var bdDiv = document.createElement('div'); |
| 211 |
bdDiv.className = 'bkbg-age-birthday'; |
| 212 |
bdDiv.style.cssText = 'background:' + resultBg + ';border:1.5px solid ' + resultBdr; |
| 213 |
var bdEmoji = document.createElement('span'); |
| 214 |
bdEmoji.className = 'bkbg-age-birthday-emoji'; |
| 215 |
bdEmoji.textContent = '🎂'; |
| 216 |
var bdInfo = document.createElement('div'); |
| 217 |
var bdMain = document.createElement('div'); |
| 218 |
bdMain.className = 'bkbg-age-birthday-main'; |
| 219 |
bdMain.style.color = titleClr; |
| 220 |
bdMain.textContent = untilBd === 0 ? '🎉 Happy Birthday!' : ('Next birthday in ' + untilBd + ' days'); |
| 221 |
var bdSub = document.createElement('div'); |
| 222 |
bdSub.className = 'bkbg-age-birthday-sub'; |
| 223 |
bdSub.style.color = subClr; |
| 224 |
bdSub.textContent = 'Turning ' + (age.years + 1) + ' soon'; |
| 225 |
bdInfo.appendChild(bdMain); |
| 226 |
bdInfo.appendChild(bdSub); |
| 227 |
bdDiv.appendChild(bdEmoji); |
| 228 |
bdDiv.appendChild(bdInfo); |
| 229 |
resultsDiv.appendChild(bdDiv); |
| 230 |
} |
| 231 |
|
| 232 |
/* Zodiac + born day */ |
| 233 |
var metaCount = (opts.showZodiac !== false ? 1 : 0) + (opts.showBornDay !== false ? 1 : 0); |
| 234 |
if (metaCount > 0) { |
| 235 |
var metaDiv = document.createElement('div'); |
| 236 |
metaDiv.className = 'bkbg-age-meta' + (metaCount === 2 ? ' two-col' : ''); |
| 237 |
metaDiv.style.marginBottom = '14px'; |
| 238 |
if (opts.showZodiac !== false) { |
| 239 |
var zEl = document.createElement('div'); |
| 240 |
zEl.className = 'bkbg-age-meta-item'; |
| 241 |
zEl.style.cssText = 'background:' + statsBg + ';border:1px solid ' + statsBdr; |
| 242 |
zEl.innerHTML = '<div class="bkbg-age-meta-label" style="color:' + subClr + '">Zodiac Sign</div>' + |
| 243 |
'<div class="bkbg-age-meta-value" style="color:' + titleClr + '">' + zodiac.symbol + ' ' + zodiac.sign + '</div>'; |
| 244 |
metaDiv.appendChild(zEl); |
| 245 |
} |
| 246 |
if (opts.showBornDay !== false) { |
| 247 |
var bdayEl = document.createElement('div'); |
| 248 |
bdayEl.className = 'bkbg-age-meta-item'; |
| 249 |
bdayEl.style.cssText = 'background:' + statsBg + ';border:1px solid ' + statsBdr; |
| 250 |
bdayEl.innerHTML = '<div class="bkbg-age-meta-label" style="color:' + subClr + '">Born On</div>' + |
| 251 |
'<div class="bkbg-age-meta-value" style="color:' + titleClr + '">� |
| 252 |
' + bornDay + '</div>'; |
| 253 |
metaDiv.appendChild(bdayEl); |
| 254 |
} |
| 255 |
resultsDiv.appendChild(metaDiv); |
| 256 |
} |
| 257 |
|
| 258 |
/* Life stats */ |
| 259 |
if (opts.showLifeStats !== false) { |
| 260 |
var statsDiv = document.createElement('div'); |
| 261 |
statsDiv.className = 'bkbg-age-stats'; |
| 262 |
statsDiv.style.cssText = 'background:' + statsBg + ';border:1px solid ' + statsBdr; |
| 263 |
var statsHead = document.createElement('div'); |
| 264 |
statsHead.className = 'bkbg-age-stats-heading'; |
| 265 |
statsHead.style.color = subClr; |
| 266 |
statsHead.textContent = 'Life Stats'; |
| 267 |
statsDiv.appendChild(statsHead); |
| 268 |
var grid = document.createElement('div'); |
| 269 |
grid.className = 'bkbg-age-stats-grid'; |
| 270 |
[ |
| 271 |
{ label:'Total Days', val: fmt(age.totalDays) }, |
| 272 |
{ label:'Total Weeks', val: fmt(Math.floor(age.totalDays / 7)) }, |
| 273 |
{ label:'Total Hours', val: fmt(age.totalDays * 24) }, |
| 274 |
{ label:'Heartbeats (est.)', val: fmt(age.totalDays * 24 * 60 * 72) }, |
| 275 |
].forEach(function (s) { |
| 276 |
var si = document.createElement('div'); |
| 277 |
var sl = document.createElement('div'); |
| 278 |
sl.className = 'bkbg-age-stat-label'; |
| 279 |
sl.style.color = subClr; |
| 280 |
sl.textContent = s.label; |
| 281 |
var sv = document.createElement('div'); |
| 282 |
sv.className = 'bkbg-age-stat-value'; |
| 283 |
sv.style.color = accent; |
| 284 |
sv.textContent = s.val; |
| 285 |
si.appendChild(sl); |
| 286 |
si.appendChild(sv); |
| 287 |
grid.appendChild(si); |
| 288 |
}); |
| 289 |
statsDiv.appendChild(grid); |
| 290 |
resultsDiv.appendChild(statsDiv); |
| 291 |
} |
| 292 |
} |
| 293 |
|
| 294 |
dateInput.addEventListener('change', render); |
| 295 |
render(); |
| 296 |
} |
| 297 |
|
| 298 |
document.querySelectorAll('.bkbg-age-app').forEach(buildApp); |
| 299 |
})(); |
| 300 |
|