body
reasonably
A Thinking Notebook
Untitled · Begin below ▾
v1 · saved
Mood at writing ·
0 chapters · 0 sentences
Contents
Write the thought as it arrives… c
Node Canvas
Syntax preview…
Edit · body
c / click barQuick capture
Ctrl+Z / Ctrl+YUndo / Redo
↑ ↓ / j kNavigate
1–9Jump to chapter
rReading mode
tToggle contents
Double-click sentenceEdit in place
Right-click pageStep mode
s / hover headerSpine wayfinding
?This help
// ─── NODE GHOST OVERLAY ──────────────────────────────────────────────────── (function() { const overlay = document.getElementById('node-ghost-overlay'); const ngoTA = document.getElementById('ngo-textarea'); const ngoPill = document.getElementById('ngo-type-pill'); const TYPE_PLACEHOLDERS = { poster: 'The central declaration.', body: 'The thought, plainly stated.', pull: 'The truest sentence.', label: 'Category · Date', spec: 'key :: value', chapter: 'Chapter title', draft: 'Raw capture…' }; const TYPE_LABELS = { poster:'poster', body:'body', pull:'pull', label:'label', spec:'spec', chapter:'chapter', draft:'draft' }; let _ngoSourceInput = null; let _ngoNodeType = 'body'; let _ngoOpen = false; let _ngoBlurTimer = null; function openNodeGhost(inp, nodeType) { _ngoSourceInput = inp; _ngoNodeType = nodeType || 'body'; _ngoOpen = true; overlay.className = 'ngo-open ngo-type-' + _ngoNodeType; ngoPill.textContent = TYPE_LABELS[_ngoNodeType] || _ngoNodeType; ngoTA.placeholder = TYPE_PLACEHOLDERS[_ngoNodeType] || 'Write…'; ngoTA.value = inp.value; ngoTA.style.height = 'auto'; ngoTA.style.height = Math.min(ngoTA.scrollHeight, window.innerHeight * 0.55) + 'px'; // Suppress capture UI while ghost is active document.body.classList.add('ngo-active'); if (typeof closeCapture === 'function' && captureOpen) closeCapture(); // Update submit button label const submitBtn = document.getElementById('ngo-submit'); if (submitBtn) { const multiLine = ['body','poster','pull','draft'].includes(_ngoNodeType); submitBtn.textContent = multiLine ? 'Commit' : 'Set'; } requestAnimationFrame(() => { ngoTA.focus(); ngoTA.setSelectionRange(ngoTA.value.length, ngoTA.value.length); }); } function closeNodeGhost(refocusSource) { if (!_ngoOpen) return; _ngoOpen = false; overlay.classList.remove('ngo-open'); document.body.classList.remove('ngo-active'); if (refocusSource && _ngoSourceInput) { _ngoSourceInput.focus(); _ngoSourceInput.setSelectionRange(_ngoSourceInput.value.length, _ngoSourceInput.value.length); } _ngoSourceInput = null; } // ngo-submit button — commit and close window.ngoCommit = function() { if (!_ngoOpen || !_ngoSourceInput) { closeNodeGhost(false); return; } _ngoSourceInput.value = ngoTA.value; _ngoSourceInput.dispatchEvent(new Event('input', { bubbles: true })); closeNodeGhost(true); }; ngoTA.addEventListener('input', () => { if (!_ngoSourceInput) return; _ngoSourceInput.value = ngoTA.value; _ngoSourceInput.dispatchEvent(new Event('input', { bubbles: true })); ngoTA.style.height = 'auto'; ngoTA.style.height = Math.min(ngoTA.scrollHeight, window.innerHeight * 0.55) + 'px'; }); ngoTA.addEventListener('keydown', e => { if (e.key === 'Escape') { e.preventDefault(); closeNodeGhost(false); } const multiLine = ['body','poster','pull','draft'].includes(_ngoNodeType); if (e.key === 'Enter' && !e.shiftKey && !multiLine) { e.preventDefault(); closeNodeGhost(false); } }); document.getElementById('ngo-backdrop').addEventListener('mousedown', () => { closeNodeGhost(false); }); const canvasEl = document.getElementById('node-canvas'); if (canvasEl) { canvasEl.addEventListener('focusin', e => { if (window.innerWidth <= 720) return; const inp = e.target; if (!inp || inp.tagName !== 'INPUT') return; clearTimeout(_ngoBlurTimer); const card = inp.closest('.n-card'); if (!card) return; const badge = card.querySelector('.n-badge'); let type = badge ? badge.textContent.trim() : 'body'; if (type === 'ch') type = 'chapter'; if (type === '?') type = 'draft'; if (inp.classList.contains('n-spec-k')) type = 'spec'; openNodeGhost(inp, type); }); canvasEl.addEventListener('focusout', () => { _ngoBlurTimer = setTimeout(() => { if (!_ngoOpen) return; const active = document.activeElement; if (active && overlay.contains(active)) return; closeNodeGhost(false); }, 120); }); } overlay.addEventListener('focusin', () => clearTimeout(_ngoBlurTimer)); window.openNodeGhost = openNodeGhost; window.closeNodeGhost = closeNodeGhost; // Close ghost if viewport shrinks to mobile window.addEventListener('resize', () => { if (window.innerWidth <= 720 && _ngoOpen) closeNodeGhost(false); }); })();