Created
September 2, 2025 01:07
-
-
Save maksadbek/811fe0670000e04bae13e73513cd8e19 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!doctype html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>JSON Formatter • Split View</title> | |
| <style> | |
| :root { --left: 50%; --bg: #0f172a; --panel: #111827; --panel-2: #0b1220; --text: #e5e7eb; --muted: #94a3b8; --accent: #38bdf8; } | |
| * { box-sizing: border-box; } | |
| html, body { height: 100%; } | |
| body { margin: 0; display: flex; flex-direction: column; height: 100vh; background: linear-gradient(180deg, var(--bg), var(--panel-2)); color: var(--text); font: 14px/1.5 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } | |
| header { display: flex; align-items: center; gap: 12px; padding: 12px 16px; background: rgba(255,255,255,0.03); border-bottom: 1px solid rgba(148,163,184,0.15); } | |
| header .dot { width: 10px; height: 10px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 12px var(--accent); } | |
| header h1 { font-size: 14px; margin: 0; font-weight: 600; } | |
| header .spacer { flex: 1; } | |
| .panes { flex: 1; display: flex; min-height: 0; } | |
| .pane { min-width: 180px; min-height: 0; display: flex; flex-direction: column; background: linear-gradient(180deg, var(--panel), var(--panel-2)); } | |
| .pane.left { flex-basis: var(--left); } | |
| .pane.right { flex: 1; border-left: 1px solid rgba(148,163,184,0.15); } | |
| .titlebar { display: flex; align-items: center; gap: 8px; padding: 10px 12px; background: rgba(255,255,255,0.02); border-bottom: 1px solid rgba(148,163,184,0.1); color: var(--muted); } | |
| .editor, .viewer { flex: 1; min-height: 0; position: relative; } | |
| textarea { width: 100%; height: 100%; background: transparent; color: var(--text); border: none; outline: none; resize: none; padding: 16px; caret-color: var(--accent); tab-size: 2; } | |
| #output { margin: 0; height: 100%; overflow: auto; padding: 16px; white-space: pre-wrap; } | |
| .placeholder { color: var(--muted); } | |
| .divider { width: 8px; cursor: col-resize; position: relative; background: rgba(148,163,184,0.12); border-left: 1px solid rgba(148,163,184,0.15); border-right: 1px solid rgba(148,163,184,0.15); } | |
| body.resizing { cursor: col-resize; user-select: none; } | |
| .actions { display: flex; gap: 8px; align-items: center; padding: 8px 12px; border-top: 1px solid rgba(148,163,184,0.15); background: rgba(255,255,255,0.02); } | |
| .actions button, .actions select, .actions label { background: #0b1220; color: var(--text); border: 1px solid rgba(148,163,184,0.25); padding: 6px 10px; border-radius: 8px; font-weight: 600; } | |
| .actions button { cursor: pointer; } | |
| .node { margin: 2px 0; } | |
| .header { user-select: none; display: inline-flex; align-items: center; gap: 6px; cursor: pointer; } | |
| .label { color: #93c5fd; } | |
| .bracket { color: #94a3b8; } | |
| .primitive { white-space: pre; } | |
| .children { margin-left: 18px; border-left: 1px dashed rgba(148,163,184,0.2); padding-left: 10px; } | |
| .meta { color: #94a3b8; margin-left: 4px; font-size: 12px; } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <span class="dot"></span> | |
| <h1>JSON Formatter • Split View</h1> | |
| <div class="spacer"></div> | |
| </header> | |
| <div class="panes" id="panes"> | |
| <section class="pane left"> | |
| <div class="titlebar">Input</div> | |
| <div class="editor"><textarea id="input" placeholder='Paste JSON here…'></textarea></div> | |
| <div class="actions"> | |
| <button id="formatBtn">Format</button> | |
| <label><input type="checkbox" id="liveToggle" checked /> Live</label> | |
| <select id="indentSel"><option value="2" selected>2 spaces</option><option value="4">4 spaces</option><option value="\t">Tabs</option></select> | |
| </div> | |
| </section> | |
| <div id="divider" class="divider"></div> | |
| <section class="pane right"> | |
| <div class="titlebar">Formatted</div> | |
| <div class="viewer"><div id="output" class="placeholder">Formatted JSON will appear here…</div></div> | |
| </section> | |
| </div> | |
| <footer>Tip: Paste JSON on the left. Drag divider. Press Ctrl/⌘+Enter.</footer> | |
| <script> | |
| const input = document.getElementById('input'); | |
| const output = document.getElementById('output'); | |
| const panes = document.getElementById('panes'); | |
| const divider = document.getElementById('divider'); | |
| const formatBtn = document.getElementById('formatBtn'); | |
| const liveToggle = document.getElementById('liveToggle'); | |
| const indentSel = document.getElementById('indentSel'); | |
| // Resizing | |
| let dragging=false; function setLeft(x){const r=panes.getBoundingClientRect();let l=((x-r.left)/r.width)*100;l=Math.min(85,Math.max(15,l));panes.style.setProperty('--left',l+'%');} | |
| divider.addEventListener('mousedown',e=>{dragging=true;document.body.classList.add('resizing');e.preventDefault();}); | |
| window.addEventListener('mousemove',e=>{if(dragging) setLeft(e.clientX)}); | |
| window.addEventListener('mouseup',()=>{dragging=false;document.body.classList.remove('resizing');}); | |
| // Utilities | |
| const isObj = (v) => v !== null && typeof v === 'object'; | |
| const isArr = Array.isArray; | |
| const jsonStr = (v) => JSON.stringify(v); | |
| function renderNode(value, label = null) { | |
| if (isObj(value)) { | |
| const node = document.createElement('div'); node.className = 'node'; | |
| const header = document.createElement('div'); header.className = 'header'; | |
| const name = document.createElement('span'); name.className = 'label'; | |
| const open = document.createElement('span'); open.className = 'bracket'; | |
| const close = document.createElement('span'); close.className = 'bracket'; | |
| const children = document.createElement('div'); children.className = 'children'; | |
| const meta = document.createElement('span'); meta.className = 'meta'; | |
| const array = isArr(value); | |
| const keys = array ? value.map((_, i) => String(i)) : Object.keys(value); | |
| name.textContent = label !== null ? (array ? label + ': ' : label + ': ') : ''; | |
| open.textContent = array ? '[' : '{'; | |
| close.textContent = array ? ']' : '}'; | |
| meta.textContent = array ? ` (${value.length} items)` : ` (${keys.length} ${keys.length===1?'key':'keys'})`; | |
| header.append(name, open, meta); | |
| node.append(header, children, close); | |
| keys.forEach((k, i) => { | |
| const line = document.createElement('div'); line.className = 'node'; | |
| const childVal = array ? value[i] : value[k]; | |
| if (isObj(childVal)) { | |
| line.append(renderNode(childVal, array ? k : '"'+k+'"')); | |
| } else { | |
| const leaf = document.createElement('div'); leaf.className = 'header'; | |
| const kspan = document.createElement('span'); kspan.className = 'label'; kspan.textContent = array ? k+': ' : '"'+k+'": '; | |
| const vspan = document.createElement('span'); vspan.className = 'primitive'; vspan.textContent = jsonStr(childVal); | |
| leaf.append(document.createTextNode(' '), kspan, vspan); | |
| line.append(leaf); | |
| } | |
| children.append(line); | |
| }); | |
| header.addEventListener('click', () => { | |
| const hidden = children.style.display === 'none'; | |
| children.style.display = hidden ? '' : 'none'; | |
| }); | |
| return node; | |
| } | |
| const root = document.createElement('div'); root.className = 'node'; | |
| const line = document.createElement('div'); line.className = 'header'; | |
| if (label !== null) { const kspan = document.createElement('span'); kspan.className = 'label'; kspan.textContent = label+': '; line.append(kspan); } | |
| const vspan = document.createElement('span'); vspan.className = 'primitive'; vspan.textContent = jsonStr(value); | |
| line.append(vspan); root.append(line); return root; | |
| } | |
| function formatNow(){ | |
| const raw=input.value.trim(); | |
| if(!raw){output.textContent='Formatted JSON will appear here…';output.className='placeholder';return;} | |
| try{ const data=JSON.parse(raw); output.innerHTML=''; output.appendChild(renderNode(data, null)); output.className=''; } | |
| catch(err){ | |
| // On invalid JSON: show raw input with newlines rendered | |
| output.className=''; | |
| output.textContent = input.value.replace(/\\n/g, '\n'); | |
| } | |
| } | |
| input.addEventListener('input',()=>{ if(liveToggle.checked) formatNow(); }); | |
| input.addEventListener('paste',()=>{ setTimeout(()=>{ if(liveToggle.checked) formatNow(); }, 0); }); | |
| formatBtn.addEventListener('click',formatNow); | |
| window.addEventListener('keydown',e=>{ if((e.ctrlKey||e.metaKey)&&e.key==='Enter'){ e.preventDefault(); formatNow(); } }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment