python.html (44571B)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> 6 <title>Amit Dutta // Python Compiler</title> 7 <link rel="icon" type="image/x-icon" href="https://compiler.aranag.site/logo.png"> 8 9 <!-- Fonts & Icons --> 10 <link rel="preconnect" href="https://fonts.googleapis.com"> 11 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 12 <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Inter:wght@300;400;500&family=JetBrains+Mono:wght@400&display=swap" rel="stylesheet"> 13 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"> 14 15 <!-- Tailwind CSS --> 16 <script src="https://cdn.tailwindcss.com"></script> 17 18 <!-- CodeMirror 5 (Professional Editor) --> 19 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css"> 20 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/dracula.min.css"> 21 <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script> 22 <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/python/python.min.js"></script> 23 <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/edit/closebrackets.min.js"></script> 24 <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/edit/matchbrackets.min.js"></script> 25 <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/selection/active-line.min.js"></script> 26 27 <!-- Xterm.js --> 28 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" /> 29 <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script> 30 <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script> 31 32 <style> 33 :root { 34 --bg-main: #050505; 35 --accent: #bef264; /* Lime 300 */ 36 --text-muted: #737373; 37 --border-color: rgba(255, 255, 255, 0.1); 38 } 39 40 body { 41 font-family: 'Inter', sans-serif; 42 background-color: var(--bg-main); 43 color: #f5f5f5; 44 height: 100vh; 45 height: 100dvh; 46 overflow: hidden; 47 display: flex; 48 flex-direction: column; 49 } 50 51 /* Portfolio Aesthetic */ 52 .font-mono { font-family: 'JetBrains Mono', monospace; } 53 .font-heading { font-family: 'Space Grotesk', sans-serif; } 54 55 /* Noise Overlay */ 56 .bg-noise { 57 position: fixed; top: 0; left: 0; width: 100%; height: 100%; 58 pointer-events: none; z-index: 0; opacity: 0.03; 59 background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E"); 60 } 61 62 /* CRT Scanline */ 63 .crt-scanline { 64 position: fixed; top: 0; left: 0; width: 100%; height: 2px; 65 background: rgba(190, 242, 100, 0.1); opacity: 0.5; 66 pointer-events: none; z-index: 50; 67 animation: scanline 8s linear infinite; 68 } 69 @keyframes scanline { 0% { top: -10vh; } 100% { top: 110vh; } } 70 71 /* CodeMirror Customization */ 72 .CodeMirror { 73 height: 100% !important; 74 font-family: 'JetBrains Mono', monospace; 75 font-size: 13px; 76 background-color: #0c0f16 !important; /* Slightly lighter than pure black */ 77 color: #f8f8f2; 78 } 79 .CodeMirror-gutters { 80 background-color: #0c0f16 !important; 81 border-right: 1px solid rgba(255,255,255,0.05) !important; 82 } 83 .CodeMirror-linenumber { 84 color: #555 !important; 85 } 86 .CodeMirror-cursor { 87 border-left: 2px solid var(--accent) !important; 88 } 89 .CodeMirror-selected { 90 background: rgba(190, 242, 100, 0.2) !important; 91 } 92 .cm-s-dracula .CodeMirror-activeline-background { 93 background: rgba(255, 255, 255, 0.03) !important; 94 } 95 96 /* Scrollbar styling */ 97 .CodeMirror-scroll::-webkit-scrollbar, 98 .xterm-viewport::-webkit-scrollbar, 99 .custom-scrollbar::-webkit-scrollbar { 100 width: 6px; height: 6px; 101 } 102 .CodeMirror-scroll::-webkit-scrollbar-track, 103 .xterm-viewport::-webkit-scrollbar-track, 104 .custom-scrollbar::-webkit-scrollbar-track { 105 background: #050505; 106 } 107 .CodeMirror-scroll::-webkit-scrollbar-thumb, 108 .xterm-viewport::-webkit-scrollbar-thumb, 109 .custom-scrollbar::-webkit-scrollbar-thumb { 110 background: #333; border-radius: 3px; 111 } 112 113 /* Mobile Helper Bar */ 114 #mobile-helper-bar { 115 display: none; /* Shown via JS on mobile */ 116 background: #151515; 117 border-top: 1px solid rgba(255,255,255,0.1); 118 padding: 8px; 119 overflow-x: auto; 120 white-space: nowrap; 121 -webkit-overflow-scrolling: touch; 122 } 123 @media (max-width: 768px) { 124 #mobile-helper-bar { display: flex; gap: 8px; } 125 } 126 .helper-key { 127 background: rgba(255,255,255,0.1); 128 color: var(--accent); 129 border: 1px solid rgba(255,255,255,0.1); 130 border-radius: 4px; 131 padding: 6px 12px; 132 font-family: 'JetBrains Mono', monospace; 133 font-size: 12px; 134 font-weight: bold; 135 cursor: pointer; 136 user-select: none; 137 } 138 .helper-key:active { background: var(--accent); color: black; } 139 140 /* Toast Notification */ 141 .toast { 142 position: fixed; 143 bottom: 2rem; 144 left: 50%; 145 transform: translateX(-50%) translateY(100px); 146 background: #1a1a1a; 147 border: 1px solid var(--accent); 148 color: var(--accent); 149 padding: 0.5rem 1.5rem; 150 border-radius: 9999px; 151 font-family: 'JetBrains Mono', monospace; 152 font-size: 0.8rem; 153 transition: transform 0.3s ease; 154 z-index: 100; 155 } 156 .toast.show { transform: translateX(-50%) translateY(0); } 157 158 /* Connection Dot Pulse */ 159 .pulse-dot { 160 width: 8px; 161 height: 8px; 162 border-radius: 50%; 163 display: inline-block; 164 box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7); 165 animation: pulse-white 2s infinite; 166 } 167 .pulse-dot.connected { 168 background-color: #bef264; /* Lime */ 169 box-shadow: 0 0 0 0 rgba(190, 242, 100, 0.7); 170 animation: pulse-lime 2s infinite; 171 } 172 .pulse-dot.disconnected { 173 background-color: #ef4444; /* Red */ 174 box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7); 175 animation: pulse-red 2s infinite; 176 } 177 178 @keyframes pulse-lime { 179 0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(190, 242, 100, 0.7); } 180 70% { transform: scale(1); box-shadow: 0 0 0 6px rgba(190, 242, 100, 0); } 181 100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(190, 242, 100, 0); } 182 } 183 @keyframes pulse-red { 184 0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7); } 185 70% { transform: scale(1); box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); } 186 100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); } 187 } 188 189 /* AI Text Overlay */ 190 .ai-overlay { 191 position: absolute; 192 bottom: 10px; 193 right: 20px; 194 background: rgba(20, 20, 20, 0.9); 195 border: 1px solid #ef4444; 196 color: #fca5a5; 197 padding: 8px 12px; 198 border-radius: 6px; 199 font-family: 'JetBrains Mono', monospace; 200 font-size: 12px; 201 z-index: 60; 202 display: none; 203 backdrop-filter: blur(4px); 204 animation: slideIn 0.3s ease-out; 205 box-shadow: 0 4px 12px rgba(0,0,0,0.5); 206 } 207 .ai-overlay.visible { display: flex; align-items: center; gap: 8px; } 208 209 .ai-link { 210 color: #fff; 211 text-decoration: underline; 212 text-decoration-style: dotted; 213 text-underline-offset: 4px; 214 cursor: pointer; 215 font-weight: bold; 216 transition: color 0.2s; 217 } 218 .ai-link:hover { color: #bef264; text-decoration-style: solid; } 219 220 @keyframes slideIn { 221 from { transform: translateY(20px); opacity: 0; } 222 to { transform: translateY(0); opacity: 1; } 223 } 224 225 /* Modals (AI & Clear) */ 226 .modal-overlay { 227 position: fixed; top: 0; left: 0; width: 100%; height: 100%; 228 background: rgba(0,0,0,0.8); backdrop-filter: blur(5px); 229 z-index: 200; display: none; align-items: center; justify-content: center; 230 } 231 .modal-overlay.open { display: flex; } 232 233 .modal-content { 234 background: #0f0f0f; 235 border: 1px solid var(--accent); 236 width: 90%; max-width: 600px; 237 border-radius: 12px; 238 display: flex; flex-direction: column; 239 box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.04); 240 overflow: hidden; 241 animation: modalPop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); 242 } 243 244 @keyframes modalPop { 245 0% { transform: scale(0.9); opacity: 0; } 246 100% { transform: scale(1); opacity: 1; } 247 } 248 249 /* Editor Toolbar Buttons */ 250 .editor-btn { 251 background: rgba(255,255,255,0.05); 252 border: 1px solid rgba(255,255,255,0.1); 253 color: #737373; 254 width: 32px; 255 height: 32px; 256 display: flex; 257 align-items: center; 258 justify-content: center; 259 border-radius: 4px; 260 transition: all 0.2s; 261 cursor: pointer; 262 } 263 .editor-btn:hover { 264 background: rgba(255,255,255,0.1); 265 color: #fff; 266 border-color: rgba(255,255,255,0.3); 267 } 268 .editor-btn.danger:hover { 269 background: rgba(239, 68, 68, 0.1); 270 color: #ef4444; 271 border-color: #ef4444; 272 } 273 </style> 274 </head> 275 <body class="antialiased selection:bg-lime-300 selection:text-black"> 276 277 <div class="bg-noise"></div> 278 <div class="crt-scanline"></div> 279 <div id="toast" class="toast">Link Copied to Clipboard!</div> 280 281 <!-- AI Overlay in Terminal --> 282 <div id="ai-suggestion" class="ai-overlay"> 283 <i class="fas fa-exclamation-triangle text-red-500"></i> 284 <span>Error detected. <span id="ask-ai-link" class="ai-link">Ask AI</span> to fix it.</span> 285 </div> 286 287 <!-- Clear Confirmation Modal --> 288 <div id="clear-modal" class="modal-overlay"> 289 <div class="modal-content max-w-[320px] p-0 border-red-500/50"> 290 <div class="bg-red-500/10 px-6 py-4 border-b border-red-500/20"> 291 <h3 class="font-heading font-bold text-lg text-red-500 flex items-center gap-2"> 292 <i class="fas fa-exclamation-circle"></i> CLEAR CODE 293 </h3> 294 </div> 295 <div class="p-6 text-center"> 296 <p class="text-neutral-300 text-sm font-sans mb-6">Are you sure you want to clear the editor? This action will remove all current code.</p> 297 <div class="flex justify-center gap-3"> 298 <button id="cancel-clear-btn" class="px-4 py-2 rounded text-xs font-mono font-bold uppercase text-neutral-400 hover:text-white hover:bg-white/10 transition-all border border-transparent">Cancel</button> 299 <button id="confirm-clear-btn" class="bg-red-500/10 hover:bg-red-500 hover:text-black text-red-500 border border-red-500/20 px-4 py-2 rounded text-xs font-mono font-bold uppercase transition-all flex items-center gap-2"> 300 <i class="fas fa-trash-alt"></i> Confirm 301 </button> 302 </div> 303 </div> 304 </div> 305 </div> 306 307 <!-- AI Fix Modal --> 308 <div id="ai-modal" class="modal-overlay"> 309 <div class="modal-content max-h-[80vh]"> 310 <!-- Modal Header --> 311 <div class="bg-white/5 px-6 py-4 border-b border-white/10 flex justify-between items-center"> 312 <h3 class="font-heading font-bold text-lg text-lime-300 flex items-center gap-2"> 313 <i class="fas fa-robot"></i> AI Diagnosis 314 </h3> 315 <button id="close-modal" class="text-neutral-500 hover:text-white transition-colors"> 316 <i class="fas fa-times"></i> 317 </button> 318 </div> 319 320 <!-- Modal Body --> 321 <div class="p-6 overflow-y-auto custom-scrollbar flex-1"> 322 <div id="ai-loading" class="hidden flex flex-col items-center justify-center py-8 gap-4"> 323 <div class="pulse-dot connected" style="width: 12px; height: 12px;"></div> 324 <p class="font-mono text-neutral-400 text-sm animate-pulse">Analyzing compile error...</p> 325 </div> 326 327 <div id="ai-result" class="hidden space-y-4"> 328 <div> 329 <h4 class="font-mono text-xs uppercase text-neutral-500 mb-2">Explanation</h4> 330 <p id="ai-explanation" class="text-sm text-neutral-200 leading-relaxed font-sans"></p> 331 </div> 332 333 <div> 334 <h4 class="font-mono text-xs uppercase text-neutral-500 mb-2">Suggested Fix</h4> 335 <div class="relative group"> 336 <pre id="ai-code-preview" class="bg-black/30 border border-white/10 p-3 rounded text-xs font-mono text-lime-100 overflow-x-auto custom-scrollbar"></pre> 337 </div> 338 </div> 339 </div> 340 341 <div id="ai-error-msg" class="hidden text-red-400 text-sm font-mono text-center py-4"></div> 342 </div> 343 344 <!-- Modal Footer --> 345 <div class="bg-white/5 px-6 py-4 border-t border-white/10 flex justify-end gap-3"> 346 <button id="cancel-fix-btn" class="px-4 py-2 rounded text-xs font-mono font-bold uppercase text-neutral-400 hover:text-white hover:bg-white/10 transition-all">Dismiss</button> 347 <button id="apply-fix-btn" disabled class="bg-lime-300/10 hover:bg-lime-300 hover:text-black text-lime-300 border border-lime-300/20 px-4 py-2 rounded text-xs font-mono font-bold uppercase transition-all flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"> 348 <i class="fas fa-check"></i> Apply Fix 349 </button> 350 </div> 351 </div> 352 </div> 353 354 <!-- Header (Responsive) --> 355 <header class="h-auto md:h-16 flex flex-col md:flex-row items-center justify-between px-4 py-3 md:px-6 shrink-0 border-b border-white/10 z-20 bg-neutral-900/50 backdrop-blur-md gap-3 md:gap-4"> 356 357 <!-- Top Row: Title & Editor Tools --> 358 <div class="flex items-center justify-between w-full md:w-auto gap-6"> 359 <h1 class="text-lg md:text-xl font-heading font-bold text-white tracking-tight flex items-center"> 360 <span class="text-lime-300 mr-2">//</span>PY.COMPILER 361 <span id="conn-dot" class="pulse-dot disconnected ml-3" title="Disconnected"></span> 362 </h1> 363 364 <!-- Editor Tools (Undo/Redo/Clear) --> 365 <div class="flex items-center gap-2"> 366 <button id="undo-btn" class="editor-btn" title="Undo (Ctrl+Z)"> 367 <i class="fas fa-undo text-xs"></i> 368 </button> 369 <button id="redo-btn" class="editor-btn" title="Redo (Ctrl+Y)"> 370 <i class="fas fa-redo text-xs"></i> 371 </button> 372 <div class="w-px h-4 bg-white/10 mx-1"></div> 373 <button id="clear-code-btn" class="editor-btn danger" title="Clear Code"> 374 <i class="fas fa-times text-xs"></i> 375 </button> 376 </div> 377 378 <!-- Mobile Only: Exit Link --> 379 <a href="./index.html" class="md:hidden font-mono text-[10px] text-neutral-500 hover:text-white uppercase tracking-wider transition-colors ml-auto">[ EXIT ]</a> 380 </div> 381 382 <!-- Middle: Connection Text --> 383 <div class="hidden md:flex items-center gap-2 font-mono text-xs text-neutral-500"> 384 <span id="connection-status">Connecting to server...</span> 385 </div> 386 387 <!-- Bottom/Right: Buttons --> 388 <div class="flex items-center gap-2 w-full md:w-auto justify-end"> 389 <a href="./index.html" class="hidden md:inline font-mono text-[10px] text-neutral-500 hover:text-white uppercase tracking-wider transition-colors mr-2">[ EXIT ]</a> 390 391 <button id="download-btn" class="bg-white/5 hover:bg-neutral-800 text-white border border-white/10 px-3 py-2 rounded font-mono text-[10px] font-bold uppercase tracking-wider flex items-center justify-center transition-all" title="Download .py"> 392 <i class="fas fa-download"></i> 393 </button> 394 395 <button id="reset-btn" class="bg-white/5 hover:bg-neutral-800 text-white border border-white/10 px-3 py-2 rounded font-mono text-[10px] font-bold uppercase tracking-wider flex items-center justify-center transition-all" title="Reset Terminal"> 396 <i class="fas fa-rotate-left"></i> 397 </button> 398 399 <button id="share-btn" class="bg-white/5 hover:bg-neutral-800 text-white border border-white/10 px-3 py-2 rounded font-mono text-[10px] font-bold uppercase tracking-wider flex items-center justify-center transition-all" title="Share Code"> 400 <i class="fas fa-share-alt"></i> 401 </button> 402 403 <button id="run-btn" disabled class="bg-white/5 hover:bg-lime-300 hover:text-black disabled:opacity-50 disabled:cursor-not-allowed text-white border border-white/10 px-4 py-2 rounded font-mono text-xs font-bold uppercase tracking-wider flex items-center gap-2 transition-all flex-1 md:flex-none justify-center shadow-lg shadow-black/50"> 404 <i class="fas fa-play text-[10px]"></i> <span class="md:hidden lg:inline">EXECUTE</span><span class="hidden md:inline lg:hidden">RUN</span> 405 </button> 406 </div> 407 </header> 408 409 <!-- Main Workspace --> 410 <div class="flex-1 flex flex-col md:flex-row min-h-0 z-10 relative"> 411 412 <!-- Code Editor --> 413 <div class="flex-1 flex flex-col h-[60%] md:h-auto min-h-0 border-b md:border-b-0 md:border-r border-white/10 relative group"> 414 <div class="bg-black/20 px-4 py-2 text-[10px] font-mono text-lime-300 uppercase border-b border-white/10 flex justify-between items-center shrink-0"> 415 <span>SRC // main.py</span> 416 <span class="text-neutral-600">PYTHON 3</span> 417 </div> 418 419 <!-- CodeMirror Container --> 420 <div id="editor-container" class="relative bg-[#0c0f16] flex-1 overflow-hidden"> 421 <textarea id="code-input" class="hidden">import time 422 423 # Simple Clock Preview 424 print("Current Time:") 425 print(time.strftime("%H:%M:%S")) 426 print("Done.")</textarea> 427 </div> 428 429 <!-- Mobile Helper Bar --> 430 <div id="mobile-helper-bar"> 431 <div class="helper-key" data-char=" ">Tab</div> 432 <div class="helper-key" data-char=":">:</div> 433 <div class="helper-key" data-char="=">=</div> 434 <div class="helper-key" data-char="()">()</div> 435 <div class="helper-key" data-char="[]">[]</div> 436 <div class="helper-key" data-char="{}">{}</div> 437 <div class="helper-key" data-char='""'>""</div> 438 <div class="helper-key" data-char="''">''</div> 439 <div class="helper-key" data-char="#">#</div> 440 <div class="helper-key" data-char="+">+</div> 441 <div class="helper-key" data-char="-">-</div> 442 <div class="helper-key" data-char="print()">print</div> 443 </div> 444 </div> 445 446 <!-- Terminal --> 447 <div class="flex-1 flex flex-col h-[40%] md:h-auto min-h-0 bg-[#050505]"> 448 <div class="bg-black/20 px-4 py-2 text-[10px] font-mono text-lime-300 uppercase border-b border-white/10 flex justify-between items-center shrink-0"> 449 <span>OUT // TERMINAL</span> 450 <div class="flex gap-2"> 451 <button id="copy-btn" class="text-neutral-500 hover:text-white transition-colors" title="Copy Output"><i class="fas fa-copy"></i></button> 452 <span class="text-neutral-600">XTERM.JS</span> 453 </div> 454 </div> 455 <div class="flex-1 relative"> 456 <div id="terminal-container" class="absolute inset-0 w-full h-full p-2 overflow-hidden"></div> 457 </div> 458 </div> 459 </div> 460 461 <script type="module"> 462 import { initializeApp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js"; 463 import { getFirestore, doc, getDoc, setDoc } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-firestore.js"; 464 import { getAuth, signInAnonymously } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-auth.js"; 465 466 // --- Firebase Config --- 467 const firebaseConfig = { 468 apiKey: "AIzaSyC8U49NgW7EtYBeKL0Sw08-QvM_Bl59zDw", 469 authDomain: "compiler-aranag-site.firebaseapp.com", 470 projectId: "compiler-aranag-site", 471 storageBucket: "compiler-aranag-site.firebasestorage.app", 472 messagingSenderId: "558890639400", 473 appId: "1:558890639400:web:125525d10ab51696a5951d", 474 measurementId: "G-KH9BGSRRGY" 475 }; 476 477 // Initialize Firebase 478 const app = initializeApp(firebaseConfig); 479 const db = getFirestore(app); 480 const auth = getAuth(app); 481 482 // Attempt initial sign-in 483 signInAnonymously(auth).catch((error) => { 484 console.warn("Auto-Auth failed. App will try unauthenticated access.", error.code); 485 }); 486 487 // --- Helper: Generate ID --- 488 function generateId(length = 7) { 489 const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 490 let result = ''; 491 for (let i = 0; i < length; i++) { 492 result += chars.charAt(Math.floor(Math.random() * chars.length)); 493 } 494 return result; 495 } 496 497 // --- DOM Elements --- 498 const runBtn = document.getElementById('run-btn'); 499 const resetBtn = document.getElementById('reset-btn'); 500 const shareBtn = document.getElementById('share-btn'); 501 const copyBtn = document.getElementById('copy-btn'); 502 const downloadBtn = document.getElementById('download-btn'); 503 const connDot = document.getElementById('conn-dot'); 504 const connectionStatus = document.getElementById('connection-status'); 505 const toast = document.getElementById('toast'); 506 507 // Editor Tool Elements 508 const undoBtn = document.getElementById('undo-btn'); 509 const redoBtn = document.getElementById('redo-btn'); 510 const clearCodeBtn = document.getElementById('clear-code-btn'); 511 const clearModal = document.getElementById('clear-modal'); 512 const cancelClearBtn = document.getElementById('cancel-clear-btn'); 513 const confirmClearBtn = document.getElementById('confirm-clear-btn'); 514 515 // AI Elements 516 const aiSuggestion = document.getElementById('ai-suggestion'); 517 const askAiLink = document.getElementById('ask-ai-link'); 518 const aiModal = document.getElementById('ai-modal'); 519 const closeModal = document.getElementById('close-modal'); 520 const cancelFixBtn = document.getElementById('cancel-fix-btn'); 521 const applyFixBtn = document.getElementById('apply-fix-btn'); 522 const aiLoading = document.getElementById('ai-loading'); 523 const aiResult = document.getElementById('ai-result'); 524 const aiExplanation = document.getElementById('ai-explanation'); 525 const aiCodePreview = document.getElementById('ai-code-preview'); 526 const aiErrorMsg = document.getElementById('ai-error-msg'); 527 528 // --- State --- 529 let socket = null; 530 let currentLine = ""; 531 let startTime = 0; 532 let lastSuggestedCode = ""; 533 let outputBuffer = ""; 534 535 // --- CodeMirror Setup --- 536 const editor = CodeMirror.fromTextArea(document.getElementById("code-input"), { 537 mode: "python", 538 theme: "dracula", 539 lineNumbers: true, 540 indentUnit: 4, 541 matchBrackets: true, 542 autoCloseBrackets: true, 543 styleActiveLine: true, 544 inputStyle: "contenteditable", // Better for mobile 545 viewportMargin: Infinity, 546 extraKeys: { 547 "Backspace": function(cm) { 548 if (cm.somethingSelected()) { 549 cm.execCommand("delCharBefore"); 550 return; 551 } 552 var cursor = cm.getCursor(); 553 var line = cm.getLine(cursor.line); 554 // If cursor is not at start, and everything before it is whitespace 555 if (cursor.ch > 0 && /^\s+$/.test(line.slice(0, cursor.ch))) { 556 cm.execCommand("indentLess"); 557 } else { 558 cm.execCommand("delCharBefore"); 559 } 560 } 561 }, 562 }); 563 564 // Fix resize issues 565 window.addEventListener('resize', () => editor.refresh()); 566 567 // Local Auto-Save 568 const LOCAL_STORAGE_KEY = 'py_compiler_code_autosave'; 569 editor.on('change', () => { 570 localStorage.setItem(LOCAL_STORAGE_KEY, editor.getValue()); 571 }); 572 573 // Load Auto-Save on Start (if not shared) 574 const savedCode = localStorage.getItem(LOCAL_STORAGE_KEY); 575 if (savedCode && !window.location.search.includes('share') && !window.location.search.includes('code')) { 576 editor.setValue(savedCode); 577 } 578 579 // --- Editor Toolbar Logic --- 580 581 // Undo/Redo 582 undoBtn.addEventListener('click', () => { editor.undo(); editor.focus(); }); 583 redoBtn.addEventListener('click', () => { editor.redo(); editor.focus(); }); 584 585 // Clear Code with Confirmation 586 clearCodeBtn.addEventListener('click', () => { 587 clearModal.classList.add('open'); 588 }); 589 590 cancelClearBtn.addEventListener('click', () => { 591 clearModal.classList.remove('open'); 592 }); 593 594 confirmClearBtn.addEventListener('click', () => { 595 editor.setValue(''); 596 editor.clearHistory(); // Optional: Clear history if you want a true "fresh start" 597 editor.focus(); 598 clearModal.classList.remove('open'); 599 showToast("Editor Cleared"); 600 }); 601 602 // Close modal on outside click 603 clearModal.addEventListener('click', (e) => { 604 if (e.target === clearModal) clearModal.classList.remove('open'); 605 }); 606 607 608 // --- Mobile Helper Bar --- 609 document.querySelectorAll('.helper-key').forEach(key => { 610 key.addEventListener('click', (e) => { 611 const char = key.getAttribute('data-char'); 612 const doc = editor.getDoc(); 613 const cursor = doc.getCursor(); 614 615 if (char === '()') { 616 doc.replaceRange('()', cursor); 617 doc.setCursor({line: cursor.line, ch: cursor.ch + 1}); 618 } else if (char === '[]') { 619 doc.replaceRange('[]', cursor); 620 doc.setCursor({line: cursor.line, ch: cursor.ch + 1}); 621 } else if (char === '{}') { 622 doc.replaceRange('{}', cursor); 623 doc.setCursor({line: cursor.line, ch: cursor.ch + 1}); 624 } else if (char === '""') { 625 doc.replaceRange('""', cursor); 626 doc.setCursor({line: cursor.line, ch: cursor.ch + 1}); 627 } else if (char === "''") { 628 doc.replaceRange("''", cursor); 629 doc.setCursor({line: cursor.line, ch: cursor.ch + 1}); 630 } else if (char === "print()") { 631 doc.replaceRange('print()', cursor); 632 doc.setCursor({line: cursor.line, ch: cursor.ch + 6}); 633 } else { 634 doc.replaceRange(char, cursor); 635 } 636 editor.focus(); 637 }); 638 }); 639 640 // --- Download Button --- 641 downloadBtn.addEventListener('click', () => { 642 const code = editor.getValue(); 643 const blob = new Blob([code], { type: 'text/x-python' }); 644 const url = window.URL.createObjectURL(blob); 645 const a = document.createElement('a'); 646 a.href = url; 647 a.download = 'main.py'; 648 document.body.appendChild(a); 649 a.click(); 650 window.URL.revokeObjectURL(url); 651 document.body.removeChild(a); 652 showToast("Downloaded main.py"); 653 }); 654 655 window.addEventListener('load', async () => { 656 const params = new URLSearchParams(window.location.search); 657 const shareId = params.get('share'); 658 const sharedCodeOld = params.get('code'); 659 660 if (shareId) { 661 // Fetch from Firebase 662 try { 663 toast.textContent = "Loading shared code..."; 664 toast.classList.add('show'); 665 666 const docRef = doc(db, "shares", shareId); 667 const docSnap = await getDoc(docRef); 668 669 if (docSnap.exists()) { 670 const data = docSnap.data(); 671 editor.setValue(data.code); 672 673 // Restore Output if it exists 674 if (data.output && term) { 675 term.write('\r\n\x1b[38;2;115;115;115m> PREVIOUS OUTPUT:\x1b[0m\r\n'); 676 term.write(data.output.replace(/\n/g, '\r\n')); 677 term.write('\r\n\x1b[38;2;115;115;115m> ----------------\x1b[0m\r\n'); 678 } 679 680 toast.textContent = "Code loaded successfully"; 681 } else { 682 toast.textContent = "Shared code not found"; 683 } 684 setTimeout(() => toast.classList.remove('show'), 3000); 685 } catch (e) { 686 console.error("Fetch error:", e); 687 if (e.code === 'permission-denied') { 688 toast.textContent = "Error: Database Access Denied"; 689 } else { 690 toast.textContent = "Error loading code"; 691 } 692 setTimeout(() => toast.classList.remove('show'), 3000); 693 } 694 } else if (sharedCodeOld) { 695 // Fallback for Base64 696 try { editor.setValue(atob(sharedCodeOld)); } 697 catch(e) {} 698 } 699 setTimeout(connect, 500); 700 }); 701 702 // --- Terminal Setup --- 703 const term = new Terminal({ 704 theme: { 705 background: '#050505', 706 foreground: '#f5f5f5', 707 cursor: '#bef264', 708 selectionBackground: 'rgba(190, 242, 100, 0.3)', 709 black: '#050505', 710 green: '#bef264', 711 yellow: '#facc15', 712 red: '#ef4444', 713 }, 714 fontFamily: "'JetBrains Mono', monospace", 715 fontSize: 12, 716 cursorBlink: true, 717 convertEol: true 718 }); 719 const fitAddon = new FitAddon.FitAddon(); 720 term.loadAddon(fitAddon); 721 term.open(document.getElementById('terminal-container')); 722 fitAddon.fit(); 723 window.addEventListener('resize', () => fitAddon.fit()); 724 725 // Helper: Get Clean Terminal Output 726 function getTerminalContent() { 727 if (!term) return ""; 728 const buffer = term.buffer.active; 729 let text = ""; 730 for (let i = 0; i < buffer.length; i++) { 731 const line = buffer.getLine(i); 732 if (line) { 733 let lineText = line.translateToString(true); 734 // Filter system messages 735 if(lineText.trim().startsWith("> [") || 736 lineText.trim().startsWith("> python") || 737 lineText.includes("SYSTEM READY") || 738 lineText.includes("ATTEMPTING") || 739 lineText.includes("CONNECTION") || 740 lineText.includes("PREVIOUS OUTPUT") || 741 lineText.includes("> ----------------") 742 ) { 743 continue; 744 } 745 text += lineText + "\n"; 746 } 747 } 748 return text.trim(); 749 } 750 751 // Custom Key Handler for Ctrl+C Copy 752 term.attachCustomKeyEventHandler((arg) => { 753 if (arg.ctrlKey && arg.code === "KeyC" && arg.type === "keydown") { 754 const selection = term.getSelection(); 755 if (selection) { 756 fallbackCopyTextToClipboard(selection); 757 return false; 758 } 759 } 760 return true; 761 }); 762 763 term.write('\x1b[38;2;115;115;115m> SYSTEM READY.\r\n> ATTEMPTING CONNECTION...\x1b[0m\r\n'); 764 765 // --- WebSocket Logic --- 766 function connect() { 767 const url = "wss://compiler-z4x4.onrender.com"; 768 try { socket = new WebSocket(url); } catch (e) { 769 term.write(`\r\n\x1b[31m> INVALID URL.\x1b[0m\r\n`); 770 return; 771 } 772 773 socket.onopen = () => { 774 connDot.className = "pulse-dot connected ml-3"; 775 if(connectionStatus) connectionStatus.textContent = "Connected to Server"; 776 if(connectionStatus) connectionStatus.className = "hidden md:flex items-center gap-2 font-mono text-xs text-lime-300"; 777 778 term.write(`\x1b[38;2;190;242;100m> CONNECTION ESTABLISHED.\x1b[0m\r\n> python3 main.py\r\n`); 779 runBtn.disabled = false; 780 }; 781 782 socket.onclose = () => { 783 connDot.className = "pulse-dot disconnected ml-3"; 784 if(connectionStatus) connectionStatus.textContent = "Disconnected"; 785 if(connectionStatus) connectionStatus.className = "hidden md:flex items-center gap-2 font-mono text-xs text-red-500"; 786 787 term.write(`\r\n\x1b[31m> CONNECTION LOST. RECONNECTING...\x1b[0m\r\n`); 788 runBtn.disabled = true; 789 setTimeout(connect, 3000); 790 socket = null; 791 }; 792 793 socket.onmessage = (event) => { 794 const msg = JSON.parse(event.data); 795 if (msg.type === 'stdout') { 796 let text = msg.data; 797 outputBuffer += text; 798 799 // Trigger AI Overlay on Error 800 if (outputBuffer.toLowerCase().includes("error") || outputBuffer.toLowerCase().includes("traceback")) { 801 aiSuggestion.classList.add('visible'); 802 } 803 804 if (!text.includes('\r')) { 805 text = text.replace(/\n/g, '\r\n'); 806 } 807 term.write(text); 808 } else if (msg.type === 'status') { 809 if (msg.msg.includes("Program finished")) { 810 const duration = (Date.now() - startTime) / 1000; 811 term.write(`\r\n\x1b[38;2;115;115;115m[Finished in ${duration.toFixed(3)}s]\x1b[0m\r\n`); 812 } 813 } else if (msg.type === 'ai_response') { 814 handleAiResponse(msg.data); 815 } else if (msg.type === 'ai_error') { 816 showAiError(msg.msg); 817 } 818 }; 819 } 820 821 // --- Run Logic --- 822 runBtn.addEventListener('click', () => { 823 if (!socket || socket.readyState !== WebSocket.OPEN) return; 824 825 // Force focus out to hide mobile keyboards 826 if (document.activeElement) document.activeElement.blur(); 827 828 term.reset(); 829 currentLine = ""; 830 outputBuffer = ""; 831 aiSuggestion.classList.remove('visible'); 832 833 // Use Editor Value instead of Textarea 834 let code = editor.getValue(); 835 836 code = code.replace(/\u00A0/g, " ").replace(/[\u2018\u2019]/g, "'").replace(/[\u201C\u201D]/g, '"'); 837 838 startTime = Date.now(); 839 term.write('\x1b[32m> python3 main.py\x1b[0m\r\n'); 840 841 socket.send(JSON.stringify({ 842 type: 'run', 843 code: code, 844 language: 'python' 845 })); 846 847 term.focus(); 848 }); 849 850 // --- AI Feature Logic --- 851 852 // 1. Open Modal and Request AI Fix 853 askAiLink.addEventListener('click', () => { 854 const errorContext = getTerminalContent(); 855 const currentCode = editor.getValue(); 856 857 aiModal.classList.add('open'); 858 aiLoading.classList.remove('hidden'); 859 aiResult.classList.add('hidden'); 860 aiErrorMsg.classList.add('hidden'); 861 applyFixBtn.disabled = true; 862 863 socket.send(JSON.stringify({ 864 type: 'ai_fix', 865 code: currentCode, 866 error: errorContext, 867 language: 'python' 868 })); 869 }); 870 871 // 2. Handle AI Response 872 function handleAiResponse(data) { 873 aiLoading.classList.add('hidden'); 874 aiResult.classList.remove('hidden'); 875 876 aiExplanation.textContent = data.explanation; 877 aiCodePreview.textContent = data.fixed_code; 878 lastSuggestedCode = data.fixed_code; 879 880 applyFixBtn.disabled = false; 881 } 882 883 // 3. Handle AI Error 884 function showAiError(msg) { 885 aiLoading.classList.add('hidden'); 886 aiErrorMsg.textContent = msg; 887 aiErrorMsg.classList.remove('hidden'); 888 } 889 890 // 4. Apply Fix 891 applyFixBtn.addEventListener('click', () => { 892 if (lastSuggestedCode) { 893 editor.setValue(lastSuggestedCode); 894 closeAiModal(); 895 showToast("Fix Applied Successfully!"); 896 aiSuggestion.classList.remove('visible'); 897 } 898 }); 899 900 // 5. Modal Controls 901 function closeAiModal() { 902 aiModal.classList.remove('open'); 903 } 904 closeModal.addEventListener('click', closeAiModal); 905 cancelFixBtn.addEventListener('click', closeAiModal); 906 aiModal.addEventListener('click', (e) => { 907 if (e.target === aiModal) closeAiModal(); 908 }); 909 910 // --- Reset Logic --- 911 resetBtn.addEventListener('click', () => { 912 term.reset(); 913 currentLine = ""; 914 outputBuffer = ""; 915 aiSuggestion.classList.remove('visible'); 916 term.write(`\x1b[38;2;190;242;100m> CONNECTION ESTABLISHED.\x1b[0m\r\n> python3 main.py\r\n`); 917 }); 918 919 // --- Share Functionality with Firebase --- 920 shareBtn.addEventListener('click', async () => { 921 const originalIcon = shareBtn.innerHTML; 922 shareBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>'; 923 shareBtn.disabled = true; 924 925 try { 926 if (!auth.currentUser) { 927 try { 928 await signInAnonymously(auth); 929 } catch (authError) { 930 console.warn("Anonymous auth failed:", authError.code); 931 } 932 } 933 934 const code = editor.getValue(); 935 const output = getTerminalContent(); 936 const shareId = generateId(); 937 938 await setDoc(doc(db, "shares", shareId), { 939 code: code, 940 language: 'python', 941 output: output, 942 createdAt: new Date().toISOString() 943 }); 944 945 const url = new URL(window.location.href); 946 url.searchParams.delete('code'); 947 url.searchParams.set('share', shareId); 948 const shareUrl = url.toString(); 949 950 if (navigator.share) { 951 try { 952 await navigator.share({ 953 title: document.title, 954 text: 'Check out my Python code!', 955 url: shareUrl 956 }); 957 } catch (err) { 958 fallbackCopyTextToClipboard(shareUrl); 959 } 960 } else { 961 fallbackCopyTextToClipboard(shareUrl); 962 } 963 } catch (error) { 964 console.error("Share failed:", error); 965 if (error.code === 'permission-denied') { 966 showToast("Share Failed: Access Denied"); 967 } else { 968 showToast("Share Failed: " + error.message); 969 } 970 } finally { 971 shareBtn.innerHTML = originalIcon; 972 shareBtn.disabled = false; 973 } 974 }); 975 976 function showToast(msg) { 977 toast.textContent = msg; 978 toast.classList.add('show'); 979 setTimeout(() => toast.classList.remove('show'), 3000); 980 } 981 982 // --- Smart Copy Function --- 983 copyBtn.addEventListener('click', () => { 984 const text = getTerminalContent(); 985 fallbackCopyTextToClipboard(text); 986 }); 987 988 function fallbackCopyTextToClipboard(text) { 989 const textArea = document.createElement("textarea"); 990 textArea.value = text; 991 textArea.style.position = "fixed"; 992 textArea.style.top = "0"; 993 textArea.style.left = "0"; 994 textArea.style.opacity = "0"; 995 document.body.appendChild(textArea); 996 textArea.focus(); 997 textArea.select(); 998 try { 999 document.execCommand('copy'); 1000 showToast("Copied to Clipboard!"); 1001 } catch (err) { 1002 showToast("Copy Failed"); 1003 } 1004 document.body.removeChild(textArea); 1005 } 1006 1007 // Terminal Input 1008 term.onData((data) => { 1009 if (!socket || socket.readyState !== WebSocket.OPEN) return; 1010 1011 if (data === '\r') { 1012 term.write('\r\n'); 1013 socket.send(JSON.stringify({ type: 'input', data: currentLine + '\n' })); 1014 currentLine = ""; 1015 } else if (data === '\x7f' || data === '\b') { 1016 if (currentLine.length > 0) { 1017 currentLine = currentLine.slice(0, -1); 1018 term.write('\b \b'); 1019 } 1020 } else { 1021 currentLine += data; 1022 term.write(data); 1023 } 1024 }); 1025 </script> 1026 </body> 1027 </html>