(function () {
'use strict';
/* ---- Syntax highlighter ---- */
function syntaxHighlight(json, colors) {
var keyC = colors.key || '#0ea5e9';
var strC = colors.string || '#16a34a';
var numC = colors.number || '#f59e0b';
var boolC = colors.boolNull || '#ec4899';
var punctC = colors.punct || '#9ca3af';
var textC = colors.text || '#cdd6f4';
/* Tokenise */
var html = '';
var regex = /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?|[{}\[\],:])/g;
json.replace(//g, '>').replace(regex, function (match, offset, fullStr) {
return match; /* replaced below */
});
var escaped = json.replace(/&/g, '&').replace(//g, '>');
html = escaped.replace(regex, function (match) {
var color = textC;
var isPunct = /^[{}\[\],:]$/.test(match);
var isBool = /^(true|false|null)$/.test(match);
var isNum = !isNaN(match) && match.trim() !== '';
var isKey = /^"/.test(match) && /:$/.test(match.trimEnd());
var isStr = /^"/.test(match) && !isKey;
if (isPunct) color = punctC;
else if (isBool) color = boolC;
else if (isNum) color = numC;
else if (isKey) color = keyC;
else if (isStr) color = strC;
return '' + match + '';
});
return html;
}
/* ---- Stats ---- */
function countKeys(obj, depth) {
depth = depth || 0;
if (typeof obj !== 'object' || obj === null) return { keys: 0, depth: depth };
var keys = 0; var maxD = depth;
var ks = Array.isArray(obj) ? obj : Object.values(obj);
ks.forEach(function (v) {
if (typeof v === 'object' && v !== null) {
var sub = countKeys(v, depth + 1);
keys += sub.keys;
if (sub.depth > maxD) maxD = sub.depth;
}
});
if (!Array.isArray(obj)) keys += Object.keys(obj).length;
return { keys: keys, depth: maxD };
}
/* ---- DOM ---- */
function typoCssVarsForEl(typo, prefix, el) {
if (!typo || typeof typo !== 'object') return;
var map = {
family:'font-family', weight:'font-weight', style:'font-style',
decoration:'text-decoration', transform:'text-transform',
sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m',
lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m',
letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m',
wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m'
};
Object.keys(map).forEach(function(k) {
if (typo[k] !== undefined && typo[k] !== '') {
var v = typo[k];
if (['sizeDesktop','sizeTablet','sizeMobile'].indexOf(k) !== -1) v = v + (typo.sizeUnit || 'px');
else if (['lineHeightDesktop','lineHeightTablet','lineHeightMobile'].indexOf(k) !== -1) v = v + (typo.lineHeightUnit || '');
else if (['letterSpacingDesktop','letterSpacingTablet','letterSpacingMobile'].indexOf(k) !== -1) v = v + (typo.letterSpacingUnit || 'px');
else if (['wordSpacingDesktop','wordSpacingTablet','wordSpacingMobile'].indexOf(k) !== -1) v = v + (typo.wordSpacingUnit || 'px');
el.style.setProperty(prefix + map[k], String(v));
}
});
}
function initApp(app) {
var opts;
try { opts = JSON.parse(app.getAttribute('data-opts') || '{}'); } catch (e) { return; }
var accent = opts.accentColor || '#0ea5e9';
var editorBg = opts.editorBg || '#1e1e2e';
var editorText = opts.editorText || '#cdd6f4';
var lineNumBg = opts.lineNumBg || '#181825';
var lineNumClr = opts.lineNumColor || '#6c7086';
var errorBg = opts.errorBg || '#fee2e2';
var errorClr = opts.errorColor || '#dc2626';
var successClr = opts.successColor || '#16a34a';
var cardR = (opts.cardRadius || 16) + 'px';
var edR = (opts.editorRadius || 10) + 'px';
var maxW = (opts.maxWidth || 800) + 'px';
var fs = (opts.editorFontSize|| 13) + 'px';
var edH = (opts.editorHeight || 320) + 'px';
var indent = opts.indentSize !== undefined ? opts.indentSize : 2;
app.style.paddingTop = (opts.paddingTop || 60) + 'px';
app.style.paddingBottom = (opts.paddingBottom || 60) + 'px';
if (opts.sectionBg) app.style.background = opts.sectionBg;
typoCssVarsForEl(opts.titleTypo, '--bkbg-jf-tt-', app);
var card = document.createElement('div');
card.className = 'bkbg-jf-card';
Object.assign(card.style, { background: opts.cardBg || '#fff', borderRadius: cardR, maxWidth: maxW });
app.appendChild(card);
if (opts.showTitle && opts.title) {
var ttl = document.createElement('div'); ttl.className = 'bkbg-jf-title';
ttl.textContent = opts.title;
if (opts.titleColor) ttl.style.color = opts.titleColor;
card.appendChild(ttl);
}
if (opts.showSubtitle && opts.subtitle) {
var sub = document.createElement('div'); sub.className = 'bkbg-jf-subtitle';
sub.textContent = opts.subtitle;
if (opts.subtitleColor) sub.style.color = opts.subtitleColor;
card.appendChild(sub);
}
/* Toolbar */
var toolbar = document.createElement('div'); toolbar.className = 'bkbg-jf-toolbar'; card.appendChild(toolbar);
function mkBtn(label, primary, onClick) {
var b = document.createElement('button'); b.className = 'bkbg-jf-btn';
b.textContent = label;
if (primary) { b.style.background = accent; b.style.color = '#fff'; }
else { b.classList.add('bkbg-jf-btn-secondary'); }
b.addEventListener('click', onClick);
return b;
}
var statusEl = document.createElement('div'); statusEl.className = 'bkbg-jf-status';
statusEl.style.marginLeft = 'auto';
function setStatus(ok, msg) {
statusEl.textContent = (ok ? '✓ ' : '✗ ') + msg;
statusEl.style.background = ok ? successClr + '18' : errorBg;
statusEl.style.color = ok ? successClr : errorClr;
}
setStatus(true, 'Ready');
/* Split: input | output */
var split = document.createElement('div'); split.className = 'bkbg-jf-split'; card.appendChild(split);
function mkEditorPane(label) {
var wrap = document.createElement('div');
var lbl = document.createElement('div'); lbl.className = 'bkbg-jf-pane-label'; lbl.textContent = label;
var ewrap = document.createElement('div'); ewrap.className = 'bkbg-jf-editor-wrap';
ewrap.style.background = editorBg; ewrap.style.borderRadius = edR; ewrap.style.height = edH;
var inner = document.createElement('div'); inner.className = 'bkbg-jf-editor-inner'; inner.style.height = edH;
wrap.appendChild(lbl); ewrap.appendChild(inner); wrap.appendChild(ewrap);
return { el: wrap, inner: inner };
}
/* Input pane */
var inPane = mkEditorPane('Input JSON');
var lnWrapIn = document.createElement('div'); lnWrapIn.className = 'bkbg-jf-line-nums';
lnWrapIn.style.background = lineNumBg; lnWrapIn.style.color = lineNumClr; lnWrapIn.style.fontSize = fs;
var taIn = document.createElement('textarea'); taIn.className = 'bkbg-jf-textarea';
taIn.value = opts.defaultJson || '{}';
taIn.style.color = editorText; taIn.style.fontSize = fs;
taIn.spellcheck = false; taIn.autocorrect = 'off'; taIn.autocapitalize = 'off';
inPane.inner.appendChild(lnWrapIn); inPane.inner.appendChild(taIn);
/* Output pane */
var outPane = mkEditorPane('Formatted Output');
var lnWrapOut = document.createElement('div'); lnWrapOut.className = 'bkbg-jf-line-nums';
lnWrapOut.style.background = lineNumBg; lnWrapOut.style.color = lineNumClr; lnWrapOut.style.fontSize = fs;
var outDiv = document.createElement('div'); outDiv.className = 'bkbg-jf-output';
outDiv.style.color = editorText; outDiv.style.fontSize = fs; outDiv.style.background = editorBg; outDiv.style.borderRadius = edR; outDiv.style.height = edH;
if (opts.showLineNums !== false) { outPane.inner.appendChild(lnWrapOut); }
outPane.inner.appendChild(outDiv);
split.appendChild(inPane.el);
split.appendChild(outPane.el);
/* Error bar */
var errorBar = document.createElement('div'); errorBar.className = 'bkbg-jf-error-bar';
errorBar.style.background = errorBg; errorBar.style.color = errorClr; errorBar.style.display = 'none';
card.appendChild(errorBar);
/* Stats bar */
var statsBar = document.createElement('div'); statsBar.className = 'bkbg-jf-stats-bar';
if (opts.showStats !== false) card.appendChild(statsBar);
function updateLineNums(text, wrap) {
if (opts.showLineNums === false) return;
var lines = text.split('\n').length;
var html = '';
for (var i = 1; i <= lines; i++) html += i + '\n';
wrap.textContent = html;
}
function updateStats(text, parsed) {
statsBar.innerHTML = '';
var size = new Blob([text]).size;
var lines = text.split('\n').length;
var sizeStr = size < 1024 ? size + ' B' : (size / 1024).toFixed(1) + ' KB';
var stats = [{ label: 'Size', val: sizeStr }, { label: 'Lines', val: lines }];
if (parsed) {
var info = countKeys(parsed, 0);
stats.push({ label: 'Keys', val: info.keys });
stats.push({ label: 'Depth', val: info.depth });
}
stats.forEach(function (s) {
var d = document.createElement('div'); d.className = 'bkbg-jf-stat-item';
d.innerHTML = s.label + ': ' + s.val + '';
statsBar.appendChild(d);
});
}
function showError(msg) {
errorBar.style.display = 'flex'; errorBar.innerHTML = '⚠️ ' + msg;
}
function hideError() { errorBar.style.display = 'none'; }
var currentOutput = '';
function format() {
try {
var parsed = JSON.parse(taIn.value);
var pretty = JSON.stringify(parsed, null, indent);
currentOutput = pretty;
outDiv.innerHTML = syntaxHighlight(pretty, {
key: opts.keyColor,
string: opts.stringColor,
number: opts.numberColor,
boolNull: opts.boolNullColor,
punct: opts.punctColor,
text: editorText
});
updateLineNums(pretty, lnWrapOut);
updateStats(pretty, parsed);
hideError(); setStatus(true, 'Valid JSON');
lnWrapOut.scrollTop = 0; outDiv.scrollTop = 0;
} catch (e) {
showError(e.message); setStatus(false, 'Invalid JSON');
outDiv.innerHTML = ''; lnWrapOut.textContent = ''; statsBar.innerHTML = '';
}
}
function minify() {
try {
var parsed = JSON.parse(taIn.value);
var min = JSON.stringify(parsed);
currentOutput = min;
outDiv.innerHTML = syntaxHighlight(min, { key: opts.keyColor, string: opts.stringColor, number: opts.numberColor, boolNull: opts.boolNullColor, punct: opts.punctColor, text: editorText });
updateLineNums(min, lnWrapOut);
updateStats(min, parsed);
hideError(); setStatus(true, 'Minified');
} catch (e) {
showError(e.message); setStatus(false, 'Invalid JSON');
}
}
function validate() {
try {
JSON.parse(taIn.value);
hideError(); setStatus(true, 'Valid JSON ✓');
} catch (e) {
showError(e.message); setStatus(false, 'Invalid JSON');
}
}
var copiedEl = document.createElement('span'); copiedEl.className = 'bkbg-jf-copied'; copiedEl.textContent = 'Copied!';
var fmtBtn = mkBtn('Format', true, format);
var minBtn = mkBtn('Minify', true, minify);
var valBtn = mkBtn('Validate', false, validate);
var cpyBtn = mkBtn('📋 Copy', false, function () {
var text = currentOutput || taIn.value;
navigator.clipboard.writeText(text).then(function () {
copiedEl.style.display = 'inline-block';
setTimeout(function () { copiedEl.style.display = 'none'; }, 1500);
}).catch(function () {});
});
var clrBtn = mkBtn('Clear', false, function () {
taIn.value = ''; outDiv.innerHTML = ''; lnWrapIn.textContent = '1'; lnWrapOut.textContent = '';
statsBar.innerHTML = ''; hideError(); setStatus(true, 'Ready'); currentOutput = '';
});
[fmtBtn, minBtn, valBtn, cpyBtn, clrBtn, copiedEl, statusEl].forEach(function (b) { toolbar.appendChild(b); });
/* Live line nums on input */
taIn.addEventListener('input', function () {
updateLineNums(taIn.value, lnWrapIn);
hideError(); setStatus(true, 'Editing…');
currentOutput = '';
});
/* Sync scroll */
function syncScroll(from, to) { from.addEventListener('scroll', function () { to.scrollTop = from.scrollTop; }); }
syncScroll(taIn, lnWrapIn); syncScroll(lnWrapIn, taIn);
syncScroll(outDiv, lnWrapOut); syncScroll(lnWrapOut, outDiv);
/* Tab key support */
taIn.addEventListener('keydown', function (e) {
if (e.key === 'Tab') {
e.preventDefault();
var s = taIn.selectionStart; var end = taIn.selectionEnd;
taIn.value = taIn.value.substring(0, s) + ' ' + taIn.value.substring(end);
taIn.selectionStart = taIn.selectionEnd = s + 2;
}
if (e.key === 'Enter' && e.ctrlKey) { e.preventDefault(); format(); }
});
/* Initial */
updateLineNums(taIn.value, lnWrapIn);
format();
}
document.querySelectorAll('.bkbg-jf-app').forEach(initApp);
})();