| 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 |
// ---------- Conversion helpers ---------- |
| 30 |
function textToBinary(text) { |
| 31 |
return text.split('').map(function (c) { |
| 32 |
return c.charCodeAt(0).toString(2).padStart(8, '0'); |
| 33 |
}).join(' '); |
| 34 |
} |
| 35 |
function binaryToText(bin) { |
| 36 |
var parts = bin.trim().split(/\s+/); |
| 37 |
for (var i = 0; i < parts.length; i++) { |
| 38 |
if (!/^[01]{1,8}$/.test(parts[i])) throw new Error('Invalid binary: "' + parts[i] + '"'); |
| 39 |
} |
| 40 |
return parts.map(function (b) { return String.fromCharCode(parseInt(b, 2)); }).join(''); |
| 41 |
} |
| 42 |
function textToHex(text) { |
| 43 |
return text.split('').map(function (c) { |
| 44 |
return c.charCodeAt(0).toString(16).padStart(2, '0').toUpperCase(); |
| 45 |
}).join(' '); |
| 46 |
} |
| 47 |
function hexToText(hex) { |
| 48 |
var parts = hex.trim().split(/\s+/); |
| 49 |
for (var i = 0; i < parts.length; i++) { |
| 50 |
if (!/^[0-9a-fA-F]{1,2}$/.test(parts[i])) throw new Error('Invalid hex: "' + parts[i] + '"'); |
| 51 |
} |
| 52 |
return parts.map(function (h) { return String.fromCharCode(parseInt(h, 16)); }).join(''); |
| 53 |
} |
| 54 |
function textToOctal(text) { |
| 55 |
return text.split('').map(function (c) { |
| 56 |
return c.charCodeAt(0).toString(8).padStart(3, '0'); |
| 57 |
}).join(' '); |
| 58 |
} |
| 59 |
function octalToText(oct) { |
| 60 |
var parts = oct.trim().split(/\s+/); |
| 61 |
for (var i = 0; i < parts.length; i++) { |
| 62 |
if (!/^[0-7]{1,3}$/.test(parts[i])) throw new Error('Invalid octal: "' + parts[i] + '"'); |
| 63 |
} |
| 64 |
return parts.map(function (o) { return String.fromCharCode(parseInt(o, 8)); }).join(''); |
| 65 |
} |
| 66 |
|
| 67 |
var TABS = [ |
| 68 |
{ id: 'binary', label: 'Binary', color: null }, |
| 69 |
{ id: 'hex', label: 'Hexadecimal', color: null }, |
| 70 |
{ id: 'octal', label: 'Octal', color: null } |
| 71 |
]; |
| 72 |
|
| 73 |
// ASCII label for special characters |
| 74 |
function asciiChar(code) { |
| 75 |
var specials = { 0:'NUL',1:'SOH',2:'STX',3:'ETX',4:'EOT',5:'ENQ',6:'ACK',7:'BEL',8:'BS',9:'TAB',10:'LF',11:'VT',12:'FF',13:'CR',14:'SO',15:'SI',16:'DLE',17:'DC1',18:'DC2',19:'DC3',20:'DC4',21:'NAK',22:'SYN',23:'ETB',24:'CAN',25:'EM',26:'SUB',27:'ESC',28:'FS',29:'GS',30:'RS',31:'US',32:'SPC',127:'DEL' }; |
| 76 |
return specials[code] || String.fromCharCode(code); |
| 77 |
} |
| 78 |
|
| 79 |
function initApp(app) { |
| 80 |
var opts; |
| 81 |
try { opts = JSON.parse(app.getAttribute('data-opts') || '{}'); } catch(e) { return; } |
| 82 |
|
| 83 |
var accent = opts.accentColor || '#6c3fb5'; |
| 84 |
var binColor = opts.binaryColor || '#6c3fb5'; |
| 85 |
var hexColor = opts.hexColor || '#0ea5e9'; |
| 86 |
var octColor = opts.octalColor || '#10b981'; |
| 87 |
var asciiClr = opts.asciiColor || '#f59e0b'; |
| 88 |
var cardR = (opts.cardRadius || 16) + 'px'; |
| 89 |
var tabR = (opts.tabRadius || 8) + 'px'; |
| 90 |
var inpR = (opts.inputRadius || 8) + 'px'; |
| 91 |
var maxW = (opts.maxWidth || 700) + 'px'; |
| 92 |
var lblClr = opts.labelColor || '#374151'; |
| 93 |
var errC = opts.errorColor || '#ef4444'; |
| 94 |
|
| 95 |
var tabColors = { binary: binColor, hex: hexColor, octal: octColor }; |
| 96 |
|
| 97 |
TABS[0].color = binColor; TABS[1].color = hexColor; TABS[2].color = octColor; |
| 98 |
|
| 99 |
app.style.paddingTop = (opts.paddingTop || 60) + 'px'; |
| 100 |
app.style.paddingBottom = (opts.paddingBottom || 60) + 'px'; |
| 101 |
if (opts.sectionBg) app.style.background = opts.sectionBg; |
| 102 |
|
| 103 |
typoCssVarsForEl(opts.titleTypo, '--bkbg-btc-title-', app); |
| 104 |
typoCssVarsForEl(opts.subtitleTypo, '--bkbg-btc-sub-', app); |
| 105 |
|
| 106 |
var card = document.createElement('div'); |
| 107 |
card.className = 'bkbg-btc-card'; |
| 108 |
Object.assign(card.style, { background: opts.cardBg || '#fff', borderRadius: cardR, maxWidth: maxW }); |
| 109 |
app.appendChild(card); |
| 110 |
|
| 111 |
if (opts.showTitle && opts.title) { |
| 112 |
var ttl = document.createElement('div'); ttl.className = 'bkbg-btc-title'; |
| 113 |
ttl.textContent = opts.title; if (opts.titleColor) ttl.style.color = opts.titleColor; |
| 114 |
card.appendChild(ttl); |
| 115 |
} |
| 116 |
if (opts.showSubtitle && opts.subtitle) { |
| 117 |
var sub = document.createElement('div'); sub.className = 'bkbg-btc-subtitle'; |
| 118 |
sub.textContent = opts.subtitle; if (opts.subtitleColor) sub.style.color = opts.subtitleColor; |
| 119 |
card.appendChild(sub); |
| 120 |
} |
| 121 |
|
| 122 |
var activeTab = opts.defaultTab || 'binary'; |
| 123 |
var activeMode = opts.defaultMode || 'encode'; |
| 124 |
|
| 125 |
// Tabs |
| 126 |
var tabsRow = document.createElement('div'); tabsRow.className = 'bkbg-btc-tabs'; card.appendChild(tabsRow); |
| 127 |
var tabBtns = {}; |
| 128 |
TABS.forEach(function (t) { |
| 129 |
var b = document.createElement('button'); b.className = 'bkbg-btc-tab'; b.textContent = t.label; |
| 130 |
b.style.borderRadius = tabR; |
| 131 |
b.addEventListener('click', function () { activeTab = t.id; refreshTabs(); clearIO(); }); |
| 132 |
tabBtns[t.id] = b; tabsRow.appendChild(b); |
| 133 |
}); |
| 134 |
function refreshTabs() { |
| 135 |
TABS.forEach(function (t) { |
| 136 |
var b = tabBtns[t.id]; var active = t.id === activeTab; var c = t.color; |
| 137 |
b.style.background = active ? c : 'transparent'; |
| 138 |
b.style.color = active ? '#fff' : c; |
| 139 |
b.style.borderColor = active ? c : (opts.inputBorder || '#d1d5db'); |
| 140 |
}); |
| 141 |
} |
| 142 |
refreshTabs(); |
| 143 |
|
| 144 |
// Mode row |
| 145 |
var modeRow = document.createElement('div'); modeRow.className = 'bkbg-btc-mode-row'; card.appendChild(modeRow); |
| 146 |
var mLbl = document.createElement('span'); mLbl.className = 'bkbg-btc-mode-label'; mLbl.textContent = 'Direction:'; modeRow.appendChild(mLbl); |
| 147 |
var mBtns = {}; |
| 148 |
[{ id: 'encode', label: '📝 Text → Code' }, { id: 'decode', label: '🔓 Code → Text' }].forEach(function (m) { |
| 149 |
var b = document.createElement('button'); b.className = 'bkbg-btc-mode-btn'; b.textContent = m.label; |
| 150 |
b.addEventListener('click', function () { activeMode = m.id; refreshModes(); updateLabels(); clearIO(); }); |
| 151 |
mBtns[m.id] = b; modeRow.appendChild(b); |
| 152 |
}); |
| 153 |
function refreshModes() { |
| 154 |
Object.keys(mBtns).forEach(function (k) { |
| 155 |
var b = mBtns[k]; var active = k === activeMode; var c = tabColors[activeTab] || accent; |
| 156 |
b.style.background = active ? c : 'transparent'; |
| 157 |
b.style.color = active ? '#fff' : '#6b7280'; |
| 158 |
b.style.borderColor = active ? c : (opts.inputBorder || '#d1d5db'); |
| 159 |
}); |
| 160 |
} |
| 161 |
refreshModes(); |
| 162 |
|
| 163 |
// Input |
| 164 |
var inpLbl = document.createElement('label'); inpLbl.className = 'bkbg-btc-lbl'; inpLbl.style.color = lblClr; card.appendChild(inpLbl); |
| 165 |
var inp = document.createElement('textarea'); inp.className = 'bkbg-btc-textarea'; |
| 166 |
inp.style.borderRadius = inpR; inp.style.background = opts.inputBg || '#f9fafb'; |
| 167 |
inp.style.borderColor = opts.inputBorder || '#d1d5db'; |
| 168 |
inp.addEventListener('input', function () { errorEl.textContent = ''; }); |
| 169 |
card.appendChild(inp); |
| 170 |
|
| 171 |
// Convert button |
| 172 |
var cvtBtn = document.createElement('button'); cvtBtn.className = 'bkbg-btc-convert-btn'; |
| 173 |
card.appendChild(cvtBtn); |
| 174 |
|
| 175 |
// Error |
| 176 |
var errorEl = document.createElement('div'); errorEl.className = 'bkbg-btc-error'; errorEl.style.color = errC; card.appendChild(errorEl); |
| 177 |
|
| 178 |
// Output |
| 179 |
var outLbl = document.createElement('label'); outLbl.className = 'bkbg-btc-lbl'; outLbl.style.color = lblClr; card.appendChild(outLbl); |
| 180 |
var outBox = document.createElement('div'); outBox.className = 'bkbg-btc-output bkbg-btc-output-empty'; |
| 181 |
outBox.style.borderRadius = inpR; outBox.style.background = opts.outputBg || '#f0f9ff'; |
| 182 |
outBox.style.border = '1.5px solid ' + (opts.outputBorder || '#bae6fd'); |
| 183 |
outBox.textContent = 'Result will appear here…'; card.appendChild(outBox); |
| 184 |
|
| 185 |
// Stats + copy row |
| 186 |
var statsRow = document.createElement('div'); statsRow.className = 'bkbg-btc-stats'; card.appendChild(statsRow); |
| 187 |
var statChars = document.createElement('span'); statsRow.appendChild(statChars); |
| 188 |
var statBits = document.createElement('span'); statsRow.appendChild(statBits); |
| 189 |
var copyBtn = document.createElement('button'); copyBtn.className = 'bkbg-btc-copy-btn'; |
| 190 |
copyBtn.textContent = '📋 Copy'; copyBtn.style.marginLeft = 'auto'; |
| 191 |
copyBtn.addEventListener('click', function () { |
| 192 |
var text = outBox.textContent; |
| 193 |
if (!text || outBox.classList.contains('bkbg-btc-output-empty')) return; |
| 194 |
navigator.clipboard.writeText(text).then(function () { |
| 195 |
copyBtn.textContent = '� |
| 196 |
Copied!'; setTimeout(function () { copyBtn.textContent = '📋 Copy'; }, 1500); |
| 197 |
}).catch(function () {}); |
| 198 |
}); |
| 199 |
statsRow.appendChild(copyBtn); |
| 200 |
|
| 201 |
var currentOutput = ''; |
| 202 |
|
| 203 |
function updateLabels() { |
| 204 |
var c = tabColors[activeTab] || accent; |
| 205 |
cvtBtn.style.background = c; |
| 206 |
if (activeMode === 'encode') { |
| 207 |
inpLbl.textContent = 'Plain Text Input'; |
| 208 |
outLbl.textContent = activeTab.charAt(0).toUpperCase() + activeTab.slice(1) + ' Output'; |
| 209 |
cvtBtn.textContent = '→ Encode to ' + (activeTab === 'hex' ? 'Hexadecimal' : activeTab.charAt(0).toUpperCase() + activeTab.slice(1)); |
| 210 |
inp.placeholder = 'Type or paste text here…'; |
| 211 |
} else { |
| 212 |
inpLbl.textContent = (activeTab === 'hex' ? 'Hexadecimal' : activeTab.charAt(0).toUpperCase() + activeTab.slice(1)) + ' Input'; |
| 213 |
outLbl.textContent = 'Decoded Text'; |
| 214 |
cvtBtn.textContent = '← Decode from ' + (activeTab === 'hex' ? 'Hexadecimal' : activeTab.charAt(0).toUpperCase() + activeTab.slice(1)); |
| 215 |
var examples = { binary:'01001000 01101001', hex:'48 69', octal:'110 151' }; |
| 216 |
inp.placeholder = 'e.g. ' + (examples[activeTab] || ''); |
| 217 |
} |
| 218 |
} |
| 219 |
updateLabels(); |
| 220 |
|
| 221 |
function clearIO() { |
| 222 |
inp.value = ''; outBox.textContent = 'Result will appear here…'; |
| 223 |
outBox.className = 'bkbg-btc-output bkbg-btc-output-empty'; |
| 224 |
errorEl.textContent = ''; statChars.textContent = ''; statBits.textContent = ''; |
| 225 |
currentOutput = ''; updateLabels(); refreshModes(); refreshTabs(); |
| 226 |
} |
| 227 |
|
| 228 |
function convert() { |
| 229 |
errorEl.textContent = ''; |
| 230 |
var val = inp.value; |
| 231 |
if (!val.trim()) { outBox.textContent = 'Result will appear here…'; outBox.className='bkbg-btc-output bkbg-btc-output-empty'; return; } |
| 232 |
try { |
| 233 |
var result; |
| 234 |
if (activeMode === 'encode') { |
| 235 |
if (activeTab === 'binary') result = textToBinary(val); |
| 236 |
else if (activeTab === 'hex') result = textToHex(val); |
| 237 |
else result = textToOctal(val); |
| 238 |
} else { |
| 239 |
if (activeTab === 'binary') result = binaryToText(val); |
| 240 |
else if (activeTab === 'hex') result = hexToText(val); |
| 241 |
else result = octalToText(val); |
| 242 |
} |
| 243 |
currentOutput = result; |
| 244 |
outBox.textContent = result; |
| 245 |
outBox.className = 'bkbg-btc-output'; |
| 246 |
outBox.style.color = activeMode === 'decode' ? '#111827' : (tabColors[activeTab] || accent); |
| 247 |
// Stats |
| 248 |
var chars = result.length; |
| 249 |
statChars.textContent = chars + ' chars'; |
| 250 |
if (activeMode === 'encode' && activeTab === 'binary') { |
| 251 |
statBits.textContent = '| ' + (val.length * 8) + ' bits'; |
| 252 |
} else { statBits.textContent = ''; } |
| 253 |
} catch (err) { |
| 254 |
errorEl.textContent = '❗ ' + err.message; |
| 255 |
outBox.textContent = 'Result will appear here…'; outBox.className='bkbg-btc-output bkbg-btc-output-empty'; |
| 256 |
} |
| 257 |
} |
| 258 |
|
| 259 |
cvtBtn.addEventListener('click', convert); |
| 260 |
inp.addEventListener('keydown', function (e) { |
| 261 |
if (e.key === 'Enter' && e.ctrlKey) { e.preventDefault(); convert(); } |
| 262 |
}); |
| 263 |
|
| 264 |
// ASCII Table |
| 265 |
if (opts.showAsciiTable !== false) { |
| 266 |
var divider = document.createElement('hr'); divider.className = 'bkbg-btc-divider'; card.appendChild(divider); |
| 267 |
var ascTitle = document.createElement('div'); ascTitle.className = 'bkbg-btc-ascii-title'; ascTitle.style.color = asciiClr; |
| 268 |
ascTitle.textContent = 'ASCII Reference Table'; card.appendChild(ascTitle); |
| 269 |
|
| 270 |
var asciiFilter = 'printable'; |
| 271 |
var filtersRow = document.createElement('div'); filtersRow.className = 'bkbg-btc-ascii-filters'; card.appendChild(filtersRow); |
| 272 |
var fBtns = {}; |
| 273 |
[{ id: 'printable', label: 'Printable (32–126)' }, { id: 'all', label: 'All (0–127)' }, { id: 'control', label: 'Control (0–31)' }].forEach(function (f) { |
| 274 |
var b = document.createElement('button'); b.className = 'bkbg-btc-ascii-filter-btn'; b.textContent = f.label; |
| 275 |
b.addEventListener('click', function () { asciiFilter = f.id; refreshFilters(); renderAscii(); }); |
| 276 |
fBtns[f.id] = b; filtersRow.appendChild(b); |
| 277 |
}); |
| 278 |
function refreshFilters() { |
| 279 |
Object.keys(fBtns).forEach(function (k) { |
| 280 |
var b = fBtns[k]; var active = k === asciiFilter; |
| 281 |
b.style.background = active ? asciiClr : 'transparent'; |
| 282 |
b.style.color = active ? '#fff' : '#6b7280'; |
| 283 |
b.style.borderColor = active ? asciiClr : '#d1d5db'; |
| 284 |
}); |
| 285 |
} |
| 286 |
refreshFilters(); |
| 287 |
|
| 288 |
var tableWrap = document.createElement('div'); tableWrap.style.overflowX = 'auto'; card.appendChild(tableWrap); |
| 289 |
var table = document.createElement('table'); table.className = 'bkbg-btc-ascii-table'; tableWrap.appendChild(table); |
| 290 |
var thead = document.createElement('thead'); table.appendChild(thead); |
| 291 |
var theadRow = document.createElement('tr'); thead.appendChild(theadRow); |
| 292 |
['Dec','Hex','Oct','Bin','Char'].forEach(function (h) { |
| 293 |
var th = document.createElement('th'); th.textContent = h; theadRow.appendChild(th); |
| 294 |
}); |
| 295 |
var tbody = document.createElement('tbody'); table.appendChild(tbody); |
| 296 |
|
| 297 |
function renderAscii() { |
| 298 |
tbody.innerHTML = ''; |
| 299 |
var start = asciiFilter === 'control' ? 0 : 32; |
| 300 |
var end = asciiFilter === 'control' ? 32 : (asciiFilter === 'all' ? 128 : 127); |
| 301 |
for (var i = start; i < end; i++) { |
| 302 |
var isControl = i < 32 || i === 127; |
| 303 |
var row = document.createElement('tr'); |
| 304 |
var ch = asciiChar(i); |
| 305 |
var isSpecial = isControl || i === 32; |
| 306 |
var data = [ |
| 307 |
{ text: i.toString(), cls: '' }, |
| 308 |
{ text: i.toString(16).toUpperCase().padStart(2,'0'), cls: 'bkbg-btc-stat', style: 'color:'+hexColor }, |
| 309 |
{ text: i.toString(8).padStart(3,'0'), cls: 'bkbg-btc-stat', style: 'color:'+octColor }, |
| 310 |
{ text: i.toString(2).padStart(8,'0'), cls: 'bkbg-btc-ascii-dim' }, |
| 311 |
{ text: ch, cls: isSpecial ? 'bkbg-btc-ascii-dim' : 'bkbg-btc-ascii-char', style: isSpecial ? '' : 'color:'+asciiClr } |
| 312 |
]; |
| 313 |
data.forEach(function (d) { |
| 314 |
var td = document.createElement('td'); td.textContent = d.text; td.className = d.cls; |
| 315 |
if (d.style) td.style.cssText = d.style; row.appendChild(td); |
| 316 |
}); |
| 317 |
// Click to insert char into input |
| 318 |
if (!isControl) { |
| 319 |
row.style.cursor = 'pointer'; |
| 320 |
row.title = 'Click to insert "' + ch + '" into input'; |
| 321 |
row.addEventListener('click', function (ch) { return function () { inp.value += ch; }; }(ch)); |
| 322 |
} |
| 323 |
tbody.appendChild(row); |
| 324 |
} |
| 325 |
} |
| 326 |
renderAscii(); |
| 327 |
} |
| 328 |
} |
| 329 |
|
| 330 |
document.querySelectorAll('.bkbg-btc-app').forEach(initApp); |
| 331 |
})(); |
| 332 |
|