Hello
\n\n', colors: 'Color Cycle
\n\n' ); doc.close(); setTimeout(function () { var status = document.getElementById('status'); if (lastErrors.length > 0) { status.innerHTML = '' + lastErrors.length + ' error(s)'; } else { status.innerHTML = 'Updated (' + new Date().toLocaleTimeString() + ')'; } }, 200); } window._playgroundError = function (msg) { lastErrors.push(msg); consoleAppend('error', msg); }; window._playgroundConsole = function (level, msg) { consoleAppend(level, msg); }; // ============================================================ // Console // ============================================================ var consoleOutput = document.getElementById('console-output'); var consoleInput = document.getElementById('console-input'); var consoleHistory = []; var historyIndex = -1; var replLocals = {}; var BLOCK_OPENERS = /(? closes; } function consoleAppend(cls, text) { var el = document.createElement('div'); el.className = cls; if (cls === 'separator') { el.innerHTML = ' '; } else { el.textContent = text; } consoleOutput.appendChild(el); consoleOutput.scrollTop = consoleOutput.scrollHeight; } function consoleEval(input) { var lines = input.split('\n'); consoleAppend('log', '> ' + lines[0]); for (var i = 1; i < lines.length; i++) { consoleAppend('log', '... ' + lines[i]); } var iframe = document.getElementById('preview'); var hs = iframe.contentWindow && iframe.contentWindow._hyperscript; if (!hs) { consoleAppend('error', 'No preview running - press Run first'); return; } try { var result = hs(input, { locals: replLocals }); if (result !== undefined) { var display; try { display = typeof result === 'object' ? JSON.stringify(result) : String(result); } catch (e) { display = String(result); } consoleAppend('result', display); } } catch (e) { consoleAppend('error', e.message ? e.message.split('\n')[0] : String(e)); } } function autoSizeInput() { consoleInput.style.height = '18px'; consoleInput.style.height = consoleInput.scrollHeight + 'px'; } consoleInput.addEventListener('input', autoSizeInput); consoleInput.addEventListener('keydown', function (e) { if (e.key === 'Enter') { var src = this.value.trim(); if (!src) return; if (needsMoreInput(src) && !e.shiftKey) { // Block incomplete - insert newline naturally return; } if (!needsMoreInput(src)) { // Complete - submit e.preventDefault(); var input = this.value; this.value = ''; autoSizeInput(); consoleHistory.push(input); historyIndex = consoleHistory.length; consoleEval(input); } // Shift+Enter always inserts newline (default behavior) } else if (e.key === 'ArrowUp' && this.selectionStart === 0 && !this.value.includes('\n')) { e.preventDefault(); if (historyIndex > 0) { historyIndex--; this.value = consoleHistory[historyIndex]; autoSizeInput(); } } else if (e.key === 'ArrowDown' && this.selectionStart === this.value.length && !this.value.includes('\n')) { e.preventDefault(); if (historyIndex < consoleHistory.length - 1) { historyIndex++; this.value = consoleHistory[historyIndex]; autoSizeInput(); } else { historyIndex = consoleHistory.length; this.value = ''; autoSizeInput(); } } }); // ============================================================ // Controls // ============================================================ document.getElementById('run-btn').addEventListener('click', run); document.getElementById('clear-btn').addEventListener('click', function () { cm.setValue(''); var doc = document.getElementById('preview').contentDocument; doc.open(); doc.write('
'); doc.close(); consoleOutput.innerHTML = ''; document.getElementById('status').textContent = 'Cleared'; }); document.getElementById('share-btn').addEventListener('click', function () { var btn = this; var compressed = LZString.compressToEncodedURIComponent(cm.getValue()); var url = window.location.origin + window.location.pathname + '?src=' + compressed; navigator.clipboard.writeText(url).then(function () { showToast('Share URL copied!', btn); }); }); function showToast(msg, anchor) { var existing = document.querySelector('.toast'); if (existing) existing.remove(); anchor.style.position = 'relative'; var toast = document.createElement('div'); toast.className = 'toast'; toast.textContent = msg; anchor.appendChild(toast); requestAnimationFrame(function () { toast.classList.add('show'); }); setTimeout(function () { toast.classList.remove('show'); toast.addEventListener('transitionend', function () { toast.remove(); }); }, 1500); } document.getElementById('examples').addEventListener('change', function () { var val = this.value; if (!val) return; this.value = ''; if (BUILTIN_EXAMPLES[val]) { cm.setValue(BUILTIN_EXAMPLES[val]); run(); return; } // Fetch from /playground/ directory fetch('/playground/' + val) .then(function (r) { return r.text(); }) .then(function (text) { cm.setValue(text); run(); }) .catch(function () { document.getElementById('status').innerHTML = 'Failed to load example'; }); }); // ============================================================ // Resizable panes // ============================================================ (function () { var resizeH = document.getElementById('resize-h'); var resizeV = document.getElementById('resize-v'); var panes = document.querySelector('.playground-panes'); var editor = document.querySelector('.pane-editor'); var preview = document.querySelector('.pane-preview'); var previewFrame = document.getElementById('preview'); var consolePane = document.querySelector('.pane-console'); function startDrag(handle, onMove, onEnd) { handle.classList.add('dragging'); // Cover iframe to prevent it from eating pointer events previewFrame.style.pointerEvents = 'none'; document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', function up() { handle.classList.remove('dragging'); previewFrame.style.pointerEvents = ''; document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', up); if (onEnd) onEnd(); }); } // Horizontal: editor | preview resizeH.addEventListener('mousedown', function (e) { e.preventDefault(); var panesRect = panes.getBoundingClientRect(); startDrag(resizeH, function (e) { var x = e.clientX - panesRect.left; var pct = (x / panesRect.width) * 100; pct = Math.max(15, Math.min(85, pct)); editor.style.flex = 'none'; editor.style.width = pct + '%'; preview.style.flex = '1'; }, function () { cm.refresh(); }); }); // Vertical: preview iframe | console resizeV.addEventListener('mousedown', function (e) { e.preventDefault(); var previewRect = preview.getBoundingClientRect(); startDrag(resizeV, function (e) { var y = e.clientY - previewRect.top; // Subtract the pane-label height (~24px) var available = previewRect.height - 24; var framePct = ((y - 24) / available) * 100; framePct = Math.max(10, Math.min(90, framePct)); previewFrame.style.flex = 'none'; previewFrame.style.height = framePct + '%'; consolePane.style.flex = '1'; }); }); })(); // Initial run setTimeout(run, 100);