| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
var _typoKeys = { |
| 5 |
family: '-ff', sizeDesktop: '-fs', sizeTablet: '-fs-tab', sizeMobile: '-fs-mob', |
| 6 |
weight: '-fw', style: '-fst', decoration: '-td', transform: '-tt', |
| 7 |
letterSpacing: '-ls', lineHeight: '-lh' |
| 8 |
}; |
| 9 |
function typoCssVarsForEl(el, obj, prefix) { |
| 10 |
if (!obj) return; |
| 11 |
Object.keys(_typoKeys).forEach(function (k) { |
| 12 |
var v = obj[k]; if (v === undefined || v === '') return; |
| 13 |
var u = (k === 'sizeDesktop' || k === 'sizeTablet' || k === 'sizeMobile') |
| 14 |
? (String(v).match(/[a-z%]/i) ? v : v + 'px') |
| 15 |
: (k === 'lineHeight' ? String(v) : v); |
| 16 |
el.style.setProperty(prefix + _typoKeys[k], u); |
| 17 |
}); |
| 18 |
} |
| 19 |
|
| 20 |
function scorePassword(pw, minLen) { |
| 21 |
if (!pw) return 0; |
| 22 |
var score = 0; |
| 23 |
if (pw.length >= minLen) score++; |
| 24 |
if (/[A-Z]/.test(pw)) score++; |
| 25 |
if (/[a-z]/.test(pw)) score++; |
| 26 |
if (/[0-9]/.test(pw)) score++; |
| 27 |
if (/[^a-zA-Z0-9]/.test(pw)) score++; |
| 28 |
return score; // 0-5 |
| 29 |
} |
| 30 |
|
| 31 |
var LEVEL_COLORS = [null, '#ef4444', '#f59e0b', '#3b82f6', '#10b981']; |
| 32 |
|
| 33 |
function init(app) { |
| 34 |
var a; |
| 35 |
try { a = JSON.parse(app.getAttribute('data-opts')); } catch(e) { return; } |
| 36 |
|
| 37 |
var minLen = a.minLength || 8; |
| 38 |
var strengthColors = [null, a.colorWeak||'#ef4444', a.colorFair||'#f59e0b', a.colorGood||'#3b82f6', a.colorStrong||'#10b981']; |
| 39 |
var strengthLabels = [null, a.labelWeak||'Weak', a.labelFair||'Fair', a.labelGood||'Good', a.labelStrong||'Strong']; |
| 40 |
|
| 41 |
// Section wrappers |
| 42 |
app.style.paddingTop = (a.paddingTop || 60) + 'px'; |
| 43 |
app.style.paddingBottom = (a.paddingBottom || 60) + 'px'; |
| 44 |
if (a.sectionBg) app.style.background = a.sectionBg; |
| 45 |
app.innerHTML = ''; |
| 46 |
|
| 47 |
var wrap = document.createElement('div'); |
| 48 |
wrap.className = 'bkbg-psm-wrap'; |
| 49 |
wrap.style.maxWidth = (a.maxWidth || 480) + 'px'; |
| 50 |
wrap.style.borderRadius = (a.cardRadius || 16) + 'px'; |
| 51 |
wrap.style.background = a.cardBg || '#fff'; |
| 52 |
app.appendChild(wrap); |
| 53 |
|
| 54 |
// Header |
| 55 |
if (a.showTitle || a.showSubtitle) { |
| 56 |
var header = document.createElement('div'); |
| 57 |
header.className = 'bkbg-psm-header'; |
| 58 |
if (a.showTitle && a.title) { |
| 59 |
var title = document.createElement('div'); |
| 60 |
title.className = 'bkbg-psm-title'; |
| 61 |
title.textContent = a.title; |
| 62 |
if (a.titleColor) title.style.color = a.titleColor; |
| 63 |
typoCssVarsForEl(title, a.titleTypo, '--bkbg-psm-title'); |
| 64 |
header.appendChild(title); |
| 65 |
} |
| 66 |
if (a.showSubtitle && a.subtitle) { |
| 67 |
var sub = document.createElement('div'); |
| 68 |
sub.className = 'bkbg-psm-subtitle'; |
| 69 |
sub.textContent = a.subtitle; |
| 70 |
if (a.subtitleColor) sub.style.color = a.subtitleColor; |
| 71 |
typoCssVarsForEl(sub, a.subtitleTypo, '--bkbg-psm-subtitle'); |
| 72 |
header.appendChild(sub); |
| 73 |
} |
| 74 |
wrap.appendChild(header); |
| 75 |
} |
| 76 |
|
| 77 |
// Input row |
| 78 |
var inputWrap = document.createElement('div'); |
| 79 |
inputWrap.className = 'bkbg-psm-input-wrap'; |
| 80 |
|
| 81 |
var input = document.createElement('input'); |
| 82 |
input.type = 'password'; |
| 83 |
input.className = 'bkbg-psm-input'; |
| 84 |
input.placeholder = a.placeholder || 'Enter a password…'; |
| 85 |
input.style.borderRadius = (a.inputRadius || 10) + 'px'; |
| 86 |
input.style.border = '1.5px solid ' + (a.inputBorder || '#e5e7eb'); |
| 87 |
input.style.background = a.inputBg || '#f9fafb'; |
| 88 |
if (!a.showToggleVisibility) input.style.paddingRight = '16px'; |
| 89 |
inputWrap.appendChild(input); |
| 90 |
|
| 91 |
if (a.showToggleVisibility) { |
| 92 |
var toggleBtn = document.createElement('button'); |
| 93 |
toggleBtn.type = 'button'; |
| 94 |
toggleBtn.className = 'bkbg-psm-toggle-vis'; |
| 95 |
toggleBtn.textContent = '👁️'; |
| 96 |
var visible = false; |
| 97 |
toggleBtn.addEventListener('click', function() { |
| 98 |
visible = !visible; |
| 99 |
input.type = visible ? 'text' : 'password'; |
| 100 |
toggleBtn.textContent = visible ? '🙈' : '👁️'; |
| 101 |
}); |
| 102 |
inputWrap.appendChild(toggleBtn); |
| 103 |
} |
| 104 |
wrap.appendChild(inputWrap); |
| 105 |
|
| 106 |
// Strength bar |
| 107 |
var bar = document.createElement('div'); |
| 108 |
bar.className = 'bkbg-psm-bar'; |
| 109 |
var segments = [1,2,3,4].map(function(i) { |
| 110 |
var seg = document.createElement('div'); |
| 111 |
seg.className = 'bkbg-psm-bar-seg'; |
| 112 |
seg.style.background = a.barBg || '#e5e7eb'; |
| 113 |
bar.appendChild(seg); |
| 114 |
return seg; |
| 115 |
}); |
| 116 |
wrap.appendChild(bar); |
| 117 |
|
| 118 |
// Strength label row |
| 119 |
var strengthRow = document.createElement('div'); |
| 120 |
strengthRow.className = 'bkbg-psm-strength-row'; |
| 121 |
var strengthHint = document.createElement('span'); |
| 122 |
strengthHint.className = 'bkbg-psm-strength-hint'; |
| 123 |
strengthHint.style.color = a.labelColor || '#6b7280'; |
| 124 |
strengthHint.textContent = ''; |
| 125 |
var strengthLabel = document.createElement('span'); |
| 126 |
strengthLabel.className = 'bkbg-psm-strength-label'; |
| 127 |
strengthRow.appendChild(strengthHint); |
| 128 |
strengthRow.appendChild(strengthLabel); |
| 129 |
wrap.appendChild(strengthRow); |
| 130 |
|
| 131 |
// Requirements |
| 132 |
var reqs = [ |
| 133 |
{ key:'showLengthReq', test: function(pw){ return pw.length >= minLen; }, label: 'At least ' + minLen + ' characters' }, |
| 134 |
{ key:'showUppercaseReq', test: function(pw){ return /[A-Z]/.test(pw); }, label: 'Uppercase letter (A-Z)' }, |
| 135 |
{ key:'showLowercaseReq', test: function(pw){ return /[a-z]/.test(pw); }, label: 'Lowercase letter (a-z)' }, |
| 136 |
{ key:'showNumberReq', test: function(pw){ return /[0-9]/.test(pw); }, label: 'Number (0-9)' }, |
| 137 |
{ key:'showSymbolReq', test: function(pw){ return /[^a-zA-Z0-9]/.test(pw); }, label: 'Special character (!@#…)' } |
| 138 |
].filter(function(r){ return a[r.key]; }); |
| 139 |
|
| 140 |
var reqEls = []; |
| 141 |
if (reqs.length) { |
| 142 |
var reqContainer = document.createElement('div'); |
| 143 |
reqContainer.className = 'bkbg-psm-reqs'; |
| 144 |
reqs.forEach(function(r) { |
| 145 |
var row = document.createElement('div'); |
| 146 |
row.className = 'bkbg-psm-req'; |
| 147 |
var icon = document.createElement('span'); |
| 148 |
icon.className = 'bkbg-psm-req-icon'; |
| 149 |
icon.textContent = '○'; |
| 150 |
var txt = document.createElement('span'); |
| 151 |
txt.textContent = r.label; |
| 152 |
row.appendChild(icon); |
| 153 |
row.appendChild(txt); |
| 154 |
reqContainer.appendChild(row); |
| 155 |
reqEls.push({ el: row, icon: icon, test: r.test }); |
| 156 |
}); |
| 157 |
wrap.appendChild(reqContainer); |
| 158 |
} |
| 159 |
|
| 160 |
function update() { |
| 161 |
var pw = input.value; |
| 162 |
var score = scorePassword(pw, minLen); |
| 163 |
var level = pw.length === 0 ? 0 : (score <= 2 ? score <= 1 ? 1 : 2 : score <= 3 ? 3 : 4); |
| 164 |
|
| 165 |
segments.forEach(function(seg, i) { |
| 166 |
seg.style.background = (pw.length > 0 && i < level) |
| 167 |
? (strengthColors[level] || '#10b981') |
| 168 |
: (a.barBg || '#e5e7eb'); |
| 169 |
}); |
| 170 |
|
| 171 |
if (pw.length > 0) { |
| 172 |
strengthHint.textContent = 'Password strength:'; |
| 173 |
strengthLabel.textContent = strengthLabels[level] || ''; |
| 174 |
strengthLabel.style.color = strengthColors[level] || '#10b981'; |
| 175 |
} else { |
| 176 |
strengthHint.textContent = ''; |
| 177 |
strengthLabel.textContent = ''; |
| 178 |
} |
| 179 |
|
| 180 |
reqEls.forEach(function(r) { |
| 181 |
var met = pw.length > 0 && r.test(pw); |
| 182 |
r.el.classList.toggle('met', met); |
| 183 |
r.icon.textContent = met ? '✓' : '○'; |
| 184 |
r.el.style.color = met ? (a.reqMetColor || '#10b981') : (a.reqUnmetColor || '#9ca3af'); |
| 185 |
}); |
| 186 |
} |
| 187 |
|
| 188 |
input.addEventListener('input', update); |
| 189 |
update(); |
| 190 |
} |
| 191 |
|
| 192 |
document.querySelectorAll('.bkbg-psm-app').forEach(init); |
| 193 |
})(); |
| 194 |
|