Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>CodeFlow Studio - Live Editor</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600&family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet"> | |
| <style> | |
| /* Custom Scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: #1e1e1e; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: #4b5563; | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: #6b7280; | |
| } | |
| /* Editor Styles */ | |
| .editor-container { | |
| position: relative; | |
| height: 100%; | |
| width: 100%; | |
| overflow: hidden; | |
| font-family: 'Fira Code', monospace; | |
| } | |
| .line-numbers { | |
| position: absolute; | |
| left: 0; | |
| top: 0; | |
| bottom: 0; | |
| width: 3rem; | |
| background-color: #1e1e1e; | |
| color: #6b7280; | |
| text-align: right; | |
| padding: 1rem 0.5rem; | |
| font-size: 0.875rem; | |
| line-height: 1.5rem; | |
| user-select: none; | |
| border-right: 1px solid #374151; | |
| z-index: 10; | |
| } | |
| textarea.code-input { | |
| position: absolute; | |
| left: 3rem; | |
| top: 0; | |
| width: calc(100% - 3rem); | |
| height: 100%; | |
| background: transparent; | |
| color: #e5e7eb; | |
| border: none; | |
| resize: none; | |
| outline: none; | |
| padding: 1rem; | |
| font-size: 0.875rem; | |
| line-height: 1.5rem; | |
| white-space: pre; | |
| overflow: auto; | |
| z-index: 20; | |
| caret-color: #60a5fa; | |
| } | |
| /* Syntax Highlighting Layer (Visual Only) */ | |
| pre.code-highlight { | |
| position: absolute; | |
| left: 3rem; | |
| top: 0; | |
| width: calc(100% - 3rem); | |
| height: 100%; | |
| margin: 0; | |
| padding: 1rem; | |
| font-size: 0.875rem; | |
| line-height: 1.5rem; | |
| pointer-events: none; | |
| z-index: 15; | |
| overflow: hidden; | |
| white-space: pre; | |
| } | |
| /* Glassmorphism & Utilities */ | |
| .glass-panel { | |
| background: rgba(30, 41, 59, 0.7); | |
| backdrop-filter: blur(12px); | |
| -webkit-backdrop-filter: blur(12px); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| .animate-fade-in { | |
| animation: fadeIn 0.3s ease-out forwards; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| /* Range Slider Styling */ | |
| input[type=range] { | |
| -webkit-appearance: none; | |
| background: transparent; | |
| } | |
| input[type=range]::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| height: 16px; | |
| width: 16px; | |
| border-radius: 50%; | |
| background: #60a5fa; | |
| cursor: pointer; | |
| margin-top: -6px; | |
| } | |
| input[type=range]::-webkit-slider-runnable-track { | |
| width: 100%; | |
| height: 4px; | |
| cursor: pointer; | |
| background: #4b5563; | |
| border-radius: 2px; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-gray-100 h-screen w-screen overflow-hidden flex flex-col font-sans selection:bg-blue-500 selection:text-white"> | |
| <!-- Header --> | |
| <header class="h-14 bg-gray-800 border-b border-gray-700 flex items-center justify-between px-4 shrink-0 z-30 shadow-lg"> | |
| <div class="flex items-center gap-3"> | |
| <div class="w-8 h-8 bg-gradient-to-br from-blue-500 to-purple-600 rounded-lg flex items-center justify-center shadow-blue-500/20 shadow-lg"> | |
| <svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path></svg> | |
| </div> | |
| <h1 class="font-bold text-lg tracking-tight">Code<span class="text-blue-400">Flow</span></h1> | |
| <span class="text-xs bg-gray-700 px-2 py-0.5 rounded text-gray-400 border border-gray-600">v2.0</span> | |
| </div> | |
| <div class="flex items-center gap-4"> | |
| <!-- Built with Anycoder Link --> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="hidden md:flex items-center gap-2 text-xs font-medium text-gray-400 hover:text-blue-400 transition-colors px-3 py-1.5 rounded-full bg-gray-800 border border-gray-700 hover:border-blue-500/50 group"> | |
| <span class="w-2 h-2 rounded-full bg-green-500 animate-pulse"></span> | |
| Built with anycoder | |
| </a> | |
| <button id="settingsBtn" class="p-2 text-gray-400 hover:text-white hover:bg-gray-700 rounded-lg transition-all duration-200 group relative"> | |
| <svg class="w-5 h-5 group-hover:rotate-90 transition-transform duration-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg> | |
| </button> | |
| </div> | |
| </header> | |
| <!-- Main Workspace --> | |
| <main class="flex-1 flex overflow-hidden relative"> | |
| <!-- Left Panel: Input --> | |
| <section class="flex-1 flex flex-col min-w-[300px] border-r border-gray-700 bg-[#1e1e1e] relative group"> | |
| <!-- Toolbar --> | |
| <div class="h-10 bg-[#252526] flex items-center justify-between px-4 border-b border-gray-700 select-none"> | |
| <div class="flex items-center gap-2"> | |
| <span class="text-xs font-semibold text-blue-400 uppercase tracking-wider">Input.js</span> | |
| <span class="w-2 h-2 rounded-full bg-yellow-500" title="Unsaved changes"></span> | |
| </div> | |
| <div class="flex gap-2"> | |
| <button onclick="copyCode()" class="text-xs flex items-center gap-1 text-gray-400 hover:text-white px-2 py-1 rounded hover:bg-gray-700 transition-colors" title="Copy to Clipboard"> | |
| <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg> | |
| Copy | |
| </button> | |
| <button onclick="downloadCode()" class="text-xs flex items-center gap-1 text-gray-400 hover:text-white px-2 py-1 rounded hover:bg-gray-700 transition-colors" title="Download .txt"> | |
| <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg> | |
| Save | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Editor Area --> | |
| <div class="relative flex-1 overflow-hidden"> | |
| <div class="editor-container"> | |
| <div class="line-numbers" id="lineNumbers">1</div> | |
| <textarea id="codeEditor" class="code-input" spellcheck="false" placeholder="// Write your JavaScript code here...">// Welcome to CodeFlow Studio | |
| // Try editing this code and see the results instantly! | |
| function calculateFactorial(n) { | |
| if (n === 0 || n === 1) { | |
| return 1; | |
| } | |
| return n * calculateFactorial(n - 1); | |
| } | |
| const number = 5; | |
| const result = calculateFactorial(number); | |
| console.log(`The factorial of ${number} is: ${result}`); | |
| // Create a simple visual element | |
| const div = document.createElement('div'); | |
| div.style.padding = '20px'; | |
| div.style.background = 'linear-gradient(135deg, #6366f1, #a855f7)'; | |
| div.style.color = 'white'; | |
| div.style.borderRadius = '12px'; | |
| div.style.marginTop = '10px'; | |
| div.style.textAlign = 'center'; | |
| div.style.fontFamily = 'sans-serif'; | |
| div.innerHTML = '<h2>Hello World!</h2><p>This DOM node was created by your code.</p>'; | |
| document.body.appendChild(div);</textarea> | |
| </div> | |
| </div> | |
| <!-- Status Bar --> | |
| <div class="h-6 bg-[#007acc] text-white text-[10px] flex items-center px-3 justify-between select-none"> | |
| <div class="flex gap-4"> | |
| <span class="flex items-center gap-1"><svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path></svg> master*</span> | |
| <span id="cursorPos">Ln 1, Col 1</span> | |
| </div> | |
| <div class="flex gap-4"> | |
| <span>UTF-8</span> | |
| <span>JavaScript</span> | |
| <span id="liveIndicator" class="flex items-center gap-1"><span class="w-1.5 h-1.5 bg-green-400 rounded-full animate-pulse"></span> Live</span> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Resizer (Visual only for this demo, functional in full app) --> | |
| <div class="w-1 bg-gray-700 hover:bg-blue-500 cursor-col-resize transition-colors z-20"></div> | |
| <!-- Right Panel: Output --> | |
| <section class="flex-1 flex flex-col min-w-[300px] bg-white relative"> | |
| <!-- Output Toolbar --> | |
| <div class="h-10 bg-gray-100 flex items-center justify-between px-4 border-b border-gray-200 select-none"> | |
| <div class="flex items-center gap-2"> | |
| <svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path></svg> | |
| <span class="text-xs font-semibold text-gray-600 uppercase tracking-wider">Live Preview</span> | |
| </div> | |
| <button onclick="clearOutput()" class="text-xs text-red-500 hover:text-red-700 font-medium px-2 py-1 rounded hover:bg-red-50 transition-colors"> | |
| Clear Console | |
| </button> | |
| </div> | |
| <!-- Output Content --> | |
| <div class="flex-1 overflow-auto p-4 font-mono text-sm text-gray-800" id="outputContainer"> | |
| <!-- Dynamic content goes here --> | |
| <div class="text-gray-400 italic text-xs mb-2 border-b border-gray-200 pb-2">Console Output:</div> | |
| <div id="consoleLogs" class="space-y-1"></div> | |
| <div id="visualOutput" class="mt-4"></div> | |
| </div> | |
| </section> | |
| </main> | |
| <!-- Settings Modal Overlay --> | |
| <div id="settingsModal" class="fixed inset-0 z-50 hidden"> | |
| <!-- Backdrop --> | |
| <div class="absolute inset-0 bg-black/60 backdrop-blur-sm transition-opacity opacity-0" id="modalBackdrop"></div> | |
| <!-- Modal Content --> | |
| <div class="absolute inset-0 flex items-center justify-center p-4 pointer-events-none"> | |
| <div class="bg-[#1e1e1e] w-full max-w-md rounded-xl shadow-2xl border border-gray-700 transform scale-95 opacity-0 transition-all duration-300 pointer-events-auto flex flex-col max-h-[80vh]" id="modalPanel"> | |
| <!-- Modal Header --> | |
| <div class="flex items-center justify-between p-4 border-b border-gray-700"> | |
| <h2 class="text-lg font-semibold text-white flex items-center gap-2"> | |
| <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg> | |
| Settings | |
| </h2> | |
| <button id="closeSettings" class="text-gray-400 hover:text-white transition-colors"> | |
| <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg> | |
| </button> | |
| </div> | |
| <!-- Modal Body --> | |
| <div class="p-6 space-y-6 overflow-y-auto custom-scrollbar"> | |
| <!-- Theme Selection --> | |
| <div class="space-y-3"> | |
| <label class="text-sm font-medium text-gray-300">Editor Theme</label> | |
| <div class="grid grid-cols-3 gap-3"> | |
| <button class="theme-btn p-3 rounded-lg border border-gray-600 bg-[#1e1e1e] hover:border-blue-500 focus:ring-2 ring-blue-500 transition-all active-theme" data-theme="dark"> | |
| <div class="w-full h-8 bg-gray-800 rounded mb-2 border border-gray-700"></div> | |
| <span class="text-xs text-gray-400">Dark</span> | |
| </button> | |
| <button class="theme-btn p-3 rounded-lg border border-gray-600 bg-[#1e1e1e] hover:border-blue-500 focus:ring-2 ring-blue-500 transition-all" data-theme="midnight"> | |
| <div class="w-full h-8 bg-[#0f172a] rounded mb-2 border border-gray-700"></div> | |
| <span class="text-xs text-gray-400">Midnight</span> | |
| </button> | |
| <button class="theme-btn p-3 rounded-lg border border-gray-600 bg-[#1e1e1e] hover:border-blue-500 focus:ring-2 ring-blue-500 transition-all" data-theme="light"> | |
| <div class="w-full h-8 bg-gray-100 rounded mb-2 border border-gray-300"></div> | |
| <span class="text-xs text-gray-400">Light</span> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Font Size --> | |
| <div class="space-y-3"> | |
| <div class="flex justify-between"> | |
| <label class="text-sm font-medium text-gray-300">Font Size</label> | |
| <span id="fontSizeVal" class="text-xs text-blue-400 font-mono">14px</span> | |
| </div> | |
| <input type="range" min="10" max="24" value="14" id="fontSizeSlider" class="w-full"> | |
| </div> | |
| <!-- Toggles --> | |
| <div class="space-y-3"> | |
| <label class="text-sm font-medium text-gray-300">Behavior</label> | |
| <label class="flex items-center justify-between p-3 rounded-lg bg-gray-800/50 border border-gray-700 cursor-pointer hover:bg-gray-800 transition-colors"> | |
| <span class="text-sm text-gray-300">Word Wrap</span> | |
| <div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in"> | |
| <input type="checkbox" name="toggle" id="wordWrapToggle" class="toggle-checkbox absolute block w-5 h-5 rounded-full bg-white border-4 appearance-none cursor-pointer checked:right-0 checked:border-blue-500"/> | |
| <label for="wordWrapToggle" class="toggle-label block overflow-hidden h-5 rounded-full bg-gray-600 cursor-pointer"></label> | |
| </div> | |
| </label> | |
| <label class="flex items-center justify-between p-3 rounded-lg bg-gray-800/50 border border-gray-700 cursor-pointer hover:bg-gray-800 transition-colors"> | |
| <span class="text-sm text-gray-300">Auto Run on Type</span> | |
| <div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in"> | |
| <input type="checkbox" name="toggle" id="autoRunToggle" checked class="toggle-checkbox absolute block w-5 h-5 rounded-full bg-white border-4 appearance-none cursor-pointer checked:right-0 checked:border-blue-500"/> | |
| <label for="autoRunToggle" class="toggle-label block overflow-hidden h-5 rounded-full bg-gray-600 cursor-pointer"></label> | |
| </div> | |
| </label> | |
| </div> | |
| </div> | |
| <!-- Modal Footer --> | |
| <div class="p-4 border-t border-gray-700 flex justify-end bg-[#252526] rounded-b-xl"> | |
| <button id="saveSettings" class="px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white text-sm font-medium rounded-lg transition-colors shadow-lg shadow-blue-600/20"> | |
| Done | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Toast Notification --> | |
| <div id="toast" class="fixed bottom-6 right-6 bg-gray-800 text-white px-4 py-3 rounded-lg shadow-xl border border-gray-700 transform translate-y-20 opacity-0 transition-all duration-300 flex items-center gap-3 z-50"> | |
| <div id="toastIcon"></div> | |
| <span id="toastMsg" class="text-sm font-medium">Notification</span> | |
| </div> | |
| <script> | |
| // --- Elements --- | |
| const editor = document.getElementById('codeEditor'); | |
| const lineNumbers = document.getElementById('lineNumbers'); | |
| const consoleLogs = document.getElementById('consoleLogs'); | |
| const visualOutput = document.getElementById('visualOutput'); | |
| const cursorPosDisplay = document.getElementById('cursorPos'); | |
| // Settings Elements | |
| const settingsBtn = document.getElementById('settingsBtn'); | |
| const settingsModal = document.getElementById('settingsModal'); | |
| const modalBackdrop = document.getElementById('modalBackdrop'); | |
| const modalPanel = document.getElementById('modalPanel'); | |
| const closeSettings = document.getElementById('closeSettings'); | |
| const saveSettings = document.getElementById('saveSettings'); | |
| const fontSizeSlider = document.getElementById('fontSizeSlider'); | |
| const fontSizeVal = document.getElementById('fontSizeVal'); | |
| const wordWrapToggle = document.getElementById('wordWrapToggle'); | |
| const autoRunToggle = document.getElementById('autoRunToggle'); | |
| // State | |
| let config = { | |
| fontSize: 14, | |
| wordWrap: false, | |
| autoRun: true, | |
| theme: 'dark' | |
| }; | |
| // --- Initialization --- | |
| document.addEventListener('DOMContentLoaded', () => { | |
| updateLineNumbers(); | |
| runCode(); // Initial run | |
| }); | |
| // --- Editor Logic --- | |
| // Sync Line Numbers | |
| const updateLineNumbers = () => { | |
| const lines = editor.value.split('\n').length; | |
| lineNumbers.innerHTML = Array(lines).fill(0).map((_, i) => `<div>${i + 1}</div>`).join(''); | |
| }; | |
| // Sync Scroll | |
| editor.addEventListener('scroll', () => { | |
| lineNumbers.scrollTop = editor.scrollTop; | |
| }); | |
| // Input Handling | |
| editor.addEventListener('input', () => { | |
| updateLineNumbers(); | |
| if (config.autoRun) { | |
| debouncedRun(); | |
| } | |
| }); | |
| // Tab Support | |
| editor.addEventListener('keydown', (e) => { | |
| if (e.key === 'Tab') { | |
| e.preventDefault(); | |
| const start = editor.selectionStart; | |
| const end = editor.selectionEnd; | |
| editor.value = editor.value.substring(0, start) + " " + editor.value.substring(end); | |
| editor.selectionStart = editor.selectionEnd = start + 4; | |
| } | |
| }); | |
| // Cursor Position Update | |
| editor.addEventListener('keyup', updateCursorPos); | |
| editor.addEventListener('click', updateCursorPos); | |
| function updateCursorPos() { | |
| const val = editor.value; | |
| const sel = editor.selectionStart; | |
| const lines = val.substr(0, sel).split("\n"); | |
| const lineNum = lines.length; | |
| const colNum = lines[lines.length - 1].length + 1; | |
| cursorPosDisplay.textContent = `Ln ${lineNum}, Col ${colNum}`; | |
| } | |
| // --- Execution Engine --- | |
| let debounceTimer; | |
| const debouncedRun = () => { | |
| clearTimeout(debounceTimer); | |
| debounceTimer = setTimeout(runCode, 800); // 800ms delay for live typing | |
| }; | |
| function clearOutput() { | |
| consoleLogs.innerHTML = ''; | |
| visualOutput.innerHTML = ''; | |
| } | |
| function runCode() { | |
| clearOutput(); | |
| const code = editor.value; | |
| // Capture Console | |
| const originalLog = console.log; | |
| const originalError = console.error; | |
| const originalWarn = console.warn; | |
| const createLogEntry = (type, args) => { | |
| const div = document.createElement('div'); | |
| div.className = `font-mono text-xs p-2 rounded border-l-2 ${ | |
| type === 'error' ? 'bg-red-50 border-red-500 text-red-700' : | |
| type === 'warn' ? 'bg-yellow-50 border-yellow-500 text-yellow-700' : | |
| 'bg-gray-50 border-blue-500 text-gray-700' | |
| }`; | |
| // Format arguments | |
| const text = args.map(arg => { | |
| if (typeof arg === 'object') return JSON.stringify(arg, null, 2); | |
| return String(arg); | |
| }).join(' '); | |
| div.textContent = `> ${text}`; | |
| consoleLogs.appendChild(div); | |
| // Auto scroll to bottom | |
| const container = document.getElementById('outputContainer'); | |
| container.scrollTop = container.scrollHeight; | |
| }; | |
| console.log = (...args) => { | |
| createLogEntry('log', args); | |
| originalLog(...args); | |
| }; | |
| console.error = (...args) => { | |
| createLogEntry('error', args); | |
| originalError(...args); | |
| }; | |
| console.warn = (...args) => { | |
| createLogEntry('warn', args); | |
| originalWarn(...args); | |
| }; | |
| try { | |
| // Execute Code safely-ish | |
| // We wrap it in a function to allow returns (though ignored) and strict mode | |
| const runUserCode = new Function(code); | |
| runUserCode(); | |
| } catch (err) { | |
| console.error(err.toString()); | |
| } finally { | |
| // Restore console | |
| console.log = originalLog; | |
| console.error = originalError; | |
| console.warn = originalWarn; | |
| } | |
| } | |
| // --- Settings Modal Logic --- | |
| function openModal() { | |
| settingsModal.classList.remove('hidden'); | |
| // Trigger animations | |
| setTimeout(() => { | |
| modalBackdrop.classList.remove('opacity-0'); | |
| modalPanel.classList.remove('opacity-0', 'scale-95'); | |
| modalPanel.classList.add('scale-100'); | |
| }, 10); | |
| } | |
| function closeModal() { | |
| modalBackdrop.classList.add('opacity-0'); | |
| modalPanel.classList.remove('scale-100'); | |
| modalPanel.classList.add('opacity-0', 'scale-95'); | |
| setTimeout(() => { | |
| settingsModal.classList.add('hidden'); | |
| }, 300); | |
| } | |
| settingsBtn.addEventListener('click', openModal); | |
| closeSettings.addEventListener('click', closeModal); | |
| saveSettings.addEventListener('click', closeModal); | |
| modalBackdrop.addEventListener('click', closeModal); | |
| // --- Settings Interactions --- | |
| // Font Size | |
| fontSizeSlider.addEventListener('input', (e) => { | |
| const size = e.target.value; | |
| fontSizeVal.textContent = `${size}px`; | |
| editor.style.fontSize = `${size}px`; | |
| lineNumbers.style.fontSize = `${size}px`; | |
| lineNumbers.style.lineHeight = `${parseInt(size) * 1.5}px`; // Approximate | |
| editor.style.lineHeight = `${parseInt(size) * 1.5}px`; | |
| }); | |
| // Word Wrap | |
| wordWrapToggle.addEventListener('change', (e) => { | |
| config.wordWrap = e.target.checked; | |
| editor.style.whiteSpace = config.wordWrap ? 'pre-wrap' : 'pre'; | |
| }); | |
| // Auto Run | |
| autoRunToggle.addEventListener('change', (e) => { | |
| config.autoRun = e.target.checked; | |
| const indicator = document.getElementById('liveIndicator'); | |
| if(config.autoRun) { | |
| indicator.innerHTML = '<span class="w-1.5 h-1.5 bg-green-400 rounded-full animate-pulse"></span> Live'; | |
| runCode(); // Run immediately if turned on | |
| } else { | |
| indicator.innerHTML = '<span class="w-1.5 h-1.5 bg-gray-400 rounded-full"></span> Manual'; | |
| } | |
| }); | |
| // Theme Switching (Simplified) | |
| document.querySelectorAll('.theme-btn').forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| // Remove active state from all | |
| document.querySelectorAll('.theme-btn').forEach(b => { | |
| b.classList.remove('ring-2', 'ring-blue-500', 'border-blue-500'); | |
| b.classList.add('border-gray-600'); | |
| }); | |
| // Add active state | |
| btn.classList.remove('border-gray-600'); | |
| btn.classList.add('ring-2', 'ring-blue-500', 'border-blue-500'); | |
| const theme = btn.dataset.theme; | |
| applyTheme(theme); | |
| }); | |
| }); | |
| function applyTheme(theme) { | |
| if (theme === 'light') { | |
| editor.style.backgroundColor = '#ffffff'; | |
| editor.style.color = '#1f2937'; | |
| lineNumbers.style.backgroundColor = '#f3f4f6'; | |
| lineNumbers.style.color = '#9ca3af'; | |
| lineNumbers.style.borderRightColor = '#e5e7eb'; | |
| } else if (theme === 'midnight') { | |
| editor.style.backgroundColor = '#0f172a'; | |
| editor.style.color = '#cbd5e1'; | |
| lineNumbers.style.backgroundColor = '#1e293b'; | |
| lineNumbers.style.color = '#64748b'; | |
| lineNumbers.style.borderRightColor = '#334155'; | |
| } else { | |
| // Dark Default | |
| editor.style.backgroundColor = '#1e1e1e'; | |
| editor.style.color = '#e5e7eb'; | |
| lineNumbers.style.backgroundColor = '#1e1e1e'; | |
| lineNumbers.style.color = '#6b7280'; | |
| lineNumbers.style.borderRightColor = '#374151'; | |
| } | |
| } | |
| // --- Utilities --- | |
| function copyCode() { | |
| navigator.clipboard.writeText(editor.value).then(() => { | |
| showToast('Code copied to clipboard!', 'success'); | |
| }).catch(() => { | |
| showToast('Failed to copy code.', 'error'); | |
| }); | |
| } | |
| function downloadCode() { | |
| const blob = new Blob([editor.value], { type: 'text/plain' }); | |
| const url = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = 'script.txt'; // As requested .txt | |
| a.click(); | |
| window.URL.revokeObjectURL(url); | |
| showToast('File downloaded successfully!', 'success'); | |
| } | |
| function showToast(message, type) { | |
| const toast = document.getElementById('toast'); | |
| const msg = document.getElementById('toastMsg'); | |
| const icon = document.getElementById('toastIcon'); | |
| msg.textContent = message; | |
| if (type === 'success') { | |
| icon.innerHTML = `<svg class="w-5 h-5 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>`; | |
| } else { | |
| icon.innerHTML = `<svg class="w-5 h-5 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>`; | |
| } | |
| toast.classList.remove('translate-y-20', 'opacity-0'); | |
| setTimeout(() => { | |
| toast.classList.add('translate-y-20', 'opacity-0'); | |
| }, 3000); | |
| } | |
| </script> | |
| </body> | |
| </html> |