cloud-compiler

A real-time, low-latency code ...
Log | Files | Refs | README | LICENSE

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>
© notamitgamer • Site Built: 2026-07-21 13:58:23 UTC • git-mirror commit: 1037f62 [view raw info]
Originally created with stagit • modified by notamitgamer
Forked from github.com/notamitgamer/git-mirror