Spaces:
Running
Running
| <html lang="de"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Nebula Editor - Modern Web Editor</title> | |
| <!-- Import FontAwesome for Icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| /* --- CSS VARIABLES & THEME --- */ | |
| :root { | |
| --bg-body: #f3f4f6; | |
| --bg-sidebar: #ffffff; | |
| --bg-editor: #ffffff; | |
| --bg-toolbar: #ffffff; | |
| --text-main: #1f2937; | |
| --text-muted: #6b7280; | |
| --border-color: #e5e7eb; | |
| --accent-color: #6366f1; /* Indigo */ | |
| --accent-hover: #4f46e5; | |
| --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
| --font-ui: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | |
| --font-code: 'Fira Code', 'Consolas', 'Monaco', monospace; | |
| } | |
| [data-theme="dark"] { | |
| --bg-body: #0f172a; | |
| --bg-sidebar: #1e293b; | |
| --bg-editor: #1e293b; | |
| --bg-toolbar: #1e293b; | |
| --text-main: #f1f5f9; | |
| --text-muted: #94a3b8; | |
| --border-color: #334155; | |
| --accent-color: #818cf8; | |
| --accent-hover: #6366f1; | |
| --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.5); | |
| } | |
| /* --- RESET & BASE STYLES --- */ | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease; | |
| } | |
| body { | |
| font-family: var(--font-ui); | |
| background-color: var(--bg-body); | |
| color: var(--text-main); | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| } | |
| /* --- HEADER --- */ | |
| header { | |
| background-color: var(--bg-sidebar); | |
| border-bottom: 1px solid var(--border-color); | |
| padding: 0 20px; | |
| height: 60px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| box-shadow: var(--shadow); | |
| z-index: 10; | |
| } | |
| .brand { | |
| font-weight: 700; | |
| font-size: 1.2rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| color: var(--accent-color); | |
| } | |
| .brand i { | |
| font-size: 1.4rem; | |
| } | |
| .header-actions { | |
| display: flex; | |
| gap: 15px; | |
| } | |
| .btn-icon { | |
| background: none; | |
| border: none; | |
| color: var(--text-muted); | |
| cursor: pointer; | |
| font-size: 1.1rem; | |
| padding: 8px; | |
| border-radius: 6px; | |
| } | |
| .btn-icon:hover { | |
| background-color: var(--bg-body); | |
| color: var(--accent-color); | |
| } | |
| /* --- MAIN LAYOUT --- */ | |
| .app-container { | |
| display: flex; | |
| flex: 1; | |
| height: calc(100vh - 60px); | |
| } | |
| /* --- SIDEBAR --- */ | |
| .sidebar { | |
| width: 250px; | |
| background-color: var(--bg-sidebar); | |
| border-right: 1px solid var(--border-color); | |
| display: flex; | |
| flex-direction: column; | |
| padding: 20px; | |
| transform: translateX(0); | |
| transition: transform 0.3s ease; | |
| } | |
| .sidebar-header { | |
| font-size: 0.85rem; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| color: var(--text-muted); | |
| margin-bottom: 15px; | |
| font-weight: 600; | |
| } | |
| .file-list { | |
| list-style: none; | |
| flex: 1; | |
| } | |
| .file-item { | |
| padding: 10px 12px; | |
| margin-bottom: 5px; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| color: var(--text-main); | |
| font-size: 0.95rem; | |
| } | |
| .file-item:hover { | |
| background-color: var(--bg-body); | |
| } | |
| .file-item.active { | |
| background-color: var(--accent-color); | |
| color: white; | |
| } | |
| .file-item i { | |
| color: var(--text-muted); | |
| } | |
| .file-item.active i { | |
| color: white; | |
| } | |
| /* --- EDITOR AREA --- */ | |
| .editor-wrapper { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| position: relative; | |
| background-color: var(--bg-editor); | |
| } | |
| /* Toolbar */ | |
| .toolbar { | |
| padding: 10px 20px; | |
| background-color: var(--bg-toolbar); | |
| border-bottom: 1px solid var(--border-color); | |
| display: flex; | |
| gap: 10px; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| } | |
| .tool-btn { | |
| background: transparent; | |
| border: 1px solid var(--border-color); | |
| color: var(--text-main); | |
| padding: 6px 12px; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| font-size: 0.9rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| .tool-btn:hover { | |
| background-color: var(--bg-body); | |
| border-color: var(--text-muted); | |
| } | |
| .tool-btn.primary { | |
| background-color: var(--accent-color); | |
| color: white; | |
| border: none; | |
| } | |
| .tool-btn.primary:hover { | |
| background-color: var(--accent-hover); | |
| } | |
| /* Code/Text Area */ | |
| .editor-content { | |
| flex: 1; | |
| position: relative; | |
| overflow: hidden; | |
| display: flex; | |
| } | |
| .line-numbers { | |
| width: 50px; | |
| background-color: var(--bg-body); | |
| color: var(--text-muted); | |
| text-align: right; | |
| padding: 20px 10px; | |
| font-family: var(--font-code); | |
| font-size: 14px; | |
| line-height: 1.6; | |
| border-right: 1px solid var(--border-color); | |
| user-select: none; | |
| overflow: hidden; | |
| } | |
| textarea#editor { | |
| flex: 1; | |
| background-color: transparent; | |
| color: var(--text-main); | |
| border: none; | |
| outline: none; | |
| padding: 20px; | |
| font-family: var(--font-code); | |
| font-size: 14px; | |
| line-height: 1.6; | |
| resize: none; | |
| white-space: pre; | |
| overflow: auto; | |
| } | |
| /* Status Bar */ | |
| .status-bar { | |
| height: 30px; | |
| background-color: var(--accent-color); | |
| color: white; | |
| display: flex; | |
| align-items: center; | |
| justify-content: flex-end; | |
| padding: 0 20px; | |
| font-size: 0.8rem; | |
| gap: 20px; | |
| } | |
| .status-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| /* --- TOAST NOTIFICATION --- */ | |
| .toast { | |
| position: fixed; | |
| bottom: 40px; | |
| right: 20px; | |
| background-color: var(--text-main); | |
| color: var(--bg-body); | |
| padding: 12px 24px; | |
| border-radius: 8px; | |
| box-shadow: var(--shadow); | |
| transform: translateY(100px); | |
| opacity: 0; | |
| transition: all 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55); | |
| z-index: 100; | |
| } | |
| .toast.show { | |
| transform: translateY(0); | |
| opacity: 1; | |
| } | |
| /* --- RESPONSIVE DESIGN --- */ | |
| @media (max-width: 768px) { | |
| .sidebar { | |
| position: absolute; | |
| height: 100%; | |
| z-index: 20; | |
| transform: translateX(-100%); | |
| box-shadow: var(--shadow); | |
| } | |
| .sidebar.open { | |
| transform: translateX(0); | |
| } | |
| .brand span { | |
| display: none; | |
| } | |
| .toolbar { | |
| padding: 10px; | |
| gap: 5px; | |
| justify-content: space-between; | |
| } | |
| .tool-text { | |
| display: none; | |
| } | |
| } | |
| /* --- ANYCREDER LINK --- */ | |
| .anycoder-link { | |
| color: var(--accent-color); | |
| text-decoration: none; | |
| font-weight: 600; | |
| font-size: 0.9rem; | |
| margin-left: auto; | |
| margin-right: 20px; | |
| } | |
| .anycoder-link:hover { | |
| text-decoration: underline; | |
| } | |
| @media (max-width: 600px) { | |
| .anycoder-link { | |
| display: none; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Header --> | |
| <header> | |
| <div class="brand"> | |
| <button class="btn-icon" id="toggle-sidebar" title="Toggle Sidebar"> | |
| <i class="fa-solid fa-bars"></i> | |
| </button> | |
| <i class="fa-solid fa-code"></i> | |
| <span>Nebula Editor</span> | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link"> | |
| Built with anycoder | |
| </a> | |
| <div class="header-actions"> | |
| <button class="btn-icon" id="theme-toggle" title="Toggle Dark Mode"> | |
| <i class="fa-solid fa-moon"></i> | |
| </button> | |
| <button class="btn-icon" onclick="downloadFile()" title="Download Code"> | |
| <i class="fa-solid fa-download"></i> | |
| </button> | |
| </div> | |
| </header> | |
| <!-- Main Application --> | |
| <div class="app-container"> | |
| <!-- Sidebar --> | |
| <aside class="sidebar" id="sidebar"> | |
| <div class="sidebar-header">Explorer</div> | |
| <ul class="file-list"> | |
| <li class="file-item active" onclick="switchFile('index.html')"> | |
| <i class="fa-brands fa-html5" style="color: #e34c26;"></i> index.html | |
| </li> | |
| <li class="file-item" onclick="switchFile('style.css')"> | |
| <i class="fa-brands fa-css3-alt" style="color: #264de4;"></i> style.css | |
| </li> | |
| <li class="file-item" onclick="switchFile('script.js')"> | |
| <i class="fa-brands fa-js" style="color: #f0db4f;"></i> script.js | |
| </li> | |
| <li class="file-item" onclick="switchFile('notes.md')"> | |
| <i class="fa-brands fa-markdown"></i> notes.md | |
| </li> | |
| </ul> | |
| <div class="sidebar-header" style="margin-top: 20px;">Settings</div> | |
| <div style="font-size: 0.85rem; color: var(--text-muted);"> | |
| <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;"> | |
| <input type="checkbox" id="autosave-check" checked> Auto-save | |
| </label> | |
| </div> | |
| </aside> | |
| <!-- Editor Section --> | |
| <main class="editor-wrapper"> | |
| <!-- Toolbar --> | |
| <div class="toolbar"> | |
| <button class="tool-btn" onclick="insertText('**', '**')" title="Bold"> | |
| <i class="fa-solid fa-bold"></i> | |
| </button> | |
| <button class="tool-btn" onclick="insertText('*', '*')" title="Italic"> | |
| <i class="fa-solid fa-italic"></i> | |
| </button> | |
| <button class="tool-btn" onclick="insertText('<', '>')" title="Tag Wrapper"> | |
| <i class="fa-solid fa-code"></i> | |
| </button> | |
| <div style="flex:1"></div> | |
| <button class="tool-btn" onclick="clearEditor()" title="Clear All"> | |
| <i class="fa-solid fa-trash"></i> | |
| <span class="tool-text">Clear</span> | |
| </button> | |
| <button class="tool-btn primary" onclick="copyToClipboard()" title="Copy to Clipboard"> | |
| <i class="fa-regular fa-copy"></i> | |
| <span class="tool-text">Copy</span> | |
| </button> | |
| </div> | |
| <!-- Editor & Line Numbers --> | |
| <div class="editor-content"> | |
| <div class="line-numbers" id="line-numbers">1</div> | |
| <textarea id="editor" spellcheck="false" placeholder="Start typing your code here..."></textarea> | |
| </div> | |
| <!-- Status Bar --> | |
| <footer class="status-bar"> | |
| <div class="status-item"> | |
| <i class="fa-solid fa-file"></i> | |
| <span id="current-filename">index.html</span> | |
| </div> | |
| <div class="status-item"> | |
| <i class="fa-solid fa-font"></i> | |
| <span id="char-count">0 chars</span> | |
| </div> | |
| <div class="status-item"> | |
| <i class="fa-solid fa-align-left"></i> | |
| <span id="word-count">0 words</span> | |
| </div> | |
| <div class="status-item"> | |
| <i class="fa-solid fa-check-circle" id="save-status"></i> | |
| <span>Ready</span> | |
| </div> | |
| </footer> | |
| </main> | |
| </div> | |
| <!-- Toast Notification --> | |
| <div class="toast" id="toast">File saved successfully!</div> | |
| <script> | |
| // --- DOM Elements --- | |
| const editor = document.getElementById('editor'); | |
| const lineNumbers = document.getElementById('line-numbers'); | |
| const charCountEl = document.getElementById('char-count'); | |
| const wordCountEl = document.getElementById('word-count'); | |
| const saveStatusEl = document.getElementById('save-status'); | |
| const themeToggleBtn = document.getElementById('theme-toggle'); | |
| const sidebar = document.getElementById('sidebar'); | |
| const toggleSidebarBtn = document.getElementById('toggle-sidebar'); | |
| const currentFilenameEl = document.getElementById('current-filename'); | |
| const fileItems = document.querySelectorAll('.file-item'); | |
| // --- State Management --- | |
| let currentFilename = 'index.html'; | |
| const files = { | |
| 'index.html': `<!DOCTYPE html>\n<html>\n <head>\n <title>My App</title>\n </head>\n <body>\n <h1>Hello World</h1>\n </body>\n</html>`, | |
| 'style.css': `/* Main Styles */\nbody {\n background-color: #f0f0f0;\n font-family: sans-serif;\n}\n\n.container {\n max-width: 1200px;\n margin: 0 auto;\n}`, | |
| 'script.js': `// Initialize App\ndocument.addEventListener('DOMContentLoaded', () => {\n console.log('App started!');\n init();\n});\n\nfunction init() {\n // your code here\n}`, | |
| 'notes.md': `# Project Notes\n\n- [x] Setup HTML\n- [ ] Design CSS\n- [ ] Implement JS\n\n## Ideas\nTry adding a dark mode toggle.` | |
| }; | |
| // --- Initialization --- | |
| function init() { | |
| // Load saved theme | |
| const savedTheme = localStorage.getItem('nebula-theme') || 'light'; | |
| document.documentElement.setAttribute('data-theme', savedTheme); | |
| updateThemeIcon(savedTheme); | |
| // Load saved content for current file | |
| const savedContent = localStorage.getItem(`nebula-${currentFilename}`); | |
| editor.value = savedContent !== null ? savedContent : files[currentFilename]; | |
| updateLineNumbers(); | |
| updateStats(); | |
| } | |
| // --- Editor Logic --- | |
| // Sync Scroll between textarea and line numbers | |
| editor.addEventListener('scroll', () => { | |
| lineNumbers.scrollTop = editor.scrollTop; | |
| }); | |
| // Handle Input | |
| editor.addEventListener('input', () => { | |
| updateLineNumbers(); | |
| updateStats(); | |
| saveToLocal(); | |
| }); | |
| // Handle Tab Key (Insert spaces instead of changing focus) | |
| editor.addEventListener('keydown', (e) => { | |
| if (e.key === 'Tab') { | |
| e.preventDefault(); | |
| const start = editor.selectionStart; | |
| const end = editor.selectionEnd; | |
| const spaces = " "; // 2 spaces | |
| editor.value = editor.value.substring(0, start) + spaces + editor.value.substring(end); | |
| editor.selectionStart = editor.selectionEnd = start + 2; | |
| updateLineNumbers(); | |
| saveToLocal(); | |
| } | |
| }); | |
| function updateLineNumbers() { | |
| const lines = editor.value.split('\n').length; | |
| lineNumbers.innerHTML = Array(lines).fill(0).map((_, i) => i + 1).join('<br>'); | |
| } | |
| function updateStats() { | |
| const text = editor.value; | |
| charCountEl.textContent = `${text.length} chars`; | |
| const words = text.trim() === '' ? 0 : text.trim().split(/\s+/).length; | |
| wordCountEl.textContent = `${words} words`; | |
| } | |
| // --- File Management --- | |
| function switchFile(filename) { | |
| // Save current file before switching | |
| saveToLocal(); | |
| // Update State | |
| currentFilename = filename; | |
| currentFilenameEl.textContent = filename; | |
| // Update UI Active State | |
| fileItems.forEach(item => item.classList.remove('active')); | |
| const activeItem = Array.from(fileItems).find(item => item.textContent.includes(filename)); | |
| if(activeItem) activeItem.classList.add('active'); | |
| // Load Content | |
| const savedContent = localStorage.getItem(`nebula-${filename}`); | |
| editor.value = savedContent !== null ? savedContent : files[filename]; | |
| updateLineNumbers(); | |
| updateStats(); | |
| // On mobile, close sidebar after selection | |
| if (window.innerWidth <= 768) { | |
| sidebar.classList.remove('open'); | |
| } | |
| } | |
| function saveToLocal() { | |
| if(!document.getElementById('autosave-check').checked) return; | |
| localStorage.setItem(`nebula-${currentFilename}`, editor.value); | |
| // Visual feedback | |
| saveStatusEl.style.opacity = '1'; | |
| setTimeout(() => { | |
| saveStatusEl.style.opacity = '0.5'; | |
| }, 1000); | |
| } | |
| function downloadFile() { | |
| const text = editor.value; | |
| const blob = new Blob([text], { type: 'text/plain' }); | |
| const anchor = document.createElement('a'); | |
| anchor.download = currentFilename; | |
| anchor.href = window.URL.createObjectURL(blob); | |
| anchor.target = '_blank'; | |
| anchor.style.display = 'none'; | |
| document.body.appendChild(anchor); | |
| anchor.click(); | |
| document.body.removeChild(anchor); | |
| showToast(`Downloaded ${currentFilename}`); | |
| } | |
| function clearEditor() { | |
| if(confirm('Are you sure you want to clear the current file?')) { | |
| editor.value = ''; | |
| updateLineNumbers(); | |
| updateStats(); | |
| saveToLocal(); | |
| } | |
| } | |
| // --- Toolbar Actions --- | |
| function insertText(before, after) { | |
| const start = editor.selectionStart; | |
| const end = editor.selectionEnd; | |
| const text = editor.value; | |
| const selectedText = text.substring(start, end); | |
| const replacement = before + selectedText + after; | |
| editor.value = text.substring(0, start) + replacement + text.substring(end); | |
| editor.focus(); | |
| editor.selectionStart = start + before.length; | |
| editor.selectionEnd = end + before.length; | |
| saveToLocal(); | |
| } | |
| function copyToClipboard() { | |
| editor.select(); | |
| document.execCommand('copy'); | |
| window.getSelection().removeAllRanges(); // Deselect | |
| showToast('Content copied to clipboard!'); | |
| } | |
| // --- UI Interactions --- | |
| function showToast(message) { | |
| const toast = document.getElementById('toast'); | |
| toast.textContent = message; | |
| toast.classList.add('show'); | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| }, 3000); | |
| } | |
| // Theme Toggle | |
| themeToggleBtn.addEventListener('click', () => { | |
| const currentTheme = document.documentElement.getAttribute('data-theme'); | |
| const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; | |
| document.documentElement.setAttribute('data-theme', newTheme); | |
| localStorage.setItem('nebula-theme', newTheme); | |
| updateThemeIcon(newTheme); | |
| }); | |
| function updateThemeIcon(theme) { | |
| const icon = themeToggleBtn.querySelector('i'); | |
| if (theme === 'dark') { | |
| icon.classList.remove('fa-moon'); | |
| icon.classList.add('fa-sun'); | |
| } else { | |
| icon.classList.remove('fa-sun'); | |
| icon.classList.add('fa-moon'); | |
| } | |
| } | |
| // Sidebar Toggle (Mobile) | |
| toggleSidebarBtn.addEventListener('click', () => { | |
| sidebar.classList.toggle('open'); | |
| }); | |
| // Close sidebar when clicking outside on mobile | |
| document.addEventListener('click', (e) => { | |
| if (window.innerWidth <= 768 && | |
| !sidebar.contains(e.target) && | |
| !toggleSidebarBtn.contains(e.target) && | |
| sidebar.classList.contains('open')) { | |
| sidebar.classList.remove('open'); | |
| } | |
| }); | |
| // Run Init | |
| init(); | |
| </script> | |
| </body> | |
| </html> |