veyrix

Veyrix IDE is a lightweight, p...
Log | Files | Refs | README | LICENSE

ide.html (114462B)


      1 <!DOCTYPE html>
      2 <html lang="en" class="dark">
      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>Veyrix IDE</title>
      7     
      8     <link rel="manifest" href="assets/manifest.json">
      9     <link rel="icon" type="image/svg+xml" href="assets/veyrix.svg">
     10     <meta name="theme-color" content="#121b2a">
     11     
     12     <!-- Tailwind CSS -->
     13     <link rel="stylesheet" href="assets/style.css">    
     14     <style>
     15         /* Global tap highlight & focus reset */
     16         * {
     17             -webkit-tap-highlight-color: transparent;
     18         }
     19         
     20         :focus:not(:focus-visible) {
     21             outline: none;
     22         }
     23 
     24         :focus-visible {
     25             outline: 2px solid theme('colors.veyrix.accent');
     26             outline-offset: 1px;
     27             border-radius: 2px;
     28         }
     29 
     30         /* Custom UI Scrollbar & Base Styles */
     31         ::-webkit-scrollbar { width: 8px; height: 8px; }
     32         ::-webkit-scrollbar-track { background: transparent; }
     33         ::-webkit-scrollbar-thumb { background: #334155; border-radius: 4px; }
     34         ::-webkit-scrollbar-thumb:hover { background: #475569; }
     35         
     36         body {
     37             background-color: theme('colors.veyrix.bg');
     38             color: theme('colors.veyrix.text');
     39             overflow: hidden;
     40             touch-action: none;
     41         }
     42         
     43         .glass-panel {
     44             background: theme('colors.veyrix.surface');
     45             border: 1px solid theme('colors.veyrix.border');
     46         }
     47 
     48         /* --- SIDEBAR RESPONSIVE LOGIC --- */
     49         #sidebar {
     50             width: 280px;
     51             background-color: theme('colors.veyrix.surface');
     52             border-right: 1px solid theme('colors.veyrix.border');
     53             transition: width 0.25s ease, transform 0.25s ease;
     54             overflow: hidden;
     55             flex-shrink: 0;
     56             z-index: 40;
     57             display: flex;
     58             flex-direction: column;
     59         }
     60         
     61         #sidebar.sidebar-collapsed {
     62             width: 0;
     63             border-right-color: transparent;
     64         }
     65 
     66         @media (max-width: 768px) {
     67             #sidebar {
     68                 position: absolute;
     69                 top: 0;
     70                 bottom: 0;
     71                 left: 0;
     72                 transform: translateX(0);
     73                 box-shadow: 5px 0 25px rgba(0,0,0,0.7);
     74                 border-right: none;
     75             }
     76             #sidebar.sidebar-collapsed {
     77                 width: 280px;
     78                 transform: translateX(-100%);
     79             }
     80         }
     81         
     82         #editor {
     83             position: absolute;
     84             top: 0;
     85             right: 0;
     86             bottom: 0;
     87             left: 0;
     88             border-radius: 0.5rem;
     89             background: theme('colors.veyrix.surface');
     90         }
     91 
     92         .ace_editor { font-family: 'Fira Code', 'Consolas', monospace !important; }
     93         .ace-veyrix-dark {
     94             background-color: theme('colors.veyrix.surface');
     95             color: #d4d4d4;
     96         }
     97         
     98         .modal-enter { animation: modalIn 0.25s cubic-bezier(0.2, 0, 0, 1) forwards; }
     99         .modal-exit { animation: modalOut 0.2s cubic-bezier(0.2, 0, 0, 1) forwards; }
    100         
    101         @keyframes modalIn {
    102             from { opacity: 0; transform: scale(0.95) translateY(10px); }
    103             to { opacity: 1; transform: scale(1) translateY(0); }
    104         }
    105         @keyframes modalOut {
    106             from { opacity: 1; transform: scale(1) translateY(0); }
    107             to { opacity: 0; transform: scale(0.95) translateY(10px); }
    108         }
    109 
    110         /* Jon Suh Hamburger - Spin */
    111         .hamburger {
    112             display: inline-flex;
    113             align-items: center;
    114             justify-content: center;
    115             cursor: pointer;
    116             transition-property: opacity, filter;
    117             transition-duration: 0.15s;
    118             transition-timing-function: linear;
    119             border: 0;
    120             margin: 0;
    121             overflow: visible;
    122             background: transparent;
    123         }
    124         .hamburger:hover { opacity: 0.8; }
    125         .hamburger-box {
    126             width: 20px;
    127             height: 14px;
    128             display: inline-block;
    129             position: relative;
    130         }
    131         .hamburger-inner {
    132             display: block;
    133             top: 50%;
    134             margin-top: -1px;
    135         }
    136         .hamburger-inner, .hamburger-inner::before, .hamburger-inner::after {
    137             width: 20px;
    138             height: 2px;
    139             background-color: white;
    140             border-radius: 4px;
    141             position: absolute;
    142             transition-property: transform;
    143             transition-duration: 0.15s;
    144             transition-timing-function: ease;
    145         }
    146         .hamburger:hover .hamburger-inner, .hamburger:hover .hamburger-inner::before, .hamburger:hover .hamburger-inner::after {
    147             background-color: white;
    148         }
    149         .hamburger.is-active .hamburger-inner, .hamburger.is-active .hamburger-inner::before, .hamburger.is-active .hamburger-inner::after {
    150             background-color: white;
    151         }
    152         .hamburger-inner::before, .hamburger-inner::after { content: ""; display: block; }
    153         .hamburger-inner::before { top: -6px; }
    154         .hamburger-inner::after { bottom: -6px; }
    155 
    156         .hamburger--spin .hamburger-inner { transition-duration: 0.22s; transition-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); }
    157         .hamburger--spin .hamburger-inner::before { transition: top 0.1s 0.25s ease-in, opacity 0.1s ease-in; }
    158         .hamburger--spin .hamburger-inner::after { transition: bottom 0.1s 0.25s ease-in, transform 0.22s cubic-bezier(0.55, 0.055, 0.675, 0.19); }
    159 
    160         .hamburger--spin.is-active .hamburger-inner { transform: rotate(225deg); transition-delay: 0.12s; transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); }
    161         .hamburger--spin.is-active .hamburger-inner::before { top: 0; opacity: 0; transition: top 0.1s ease-out, opacity 0.1s 0.12s ease-out; }
    162         .hamburger--spin.is-active .hamburger-inner::after { bottom: 0; transform: rotate(-90deg); transition: bottom 0.1s ease-out, transform 0.22s 0.12s cubic-bezier(0.215, 0.61, 0.355, 1); }
    163     </style>
    164 
    165     <!-- Dependencies -->
    166     <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.6/ace.js"></script>
    167     <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.6/ext-beautify.js"></script>
    168     <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.6/ext-language_tools.min.js"></script>
    169     <script src="https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.5.0/lz-string.min.js"></script>
    170 </head>
    171 <body class="flex flex-col text-sm antialiased selection:bg-veyrix-accent/30">
    172 
    173     <!-- Top Navigation Bar -->
    174     <header class="h-12 border-b border-veyrix-border bg-veyrix-surface flex items-center justify-between px-2 md:px-3 flex-shrink-0 z-40 relative gap-2">
    175         
    176         <!-- Left Side: Menu + Logo -->
    177         <div class="flex items-center gap-2 md:gap-3 shrink-0">
    178             <button class="hamburger hamburger--spin sidebar-toggle-btn p-1.5 rounded transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent" type="button" aria-expanded="false" aria-controls="sidebar" aria-label="Toggle menu" onclick="UI.toggleSidebar()">
    179                 <span class="hamburger-box">
    180                     <span class="hamburger-inner"></span>
    181                 </span>
    182             </button>
    183             
    184             <!-- Desktop Logo -->
    185             <div class="hidden md:flex items-center gap-2">
    186                 <img src="assets/veyrix.svg" alt="Veyrix Logo" class="w-6 h-6">
    187                 <span class="font-semibold text-white tracking-wide">Veyrix IDE</span>
    188             </div>
    189             
    190             <!-- Mobile Logo (Stacked) -->
    191             <div class="flex flex-col items-center justify-center md:hidden leading-[1.1] ml-0.5">
    192                 <img src="assets/veyrix.svg" alt="Veyrix Logo" class="w-[18px] h-[18px]">
    193                 <span class="font-bold text-white tracking-wider text-[8px] uppercase mt-[1px]">Veyrix</span>
    194             </div>
    195             
    196             <div class="h-5 w-px bg-veyrix-border mx-2 hidden md:block"></div>
    197 
    198         </div>
    199 
    200         <!-- Center: Active File Display (Dynamic Width) -->
    201         <div class="flex-1 flex justify-start md:justify-center min-w-0 overflow-hidden">
    202             <div id="top-active-file" class="hidden items-center gap-1.5 bg-black/30 px-3 py-1 rounded-full border border-white/10 max-w-full overflow-hidden whitespace-nowrap text-veyrix-text font-mono text-xs">
    203                 <span id="top-file-name" class="truncate">No file open</span>
    204                 <span id="top-file-unsaved" class="w-2 h-2 rounded-full bg-veyrix-accent hidden shrink-0"></span>
    205             </div>
    206         </div>
    207 
    208         <!-- Right Side: Action Buttons -->
    209         <div class="flex items-center gap-0.5 md:gap-1 shrink-0">
    210             <button onclick="document.getElementById('file-upload').click()" class="p-1 md:p-1.5 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors tooltip-trigger" title="Open File (Ctrl+O)">
    211                 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 19a2 2 0 01-2-2V7a2 2 0 012-2h4l2 2h4a2 2 0 012 2v1M5 19h14a2 2 0 002-2v-5a2 2 0 00-2-2H9a2 2 0 00-2 2v5a2 2 0 01-2 2z"></path></svg>
    212             </button>
    213             <button onclick="App.saveCurrentFile()" class="p-1 md:p-1.5 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors tooltip-trigger" title="Save (Ctrl+S)">
    214                 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"></path></svg>
    215             </button>
    216             <button onclick="App.triggerDownload()" class="p-1 md:p-1.5 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors tooltip-trigger" title="Download File">
    217                 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg>
    218             </button>
    219             <button onclick="App.triggerSnapshot()" class="p-1 md:p-1.5 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors tooltip-trigger" title="Save Snapshot">
    220                 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"></path></svg>
    221             </button>
    222             <button onclick="UI.showHistory()" class="p-1 md:p-1.5 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors tooltip-trigger" title="View History">
    223                 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
    224             </button>
    225             <button onclick="UI.showModal('share-modal')" class="p-1 md:hidden rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors tooltip-trigger" title="Share">
    226                 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path></svg>
    227             </button>
    228             <button onclick="UI.showModal('share-modal')" class="hidden md:flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg bg-veyrix-accent/10 text-veyrix-accent hover:bg-veyrix-accent/20 transition-colors border border-veyrix-accent/20 md:ml-1">
    229                 <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path></svg>
    230                 Share
    231             </button>
    232             <button onclick="UI.showModal('help-modal')" class="p-1 md:p-1.5 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors tooltip-trigger md:ml-1" title="Help & Shortcuts">
    233                 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
    234             </button>
    235             <button onclick="App.toggleFullscreen()" class="hidden md:block p-1.5 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors tooltip-trigger" title="Fullscreen (F11)">
    236                 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg>
    237             </button>
    238             <button onclick="UI.showModal('settings-modal')" class="p-1 md:p-1.5 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors tooltip-trigger" title="Settings">
    239                 <svg class="w-4 h-4 md:w-4 md:h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
    240             </button>
    241         </div>
    242     </header>
    243 
    244     <!-- Main Workspace -->
    245     <main class="flex-1 flex overflow-hidden relative z-10 w-full" id="main-workspace">
    246         
    247         <!-- Mobile Sidebar Backdrop -->
    248         <div id="sidebar-backdrop" class="fixed inset-0 bg-black/60 z-30 hidden md:hidden transition-opacity duration-250 opacity-0"></div>
    249 
    250         <!-- Sidebar / File Explorer -->
    251         <aside id="sidebar" class="sidebar-collapsed bg-veyrix-surface">
    252             <div class="p-3 border-b border-veyrix-border flex items-center justify-between">
    253                 <span class="text-xs font-bold text-veyrix-muted uppercase tracking-wider">Explorer</span>
    254                 <div class="flex gap-1">
    255                     <button onclick="UI.showModal('new-file-modal')" class="p-1 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors" title="New File">
    256                         <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
    257                     </button>
    258                 </div>
    259             </div>
    260             <div id="file-list" class="flex-1 overflow-y-auto p-2 space-y-0.5 relative">
    261                 <!-- File items injected here -->
    262             </div>
    263             
    264             <!-- Mobile Sidebar Footer -->
    265             <div class="md:hidden p-3 border-t border-veyrix-border flex justify-between">
    266                 <button onclick="UI.toggleSidebar(true)" class="text-xs text-veyrix-muted hover:text-white flex items-center gap-1 w-full justify-center bg-white/5 hover:bg-white/10 py-2 rounded-lg transition-colors">
    267                     <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
    268                     Close Sidebar
    269                 </button>
    270             </div>
    271         </aside>
    272 
    273         <!-- Editor Area -->
    274         <section id="editor-section" class="flex-1 relative p-2 md:p-4 bg-veyrix-bg flex flex-col min-w-0">
    275             <!-- Security/Share Warning Banner -->
    276             <div id="banner-warning" class="hidden mb-2 bg-amber-500/10 border border-amber-500/20 text-amber-200 text-xs px-3 py-2 rounded-lg flex items-center justify-between">
    277                 <div class="flex items-center gap-2">
    278                     <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>
    279                     <span id="banner-text">Viewing shared file. Edits won't sync automatically.</span>
    280                 </div>
    281                 <button onclick="this.parentElement.classList.add('hidden')" class="hover:text-amber-100">×</button>
    282             </div>
    283             
    284             <div class="flex-1 relative rounded-lg overflow-hidden border border-veyrix-border shadow-2xl glass-panel">
    285                 <div id="editor-placeholder" class="absolute inset-0 flex flex-col items-center justify-center text-veyrix-muted bg-veyrix-surface z-10">
    286                     <img src="assets/veyrix.svg" alt="Veyrix" class="w-20 h-20 mb-6 opacity-20 grayscale">
    287                     <p class="text-sm font-medium text-veyrix-text">Ready to build something?</p>
    288                     <div class="mt-6 flex gap-4 text-xs text-veyrix-muted">
    289                         <button onclick="UI.showModal('new-file-modal')" class="px-3 py-1.5 rounded-md bg-white/5 hover:bg-white/10 border border-white/10 text-veyrix-text transition-colors">Create New File</button>
    290                     </div>
    291                     <div id="app-version-display" class="absolute bottom-4 text-[10px] font-mono text-veyrix-muted/30 select-none"></div>
    292                 </div>
    293                 <div id="editor"></div>
    294             </div>
    295         </section>
    296 
    297         <!-- Floating Action Button for Mobile -->
    298         <button onclick="UI.showModal('new-file-modal')" class="md:hidden fixed bottom-6 right-6 w-14 h-14 bg-veyrix-accent hover:bg-veyrix-accentHover rounded-full shadow-lg flex items-center justify-center text-white z-20 transition-transform active:scale-95">
    299             <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
    300         </button>
    301     </main>
    302 
    303     <!-- Global Dropdown Menu (Z-index top priority) -->
    304     <div id="global-file-dropdown" class="hidden absolute w-40 bg-slate-800 border border-slate-700 rounded-lg shadow-xl z-[100] flex-col py-1 transition-opacity duration-150 opacity-0 scale-95 origin-top-right">
    305         <button id="dd-rename" class="flex items-center gap-2 w-full text-left px-3 py-2 text-xs text-slate-200 hover:bg-slate-700 hover:text-white transition-colors">
    306             <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"></path></svg> Rename
    307         </button>
    308         <button id="dd-download" class="flex items-center gap-2 w-full text-left px-3 py-2 text-xs text-slate-200 hover:bg-slate-700 hover:text-white transition-colors">
    309             <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg> Download
    310         </button>
    311         <button id="dd-share" class="flex items-center gap-2 w-full text-left px-3 py-2 text-xs text-slate-200 hover:bg-slate-700 hover:text-white transition-colors">
    312             <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path></svg> Share
    313         </button>
    314         <div class="h-px bg-slate-700 my-1 mx-2"></div>
    315         <button id="dd-delete" class="flex items-center gap-2 w-full text-left px-3 py-2 text-xs text-red-400 hover:bg-slate-700 hover:text-red-300 transition-colors">
    316             <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg> Delete
    317         </button>
    318     </div>
    319 
    320     <!-- Hidden File Input for Open -->
    321     <input type="file" id="file-upload" class="hidden" accept=".txt,.js,.html,.css,.json,.md,.xml,.csv,.log,.sh,.py,.yml,.yaml,.ini,.env,text/*" onchange="App.handleFileUpload(event)">
    322 
    323     <!-- TOAST CONTAINER -->
    324     <div id="toast-container" class="fixed bottom-16 md:bottom-6 right-4 md:right-6 z-50 flex flex-col gap-2 pointer-events-none"></div>
    325 
    326     <!-- MODALS OVERLAY -->
    327     <div id="modal-overlay" class="fixed inset-0 bg-black/60 z-50 hidden items-start pt-16 md:pt-0 md:items-center justify-center p-4 overflow-y-auto">
    328         
    329         <!-- Generic Confirm Modal -->
    330         <div id="confirm-modal" class="hidden bg-veyrix-surface border border-veyrix-border rounded-lg w-full max-w-sm shadow-2xl modal-content pointer-events-auto flex flex-col overflow-hidden mb-auto md:mb-0 mt-4 md:mt-0">
    331             <div class="p-5 space-y-4 text-center">
    332                 <div class="w-12 h-12 rounded-full bg-veyrix-accent/10 text-veyrix-accent flex items-center justify-center mx-auto mb-2" id="confirm-icon-bg">
    333                     <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>
    334                 </div>
    335                 <h3 class="font-semibold text-white" id="confirm-title">Confirm Action</h3>
    336                 <p class="text-xs text-veyrix-muted" id="confirm-message">Are you sure?</p>
    337                 <div class="flex gap-2 justify-center pt-2">
    338                     <button onclick="UI.hideModal()" class="flex-1 py-2 bg-white/5 hover:bg-white/10 text-white rounded-lg text-sm transition-colors">Cancel</button>
    339                     <button id="confirm-action-btn" class="flex-1 py-2 bg-veyrix-accent hover:bg-veyrix-accentHover text-white rounded-lg text-sm font-medium transition-colors">Confirm</button>
    340                 </div>
    341             </div>
    342         </div>
    343 
    344         <!-- Snapshot Limit Modal -->
    345         <div id="snapshot-limit-modal" class="hidden bg-veyrix-surface border border-veyrix-border rounded-lg w-full max-w-sm shadow-2xl modal-content pointer-events-auto flex flex-col overflow-hidden mb-auto md:mb-0 mt-4 md:mt-0">
    346             <div class="p-4 border-b border-veyrix-border flex justify-between items-center bg-veyrix-bg">
    347                 <h3 class="font-semibold text-white">Snapshot Limit Reached</h3>
    348                 <button onclick="UI.hideModal()" class="text-veyrix-muted hover:text-white transition-colors">✕</button>
    349             </div>
    350             <div class="p-5 space-y-4">
    351                 <p class="text-sm text-veyrix-text">You have reached the maximum limit of 5 snapshots for this file.</p>
    352                 <div class="bg-white/5 border border-white/10 rounded-lg p-3">
    353                     <p class="text-xs text-veyrix-muted mb-1">Oldest Snapshot:</p>
    354                     <p class="text-sm font-mono text-white" id="snapshot-limit-oldest-info"></p>
    355                 </div>
    356                 <p class="text-xs text-amber-400">Creating a new snapshot will permanently delete the oldest one.</p>
    357             </div>
    358             <div class="p-4 border-t border-veyrix-border bg-black/20 flex justify-end gap-2">
    359                 <button onclick="UI.hideModal()" class="px-4 py-2 text-sm rounded-lg hover:bg-white/5 text-veyrix-text transition-colors">Cancel</button>
    360                 <button onclick="App.executeSnapshotSave(true)" class="px-4 py-2 text-sm rounded-lg bg-veyrix-accent text-white hover:bg-veyrix-accentHover transition-colors font-medium">Delete Oldest & Continue</button>
    361             </div>
    362         </div>
    363 
    364         <!-- Help & Shortcuts Modal -->
    365         <div id="help-modal" class="hidden bg-veyrix-surface border border-veyrix-border rounded-lg w-full max-w-md shadow-2xl modal-content pointer-events-auto flex flex-col overflow-hidden mb-auto md:mb-0 mt-4 md:mt-0 max-h-[90vh]">
    366             <div class="p-4 border-b border-veyrix-border flex justify-between items-center bg-veyrix-bg shrink-0">
    367                 <h3 class="font-semibold text-white flex items-center gap-2">
    368                     <svg class="w-5 h-5 text-veyrix-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
    369                     Help & Shortcuts
    370                 </h3>
    371                 <button onclick="UI.hideModal()" class="text-veyrix-muted hover:text-white transition-colors">✕</button>
    372             </div>
    373             <div class="p-5 overflow-y-auto space-y-6">
    374                 <div class="bg-veyrix-accent/10 border border-veyrix-accent/20 rounded-lg p-3 text-sm text-veyrix-accent">
    375                 <div class="flex flex-wrap items-center gap-x-2 leading-relaxed">
    376                     <svg class="w-4 h-4 opacity-80" fill="none" stroke="currentColor" viewBox="0 0 24 24">
    377                     <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
    378                     </svg>
    379 
    380                     <span class="font-medium">Recommendation:</span>
    381                     
    382                     <span class="flex items-center gap-1.5">
    383                     Use <strong>Fullscreen</strong> 
    384                     <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
    385                         <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path>
    386                     </svg>
    387                     </span>
    388 
    389                     <span>or press</span>
    390 
    391                     <kbd class="px-2 py-0.5 text-[11px] font-bold bg-veyrix-accent/25 border-b-2 border-veyrix-accent/40 rounded text-veyrix-accent">
    392                     F11
    393                     </kbd>
    394 
    395                     <span class="opacity-90">for the best IDE experience.</span>
    396                     
    397                     
    398                 </div>
    399                 </div>
    400                 <div class="bg-veyrix-accent/10 border border-veyrix-accent/20 rounded-lg p-3 text-sm text-veyrix-accent leading-relaxed">
    401   <!-- Info Icon - inline-block keeps it in the flow -->
    402   <svg class="w-4 h-4 inline-block mr-1.5 opacity-80 -mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
    403     <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
    404   </svg>
    405 
    406   <span>Install Veyrix IDE on your device for a faster experience with offline capabilities.<br>Go to</span>
    407 
    408   <!-- Action Group: Locked together so they never break apart -->
    409   <span class="inline-flex items-center gap-1 ml-1">
    410     <svg class="w-3.5 h-3.5 opacity-90" fill="none" stroke="currentColor" viewBox="0 0 24 24">
    411       <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
    412       <circle cx="12" cy="12" r="3" stroke-width="2"></circle>
    413     </svg>
    414     <span class="mx-0.5 opacity-80">→</span>
    415     <strong class="font-bold">Install Web App</strong>
    416   </span>
    417 </div>
    418 
    419                 <section>
    420                     <h4 class="text-sm font-semibold text-white border-b border-veyrix-border pb-1 mb-3">Icon Guide & Usage</h4>
    421                     <ul class="space-y-4 text-xs text-veyrix-muted">
    422                         <li class="flex items-start gap-3">
    423                             <div class="p-1.5 bg-white/5 rounded text-white shrink-0"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 19a2 2 0 01-2-2V7a2 2 0 012-2h4l2 2h4a2 2 0 012 2v1M5 19h14a2 2 0 002-2v-5a2 2 0 00-2-2H9a2 2 0 00-2 2v5a2 2 0 01-2 2z"></path></svg></div>
    424                             <div><strong class="text-white block mb-0.5">Open File</strong> Load a text or code file from your device into the IDE.</div>
    425                         </li>
    426                         <li class="flex items-start gap-3">
    427                             <div class="p-1.5 bg-white/5 rounded text-white shrink-0"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"></path></svg></div>
    428                             <div><strong class="text-white block mb-0.5">Save</strong> Manually save the current file to your browser's local storage.</div>
    429                         </li>
    430                         <li class="flex items-start gap-3">
    431                             <div class="p-1.5 bg-white/5 rounded text-white shrink-0"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg></div>
    432                             <div><strong class="text-white block mb-0.5">Download</strong> Download the file to your device.</div>
    433                         </li>
    434                         <li class="flex items-start gap-3">
    435                             <div class="p-1.5 bg-white/5 rounded text-white shrink-0"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"></path></svg></div>
    436                             <div><strong class="text-white block mb-0.5">Save Snapshot</strong> Create a versioned backup of your current file (maximum 5 snapshots per file).</div>
    437                         </li>
    438                         <li class="flex items-start gap-3">
    439                             <div class="p-1.5 bg-white/5 rounded text-white shrink-0"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg></div>
    440                             <div><strong class="text-white block mb-0.5">View History</strong> View all your saved snapshots and safely restore older versions.</div>
    441                         </li>
    442                         <li class="flex items-start gap-3">
    443                             <div class="p-1.5 bg-white/5 rounded text-white shrink-0"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path></svg></div>
    444                             <div><strong class="text-white block mb-0.5">Share</strong> Share your code directly via device, fast local link, or cloud upload.</div>
    445                         </li>
    446                     </ul>
    447                 </section>
    448 
    449                 <section>
    450                     <h4 class="text-sm font-semibold text-white border-b border-veyrix-border pb-1 mb-2">Keyboard Shortcuts</h4>
    451                     <div class="grid grid-cols-2 gap-2 text-xs">
    452                         <div class="text-veyrix-muted flex justify-between pr-2 border-r border-white/5"><span>Save File</span> <kbd class="px-1 py-0.5 rounded bg-white/10 text-white">Ctrl+S</kbd></div>
    453                         <div class="text-veyrix-muted flex justify-between pl-2"><span>Open File</span> <kbd class="px-1 py-0.5 rounded bg-white/10 text-white">Ctrl+O</kbd></div>
    454                         <div class="text-veyrix-muted flex justify-between pr-2 border-r border-white/5"><span>Format Code</span> <kbd class="px-1 py-0.5 rounded bg-white/10 text-white">Alt+Shift+F</kbd></div>
    455                         <div class="text-veyrix-muted flex justify-between pl-2"><span>Fullscreen</span> <kbd class="px-1 py-0.5 rounded bg-white/10 text-white">F11</kbd></div>
    456                         <div class="text-veyrix-muted flex justify-between pr-2 border-r border-white/5"><span>Close Modal</span> <kbd class="px-1 py-0.5 rounded bg-white/10 text-white">Esc</kbd></div>
    457                     </div>
    458                 </section>
    459             </div>
    460         </div>
    461 
    462         <!-- Create New File Modal -->
    463         <div id="new-file-modal" class="hidden bg-veyrix-surface border border-veyrix-border rounded-lg w-full max-w-sm shadow-2xl modal-content pointer-events-auto flex flex-col overflow-hidden mb-auto md:mb-0 mt-4 md:mt-0">
    464             <div class="p-4 border-b border-veyrix-border flex justify-between items-center bg-veyrix-bg">
    465                 <h3 class="font-semibold text-white">Create New File</h3>
    466                 <button onclick="UI.hideModal()" class="text-veyrix-muted hover:text-white transition-colors">✕</button>
    467             </div>
    468             <div class="p-5 space-y-4">
    469                 <div>
    470                     <label class="block text-xs font-medium text-veyrix-muted mb-1.5">Filename</label>
    471                     <input type="text" id="new-file-input" placeholder="e.g., app.js, Dockerfile, .env" 
    472                            class="w-full bg-veyrix-bg border border-veyrix-border rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent transition-all">
    473                     <p class="text-[10px] text-veyrix-muted mt-1.5">No extension required. Recognized files will be auto-formatted.</p>
    474                 </div>
    475                 
    476                 <div id="web-editor-recommendation" class="hidden animate-fade-in bg-veyrix-accent/10 border border-veyrix-accent/20 rounded-lg p-3">
    477                     <p class="text-xs text-veyrix-accent mb-2">Better experience for HTML, CSS & JavaScript projects.</p>
    478                     <button onclick="App.openInWebEditor()" class="w-full py-1.5 bg-veyrix-surface hover:bg-white/5 border border-white/10 rounded text-xs text-veyrix-text transition-colors flex items-center justify-center gap-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent">
    479                         <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path></svg>
    480                         Open in Web Editor (Recommended)
    481                     </button>
    482                 </div>
    483             </div>
    484             <div class="p-4 border-t border-veyrix-border bg-black/20 flex justify-end gap-2">
    485                 <button onclick="UI.hideModal()" class="px-4 py-2 text-sm rounded-lg hover:bg-white/5 text-veyrix-text transition-colors">Cancel</button>
    486                 <button onclick="App.createNewFile()" class="px-4 py-2 text-sm rounded-lg bg-veyrix-accent text-white hover:bg-veyrix-accentHover transition-colors font-medium">Create</button>
    487             </div>
    488         </div>
    489 
    490         <!-- Rename File Modal -->
    491         <div id="rename-file-modal" class="hidden bg-veyrix-surface border border-veyrix-border rounded-lg w-full max-w-sm shadow-2xl modal-content pointer-events-auto flex flex-col overflow-hidden mb-auto md:mb-0 mt-4 md:mt-0">
    492             <div class="p-4 border-b border-veyrix-border flex justify-between items-center bg-veyrix-bg">
    493                 <h3 class="font-semibold text-white">Rename File</h3>
    494                 <button onclick="UI.hideModal()" class="text-veyrix-muted hover:text-white transition-colors">✕</button>
    495             </div>
    496             <div class="p-5 space-y-4">
    497                 <div>
    498                     <label class="block text-xs font-medium text-veyrix-muted mb-1.5">New Filename</label>
    499                     <input type="text" id="rename-file-input" placeholder="e.g., app.js, Dockerfile, .env" 
    500                            class="w-full bg-veyrix-bg border border-veyrix-border rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent transition-all">
    501                 </div>
    502             </div>
    503             <div class="p-4 border-t border-veyrix-border bg-black/20 flex justify-end gap-2">
    504                 <button onclick="UI.hideModal()" class="px-4 py-2 text-sm rounded-lg hover:bg-white/5 text-veyrix-text transition-colors">Cancel</button>
    505                 <button onclick="App.executeRename()" class="px-4 py-2 text-sm rounded-lg bg-veyrix-accent text-white hover:bg-veyrix-accentHover transition-colors font-medium">Rename</button>
    506             </div>
    507         </div>
    508 
    509         <!-- Share Modal -->
    510         <div id="share-modal" class="hidden bg-veyrix-surface border border-veyrix-border rounded-lg w-full max-w-md shadow-2xl modal-content pointer-events-auto flex flex-col overflow-hidden mb-auto md:mb-0 mt-4 md:mt-0 max-h-[90vh]">
    511             <div class="p-4 border-b border-veyrix-border flex justify-between items-center bg-veyrix-bg shrink-0">
    512                 <h3 class="font-semibold text-white">Share File</h3>
    513                 <button onclick="UI.hideModal()" class="text-veyrix-muted hover:text-white transition-colors">✕</button>
    514             </div>
    515             
    516             <div class="flex border-b border-veyrix-border shrink-0">
    517                 <button onclick="UI.switchShareTab('local')" id="tab-btn-local" class="flex-1 py-3 text-sm font-medium border-b-2 border-veyrix-accent text-veyrix-accent transition-colors">Fast Local Share</button>
    518                 <button onclick="UI.switchShareTab('cloud')" id="tab-btn-cloud" class="flex-1 py-3 text-sm font-medium border-b-2 border-transparent text-veyrix-muted hover:text-white transition-colors">Cloud Share</button>
    519             </div>
    520 
    521             <div class="overflow-y-auto flex-1">
    522                 <div class="p-5">
    523                     <!-- Local Share Tab -->
    524                     <div id="tab-local" class="space-y-4">
    525                         <p class="text-xs text-veyrix-muted">Generates a URL containing the compressed code. No data is stored on servers. URL might be long.</p>
    526                         <button onclick="Cloud.generateLocalShare()" class="w-full py-2 bg-white/5 hover:bg-white/10 border border-white/10 rounded-lg text-sm text-veyrix-text transition-colors flex items-center justify-center gap-2">
    527                             <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path></svg>
    528                             Generate Local Link
    529                         </button>
    530                         <div id="local-share-result" class="hidden mt-3">
    531                             <input type="text" id="local-share-url" readonly class="w-full bg-veyrix-bg border border-veyrix-border rounded-lg px-3 py-2 text-xs text-veyrix-muted focus:outline-none mb-2">
    532                             <button onclick="UI.copyToClipboard('local-share-url')" class="w-full py-2 bg-veyrix-accent/20 text-veyrix-accent rounded-lg text-sm font-medium hover:bg-veyrix-accent/30 transition-colors">Copy Link</button>
    533                         </div>
    534                     </div>
    535 
    536                     <!-- Cloud Share Tab -->
    537                     <div id="tab-cloud" class="space-y-4 hidden">
    538                         <div class="bg-blue-500/10 border border-blue-500/20 p-3 rounded-lg flex gap-3 text-sm">
    539                             <svg class="w-5 h-5 text-blue-400 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z"></path></svg>
    540                             <p class="text-blue-200 text-xs">Saves to Firebase Firestore. Firebase initializes ONLY when you use this feature.</p>
    541                         </div>
    542                         
    543                         <div>
    544                             <label class="block text-xs font-medium text-veyrix-muted mb-1.5">Optional Password Protection</label>
    545                             <input type="password" id="cloud-password" placeholder="Leave blank for public access" 
    546                                 class="w-full bg-veyrix-bg border border-veyrix-border rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent transition-all">
    547                         </div>
    548 
    549                         <button onclick="Cloud.generateCloudShare()" id="btn-cloud-share" class="w-full py-2 bg-veyrix-accent hover:bg-veyrix-accentHover text-white rounded-lg text-sm font-medium transition-colors flex items-center justify-center gap-2">
    550                             <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path></svg>
    551                             Upload & Share
    552                         </button>
    553                         
    554                         <div id="cloud-share-result" class="hidden mt-3">
    555                             <input type="text" id="cloud-share-url" readonly class="w-full bg-veyrix-bg border border-veyrix-border rounded-lg px-3 py-2 text-xs text-veyrix-muted focus:outline-none mb-2">
    556                             <button onclick="UI.copyToClipboard('cloud-share-url')" class="w-full py-2 bg-veyrix-accent/20 text-veyrix-accent rounded-lg text-sm font-medium hover:bg-veyrix-accent/30 transition-colors">Copy Link</button>
    557                         </div>
    558                     </div>
    559                 </div>
    560             </div>
    561 
    562             <!-- Native Web Share API Section -->
    563             <div id="native-share-section" class="hidden border-t border-veyrix-border bg-black/20 p-5 mt-auto shrink-0">
    564                 <h4 class="text-sm font-semibold text-white mb-3">Quick Share (Device Share)</h4>
    565                 <button onclick="Cloud.nativeShare()" class="w-full py-2 bg-veyrix-surface hover:bg-white/5 border border-white/10 rounded-lg text-sm text-veyrix-text transition-colors flex items-center justify-center gap-2">
    566                     <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path></svg>
    567                     Share via Device
    568                 </button>
    569             </div>
    570         </div>
    571 
    572         <!-- Password Prompt Modal -->
    573         <div id="password-modal" class="hidden bg-veyrix-surface border border-veyrix-border rounded-lg w-full max-w-sm shadow-2xl modal-content pointer-events-auto flex flex-col overflow-hidden mb-auto md:mb-0 mt-4 md:mt-0">
    574             <div class="p-5 space-y-4 text-center">
    575                 <div class="w-12 h-12 rounded-full bg-amber-500/10 text-amber-500 flex items-center justify-center mx-auto mb-2">
    576                     <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path></svg>
    577                 </div>
    578                 <h3 class="font-semibold text-white">Protected File</h3>
    579                 <p class="text-xs text-veyrix-muted">This shared file requires a password.</p>
    580                 <input type="password" id="access-password" placeholder="Enter password" 
    581                        class="w-full bg-veyrix-bg border border-veyrix-border rounded-lg px-3 py-2 text-sm text-center text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent transition-all">
    582                 <button onclick="Cloud.verifyAndLoadCloudFile()" class="w-full py-2 bg-veyrix-accent text-white rounded-lg text-sm font-medium hover:bg-veyrix-accentHover transition-colors">Unlock</button>
    583             </div>
    584         </div>
    585 
    586         <!-- Settings & Data Security Modal -->
    587         <div id="settings-modal" class="hidden bg-veyrix-surface border border-veyrix-border rounded-lg w-full max-w-lg shadow-2xl modal-content pointer-events-auto flex flex-col overflow-hidden max-h-[90vh] mb-auto md:mb-0 mt-4 md:mt-0">
    588             <div class="p-4 border-b border-veyrix-border flex justify-between items-center bg-veyrix-bg sticky top-0 z-10">
    589                 <h3 class="font-semibold text-white flex items-center gap-2">
    590                     <svg class="w-5 h-5 text-veyrix-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path></svg>
    591                     Settings & Data Panel
    592                 </h3>
    593                 <button onclick="UI.hideModal()" class="text-veyrix-muted hover:text-white transition-colors">✕</button>
    594             </div>
    595             <div class="p-5 overflow-y-auto space-y-6">
    596                 
    597                 <!-- App Installation -->
    598                 <section class="space-y-3">
    599                     <h4 class="text-sm font-semibold text-white">App Installation</h4>
    600                     <div class="bg-white/5 border border-white/10 rounded-lg p-3 text-xs text-veyrix-muted space-y-2 flex flex-col sm:flex-row justify-between sm:items-center gap-3">
    601                         <p>Install Veyrix IDE on your device for a faster, app-like experience with offline capabilities.</p>
    602                         <button id="install-app-btn" onclick="App.installApp()" disabled class="shrink-0 px-4 py-2 bg-veyrix-accent text-white rounded-lg text-sm font-medium hover:bg-veyrix-accentHover transition-colors opacity-50 cursor-not-allowed">Install Web App</button>
    603                     </div>
    604                 </section>
    605 
    606                 <!-- Frequently Asked Questions -->
    607                 <section class="space-y-3">
    608                     <h4 class="text-sm font-semibold text-white">Frequently Asked Questions</h4>
    609                     <div class="space-y-2">
    610                         
    611                         <div class="border border-white/10 rounded-lg overflow-hidden">
    612                             <button class="w-full text-left p-3 bg-white/5 text-sm font-medium text-white flex justify-between items-center hover:bg-white/10 transition-colors" onclick="this.nextElementSibling.classList.toggle('hidden'); this.querySelector('svg').classList.toggle('rotate-180');">
    613                                 Where are my files stored?
    614                                 <svg class="w-4 h-4 text-veyrix-muted transform transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
    615                             </button>
    616                             <div class="p-3 text-xs text-veyrix-muted hidden bg-black/20 border-t border-white/5">
    617                                 All your files are stored locally on your device inside your browser's secure IndexedDB. They are never uploaded to any server unless you specifically use the Cloud Share tool.
    618                             </div>
    619                         </div>
    620 
    621                         <div class="border border-white/10 rounded-lg overflow-hidden">
    622                             <button class="w-full text-left p-3 bg-white/5 text-sm font-medium text-white flex justify-between items-center hover:bg-white/10 transition-colors" onclick="this.nextElementSibling.classList.toggle('hidden'); this.querySelector('svg').classList.toggle('rotate-180');">
    623                                 What is Fast Share vs Cloud Share?
    624                                 <svg class="w-4 h-4 text-veyrix-muted transform transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
    625                             </button>
    626                             <div class="p-3 text-xs text-veyrix-muted hidden bg-black/20 border-t border-white/5">
    627                                 <strong>Fast Share</strong> compresses your code using Lempel-Ziv and puts it entirely in the URL without using any server. <strong>Cloud Share</strong> uploads your code to an anonymous Firebase Firestore database, giving you a shorter, cleaner link.
    628                             </div>
    629                         </div>
    630 
    631                         <div class="border border-white/10 rounded-lg overflow-hidden">
    632                             <button class="w-full text-left p-3 bg-white/5 text-sm font-medium text-white flex justify-between items-center hover:bg-white/10 transition-colors" onclick="this.nextElementSibling.classList.toggle('hidden'); this.querySelector('svg').classList.toggle('rotate-180');">
    633                                 Is my data secure?
    634                                 <svg class="w-4 h-4 text-veyrix-muted transform transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
    635                             </button>
    636                             <div class="p-3 text-xs text-veyrix-muted hidden bg-black/20 border-t border-white/5">
    637                                 Yes. Because files are local, no one can see them. If you use Cloud Share, you can add a password. Passwords are securely hashed using the Web Crypto API (SHA-256) locally before being checked against the database.
    638                             </div>
    639                         </div>
    640 
    641                         <div class="border border-white/10 rounded-lg overflow-hidden">
    642                             <button class="w-full text-left p-3 bg-white/5 text-sm font-medium text-white flex justify-between items-center hover:bg-white/10 transition-colors" onclick="this.nextElementSibling.classList.toggle('hidden'); this.querySelector('svg').classList.toggle('rotate-180');">
    643                                 How many snapshots can I take?
    644                                 <svg class="w-4 h-4 text-veyrix-muted transform transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
    645                             </button>
    646                             <div class="p-3 text-xs text-veyrix-muted hidden bg-black/20 border-t border-white/5">
    647                                 You can save a maximum of 5 snapshots per file. If you try to save a 6th snapshot, you will be prompted to replace the oldest one.
    648                             </div>
    649                         </div>
    650 
    651                         <div class="border border-white/10 rounded-lg overflow-hidden">
    652                             <button class="w-full text-left p-3 bg-white/5 text-sm font-medium text-white flex justify-between items-center hover:bg-white/10 transition-colors" onclick="this.nextElementSibling.classList.toggle('hidden'); this.querySelector('svg').classList.toggle('rotate-180');">
    653                                 Does Veyrix IDE work offline?
    654                                 <svg class="w-4 h-4 text-veyrix-muted transform transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
    655                             </button>
    656                             <div class="p-3 text-xs text-veyrix-muted hidden bg-black/20 border-t border-white/5">
    657                                 Yes! Once loaded for the first time, Veyrix operates entirely offline using PWA Service Workers. Note: Cloud Share requires an internet connection.
    658                             </div>
    659                         </div>
    660 
    661                         <div class="border border-white/10 rounded-lg overflow-hidden">
    662                             <button class="w-full text-left p-3 bg-white/5 text-sm font-medium text-white flex justify-between items-center hover:bg-white/10 transition-colors" onclick="this.nextElementSibling.classList.toggle('hidden'); this.querySelector('svg').classList.toggle('rotate-180');">
    663                                 Can I create custom file extensions?
    664                                 <svg class="w-4 h-4 text-veyrix-muted transform transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
    665                             </button>
    666                             <div class="p-3 text-xs text-veyrix-muted hidden bg-black/20 border-t border-white/5">
    667                                 Absolutely. When creating a file, you can type any extension (e.g., <code>project.custom</code>) or no extension at all (e.g., <code>Dockerfile</code>). Unrecognized extensions will default to plain text mode formatting.
    668                             </div>
    669                         </div>
    670 
    671                     </div>
    672                 </section>
    673 
    674                 <!-- Extensions -->
    675                 <section class="space-y-3">
    676                     <h4 class="text-sm font-semibold text-white">Extensions</h4>
    677                     <div class="space-y-2">
    678                         <label class="flex items-center justify-between p-3 bg-white/5 border border-white/10 rounded-lg cursor-pointer hover:bg-white/10 transition-colors">
    679                             <div>
    680                                 <span class="text-sm text-white block">Prettier Code Formatter</span>
    681                                 <span class="text-[10px] text-veyrix-muted">Auto-formats on save (Alt+Shift+F)</span>
    682                             </div>
    683                             <div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
    684                                 <input type="checkbox" name="toggle" checked class="toggle-checkbox absolute block w-5 h-5 rounded-full bg-white border-4 appearance-none cursor-pointer border-veyrix-accent" style="right:0; border-color: #3b82f6;"/>
    685                                 <label class="toggle-label block overflow-hidden h-5 rounded-full bg-veyrix-accent cursor-pointer"></label>
    686                             </div>
    687                         </label>
    688                     </div>
    689                 </section>
    690 
    691                 <div class="border-t border-veyrix-border pt-4">
    692                     <button onclick="App.factoryReset()" class="w-full py-2 bg-red-500/10 text-red-400 border border-red-500/20 rounded-lg text-sm font-medium hover:bg-red-500/20 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500">Factory Reset (Delete All Data)</button>
    693                 </div>
    694 
    695                 <!-- Footer & Branding Section -->
    696                 <div class="border-t border-veyrix-border pt-6 pb-2 space-y-6">
    697                     <!-- Tagline & Version -->
    698                     <div class="text-center space-y-1">
    699                         <center><img src="assets/veyrix.svg" alt="Veyrix Logo" class="w-10 h-10"></center>
    700                         <h4 class="text-lg font-bold text-white tracking-wide">Veyrix</h4>
    701                         <p class="text-xs text-veyrix-muted font-light">Code. Anywhere.</p>
    702                         <p class="text-[10px] text-veyrix-muted/60 font-mono pt-1" id="settings-version-display"></p>
    703                     </div>
    704 
    705                     <!-- Buttons -->
    706                     <div class="flex flex-col sm:flex-row gap-3">
    707                         <a href="https://github.com/notamitgamer/veyrix" target="_blank" rel="noopener noreferrer" class="flex-1 py-2 bg-white/5 hover:bg-white/10 border border-white/10 rounded-lg text-sm text-veyrix-text transition-colors flex items-center justify-center gap-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent">
    708                             <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.166 6.839 9.489.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.603-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.462-1.11-1.462-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.831.092-.646.35-1.086.636-1.336-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0112 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.161 22 16.416 22 12c0-5.523-4.477-10-10-10z"/></svg>
    709                             View Source on GitHub
    710                         </a>
    711                         <a href="/docs" class="flex-1 py-2 bg-white/5 hover:bg-white/10 border border-white/10 rounded-lg text-sm text-veyrix-text transition-colors flex items-center justify-center gap-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent">
    712                             <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path></svg>
    713                             Documentation
    714                         </a>
    715                     </div>
    716 
    717                     <!-- Creator & Disclaimer -->
    718                     <div class="text-center space-y-4">
    719                         <div class="text-xs text-veyrix-muted">
    720                             &copy; <a href="https://amit.is-a.dev" target="_blank" rel="noopener noreferrer" class="hover:text-veyrix-accent hover:underline transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent rounded px-0.5">Amit Dutta</a> 
    721                             <span class="mx-1 opacity-50">|</span> 
    722                             Username: <a href="https://github.com/notamitgamer" target="_blank" rel="noopener noreferrer" class="hover:text-veyrix-accent hover:underline transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-veyrix-accent rounded px-0.5">notamitgamer</a>
    723                         </div>
    724                         
    725                         <div class="text-[10px] text-veyrix-muted/60 leading-relaxed bg-black/20 p-3 rounded-lg border border-white/5 mx-auto text-center">
    726                             Use of this tool is at your own risk.<br>
    727                             The creator is not responsible for any data loss, misuse, or damages caused by using this application.
    728                             The creator holds full rights to modify or permanently discontinue this project at any time without prior notice.
    729                         </div>
    730                     </div>
    731                 </div>
    732             </div>
    733         </div>
    734 
    735         <!-- History Modal -->
    736         <div id="history-modal" class="hidden bg-veyrix-surface border border-veyrix-border rounded-lg w-full max-w-sm shadow-2xl modal-content pointer-events-auto flex flex-col overflow-hidden mb-auto md:mb-0 mt-4 md:mt-0 z-[100]">
    737             <div class="p-4 border-b border-veyrix-border flex justify-between items-center bg-veyrix-bg">
    738                 <h3 class="font-semibold text-white">Version History</h3>
    739                 <button onclick="UI.hideModal()" class="text-veyrix-muted hover:text-white transition-colors">✕</button>
    740             </div>
    741             <div id="history-list" class="overflow-y-auto max-h-64 p-2 space-y-1">
    742                 <!-- Snapshots injected here -->
    743             </div>
    744         </div>
    745 
    746     </div>
    747 
    748 <script>
    749 /**
    750  * VEYRIX IDE - CORE APPLICATION LOGIC
    751  */
    752 
    753 const APP_VERSION = '1.0.9';
    754 
    755 // ===== FIREBASE CONFIGURATION =====
    756 // Firebase initializes ONLY when Cloud Share is selected.
    757 const firebaseConfig = {
    758 
    759 };
    760 
    761 // Application State
    762 const state = {
    763     files: [],
    764     activeFileId: null,
    765     editor: null,
    766     db: null,
    767     renameTargetId: null
    768 };
    769 
    770 // Utilities
    771 const Utils = {
    772     generateId: () => 'f_' + Math.random().toString(36).substr(2, 9) + Date.now().toString(36),
    773     getDisplayName: (file) => file.ext ? `${file.name}.${file.ext}` : file.name,
    774     getMobileShortName: (name, ext) => {
    775         let sName = name || '';
    776         let sExt = ext || '';
    777         
    778         if (sName.length > 3) {
    779             sName = sName.substring(0, 2) + '...';
    780         }
    781         if (sExt.length > 3) {
    782             sExt = sExt.substring(0, 2) + '...';
    783         }
    784         
    785         return sExt ? `${sName}.${sExt}` : sName;
    786     },
    787     getFileIcon: (ext, name = '') => {
    788         const fullName = ext ? `${name}.${ext}` : name;
    789         const lowerName = fullName.toLowerCase();
    790         const lowerExt = ext.toLowerCase();
    791 
    792         const baseClass = "w-4 h-4 shrink-0 ";
    793         if(lowerExt === 'html') return `<svg class="${baseClass} text-orange-500" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 7l2 15 8 2 8-2 2-15L12 2zm0 2.5l8 4-1.5 11.5-6.5 1.5-6.5-1.5L4 8.5l8-4z"/></svg>`;
    794         if(lowerExt === 'css') return `<svg class="${baseClass} text-blue-500" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 7l2 15 8 2 8-2 2-15L12 2zm0 2.5l8 4-1.5 11.5-6.5 1.5-6.5-1.5L4 8.5l8-4z"/></svg>`;
    795         if(lowerExt === 'js' || lowerExt === 'jsx' || lowerExt === 'ts' || lowerExt === 'tsx') return `<svg class="${baseClass} text-yellow-400" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 7l2 15 8 2 8-2 2-15L12 2zm0 2.5l8 4-1.5 11.5-6.5 1.5-6.5-1.5L4 8.5l8-4z"/></svg>`;
    796         if(lowerExt === 'json') return `<svg class="${baseClass} text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"></path></svg>`;
    797         if(lowerExt === 'md') return `<svg class="${baseClass} text-blue-300" fill="currentColor" viewBox="0 0 24 24"><path d="M20.56 18H3.44C2.65 18 2 17.37 2 16.59V7.41C2 6.63 2.65 6 3.44 6h17.12C21.35 6 22 6.63 22 7.41v9.18c0 .78-.65 1.41-1.44 1.41zM11 14l-2.5-3.5L6 14H4V9h2v2.5l2.5-3.5L11 11.5V9h2v5h-2zM20 12h-2V9h-2v3h-2l3 3 3-3z"/></svg>`;
    798         
    799         // Config and special files
    800         if(lowerName === 'dockerfile' || lowerName === 'makefile' || lowerName === '.env' || lowerExt === 'env' || lowerName.endsWith('gitignore') || lowerExt === 'yml' || lowerExt === 'yaml') {
    801             return `<svg class="${baseClass} text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>`;
    802         }
    803         
    804         return `<svg class="${baseClass} text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path></svg>`;
    805     },
    806     getModeFromExt: (ext, name = '') => {
    807         const fullName = ext ? `${name}.${ext}` : name;
    808         const lowerFullName = fullName.toLowerCase();
    809         const lowerExt = ext.toLowerCase();
    810 
    811         const map = { html: 'html', css: 'css', js: 'javascript', jsx: 'javascript', ts: 'javascript', tsx: 'javascript', json: 'json', md: 'markdown', txt: 'text', py: 'python', yml: 'yaml', yaml: 'yaml', xml: 'xml', sh: 'sh', csv: 'text' };
    812         
    813         if (map[lowerExt]) return map[lowerExt];
    814         if (lowerFullName === 'dockerfile') return 'dockerfile';
    815         if (lowerFullName === 'makefile') return 'makefile';
    816         if (lowerFullName.endsWith('.env') || lowerExt === 'env') return 'ini';
    817         if (lowerFullName.endsWith('gitignore')) return 'gitignore';
    818 
    819         return 'text';
    820     },
    821     debounce: (func, wait) => {
    822         let timeout;
    823         return function executedFunction(...args) {
    824             const later = () => { clearTimeout(timeout); func(...args); };
    825             clearTimeout(timeout);
    826             timeout = setTimeout(later, wait);
    827         };
    828     },
    829     async hashPassword(password) {
    830         if (!password) return null;
    831         const msgBuffer = new TextEncoder().encode(password);
    832         const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
    833         const hashArray = Array.from(new Uint8Array(hashBuffer));
    834         return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    835     }
    836 };
    837 
    838 // IndexedDB Wrapper
    839 const DB = {
    840     init: () => {
    841         return new Promise((resolve, reject) => {
    842             const request = indexedDB.open('VeyrixFS', 1);
    843             request.onerror = () => reject(request.error);
    844             request.onsuccess = () => { state.db = request.result; resolve(); };
    845             request.onupgradeneeded = (e) => {
    846                 const db = e.target.result;
    847                 if (!db.objectStoreNames.contains('files')) {
    848                     db.createObjectStore('files', { keyPath: 'id' });
    849                 }
    850             };
    851         });
    852     },
    853     getAll: () => {
    854         return new Promise((resolve, reject) => {
    855             const tx = state.db.transaction('files', 'readonly');
    856             const store = tx.objectStore('files');
    857             const request = store.getAll();
    858             request.onsuccess = () => resolve(request.result);
    859             request.onerror = () => reject(request.error);
    860         });
    861     },
    862     save: (file) => {
    863         return new Promise((resolve, reject) => {
    864             const tx = state.db.transaction('files', 'readwrite');
    865             const store = tx.objectStore('files');
    866             file.lastModified = Date.now();
    867             const request = store.put(file);
    868             request.onsuccess = () => resolve(file);
    869             request.onerror = () => reject(request.error);
    870         });
    871     },
    872     delete: (id) => {
    873         return new Promise((resolve, reject) => {
    874             const tx = state.db.transaction('files', 'readwrite');
    875             const store = tx.objectStore('files');
    876             const request = store.delete(id);
    877             request.onsuccess = () => resolve();
    878             request.onerror = () => reject(request.error);
    879         });
    880     }
    881 };
    882 
    883 // PWA Install Logic
    884 let deferredPrompt;
    885 window.addEventListener('beforeinstallprompt', (e) => {
    886     e.preventDefault();
    887     deferredPrompt = e;
    888     const btn = document.getElementById('install-app-btn');
    889     if(btn) {
    890         btn.classList.remove('opacity-50', 'cursor-not-allowed');
    891         btn.disabled = false;
    892     }
    893 });
    894 
    895 // UI Manager
    896 const UI = {
    897     activeModal: null,
    898     modalStack: [],
    899     activeDropdownId: null,
    900 
    901     toggleSidebar: (forceClose = false) => {
    902         const sidebar = document.getElementById('sidebar');
    903         const backdrop = document.getElementById('sidebar-backdrop');
    904         const hamburger = document.querySelector('.hamburger');
    905         
    906         let isCollapsed = sidebar.classList.contains('sidebar-collapsed');
    907         if (forceClose) {
    908             isCollapsed = false; // Intention: want to collapse it
    909         }
    910 
    911         if (isCollapsed) {
    912             // Open Sidebar
    913             sidebar.classList.remove('sidebar-collapsed');
    914             localStorage.setItem('veyrix_sidebar', 'open');
    915             if (hamburger) {
    916                 hamburger.classList.add('is-active');
    917                 hamburger.setAttribute('aria-expanded', 'true');
    918             }
    919             if (window.innerWidth <= 768) {
    920                 backdrop.classList.remove('hidden');
    921                 requestAnimationFrame(() => backdrop.classList.remove('opacity-0'));
    922             }
    923         } else {
    924             // Close Sidebar
    925             sidebar.classList.add('sidebar-collapsed');
    926             localStorage.setItem('veyrix_sidebar', 'closed');
    927             if (hamburger) {
    928                 hamburger.classList.remove('is-active');
    929                 hamburger.setAttribute('aria-expanded', 'false');
    930             }
    931             backdrop.classList.add('opacity-0');
    932             setTimeout(() => backdrop.classList.add('hidden'), 250);
    933         }
    934         
    935         // Ensure Ace Editor resizes after DOM reflow transition
    936         setTimeout(() => { if (state.editor) state.editor.resize(); }, 260);
    937     },
    938 
    939     toggleFileDropdown: (id, btnElement, e) => {
    940         e.stopPropagation();
    941         const dd = document.getElementById('global-file-dropdown');
    942         
    943         if (UI.activeDropdownId === id && !dd.classList.contains('hidden')) {
    944             UI.closeAllDropdowns();
    945             return;
    946         }
    947         
    948         UI.activeDropdownId = id;
    949         const rect = btnElement.getBoundingClientRect();
    950         
    951         dd.classList.remove('hidden');
    952         dd.style.top = (rect.bottom + 4) + 'px';
    953         dd.style.left = (rect.right - 160) + 'px'; // width 40 = 160px
    954         
    955         // Map current actions to buttons
    956         document.getElementById('dd-rename').onclick = (ev) => { App.triggerRename(id, ev); };
    957         document.getElementById('dd-download').onclick = (ev) => { App.triggerDownload(id, ev); };
    958         document.getElementById('dd-share').onclick = (ev) => { App.triggerShare(id, ev); };
    959         document.getElementById('dd-delete').onclick = (ev) => { App.triggerDelete(id, ev); };
    960 
    961         requestAnimationFrame(() => {
    962             dd.classList.remove('opacity-0', 'scale-95');
    963             dd.classList.add('opacity-100', 'scale-100');
    964         });
    965     },
    966 
    967     closeAllDropdowns: () => {
    968         const dd = document.getElementById('global-file-dropdown');
    969         if(dd && !dd.classList.contains('hidden')) {
    970             dd.classList.remove('opacity-100', 'scale-100');
    971             dd.classList.add('opacity-0', 'scale-95');
    972             setTimeout(() => dd.classList.add('hidden'), 150);
    973             UI.activeDropdownId = null;
    974         }
    975     },
    976 
    977     showConfirm: (title, message, confirmText, actionCallback) => {
    978         document.getElementById('confirm-title').textContent = title;
    979         document.getElementById('confirm-message').textContent = message;
    980         
    981         const btn = document.getElementById('confirm-action-btn');
    982         btn.textContent = confirmText;
    983         
    984         // Handle danger vs normal confirm look
    985         const iconBg = document.getElementById('confirm-icon-bg');
    986         if (confirmText.toLowerCase().includes('delete') || confirmText.toLowerCase().includes('restore')) {
    987             btn.className = "flex-1 py-2 bg-red-500 hover:bg-red-600 text-white rounded-lg text-sm font-medium transition-colors";
    988             iconBg.className = "w-12 h-12 rounded-full bg-red-500/10 text-red-500 flex items-center justify-center mx-auto mb-2";
    989         } else {
    990             btn.className = "flex-1 py-2 bg-veyrix-accent hover:bg-veyrix-accentHover text-white rounded-lg text-sm font-medium transition-colors";
    991             iconBg.className = "w-12 h-12 rounded-full bg-veyrix-accent/10 text-veyrix-accent flex items-center justify-center mx-auto mb-2";
    992         }
    993 
    994         btn.onclick = () => {
    995             UI.hideModal();
    996             actionCallback();
    997         };
    998         UI.showModal('confirm-modal');
    999     },
   1000 
   1001     showHistory: () => {
   1002         const file = state.files.find(f => f.id === state.activeFileId);
   1003         if(!file) return UI.toast('No file open', 'error');
   1004         const list = document.getElementById('history-list');
   1005         list.innerHTML = '';
   1006         
   1007         if (!file.snapshots || file.snapshots.length === 0) {
   1008             list.innerHTML = `<p class="text-xs text-veyrix-muted p-3 text-center">No snapshots saved.</p>`;
   1009         } else {
   1010             [...file.snapshots].reverse().forEach((snap, idx) => {
   1011                 const date = new Date(snap.ts).toLocaleString();
   1012                 const realIndex = file.snapshots.length - 1 - idx;
   1013                 const versionNumber = snap.version || (realIndex + 1);
   1014                 
   1015                 const el = document.createElement('div');
   1016                 el.className = 'flex items-center justify-between p-3 hover:bg-white/5 rounded-md text-sm border-b border-veyrix-border/50';
   1017                 el.innerHTML = `
   1018                     <div class="flex flex-col">
   1019                         <span class="text-white">Version ${versionNumber}</span>
   1020                         <span class="text-xs text-veyrix-muted">${date}</span>
   1021                     </div>
   1022                     <button onclick="App.triggerRestore(${realIndex}, event)" class="text-veyrix-accent hover:text-veyrix-accentHover text-xs font-medium px-3 py-1.5 rounded bg-veyrix-accent/10 hover:bg-veyrix-accent/20 transition-colors">Restore</button>
   1023                 `;
   1024                 list.appendChild(el);
   1025             });
   1026         }
   1027         UI.showModal('history-modal');
   1028     },
   1029 
   1030     showModal: (id) => {
   1031         UI.closeAllDropdowns();
   1032         const overlay = document.getElementById('modal-overlay');
   1033         const modal = document.getElementById(id);
   1034         
   1035         if (UI.activeModal && UI.activeModal.id !== id) {
   1036             UI.activeModal.classList.add('hidden');
   1037             UI.activeModal.classList.remove('modal-enter', 'modal-exit');
   1038             UI.modalStack.push(UI.activeModal);
   1039         }
   1040         
   1041         overlay.classList.remove('hidden');
   1042         overlay.classList.add('flex');
   1043         
   1044         modal.classList.remove('hidden');
   1045         modal.classList.add('modal-enter');
   1046         modal.classList.remove('modal-exit');
   1047         UI.activeModal = modal;
   1048 
   1049         if(id === 'new-file-modal') {
   1050             setTimeout(() => {
   1051                 const input = document.getElementById('new-file-input');
   1052                 if (input) input.focus();
   1053             }, 100);
   1054         } else if(id === 'rename-file-modal') {
   1055             setTimeout(() => {
   1056                 const input = document.getElementById('rename-file-input');
   1057                 if (input) {
   1058                     input.focus();
   1059                     input.select();
   1060                 }
   1061             }, 100);
   1062         }
   1063     },
   1064 
   1065     hideModal: () => {
   1066         if (!UI.activeModal) return;
   1067         const overlay = document.getElementById('modal-overlay');
   1068         const modal = UI.activeModal;
   1069         
   1070         modal.classList.remove('modal-enter');
   1071         modal.classList.add('modal-exit');
   1072         
   1073         UI.activeModal = null; // Important to release current immediately
   1074 
   1075         setTimeout(() => {
   1076             modal.classList.add('hidden');
   1077             modal.classList.remove('modal-exit');
   1078             
   1079             if (UI.modalStack && UI.modalStack.length > 0) {
   1080                 UI.activeModal = UI.modalStack.pop();
   1081                 UI.activeModal.classList.remove('hidden');
   1082                 UI.activeModal.classList.add('modal-enter');
   1083             } else {
   1084                 overlay.classList.add('hidden');
   1085                 overlay.classList.remove('flex');
   1086             }
   1087         }, 200);
   1088     },
   1089 
   1090     closeAllModals: () => {
   1091         if (UI.activeModal) {
   1092             UI.activeModal.classList.add('hidden');
   1093             UI.activeModal.classList.remove('modal-enter', 'modal-exit');
   1094         }
   1095         UI.modalStack.forEach(m => {
   1096             m.classList.add('hidden');
   1097             m.classList.remove('modal-enter', 'modal-exit');
   1098         });
   1099         UI.modalStack = [];
   1100         UI.activeModal = null;
   1101         const overlay = document.getElementById('modal-overlay');
   1102         overlay.classList.add('hidden');
   1103         overlay.classList.remove('flex');
   1104     },
   1105 
   1106     toast: (msg, type = 'info') => {
   1107         const container = document.getElementById('toast-container');
   1108         const toast = document.createElement('div');
   1109         const colors = type === 'error' ? 'bg-red-500/90 text-white border-red-400' : 'bg-veyrix-surface text-white border-veyrix-border';
   1110         toast.className = `px-4 py-2 rounded-lg border shadow-lg text-sm font-medium transform transition-all duration-300 translate-y-10 opacity-0 flex items-center gap-2 ${colors}`;
   1111         toast.innerHTML = type === 'error' ? `<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> ${msg}` 
   1112                                            : `<svg class="w-4 h-4 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg> ${msg}`;
   1113         container.appendChild(toast);
   1114         
   1115         requestAnimationFrame(() => {
   1116             toast.classList.remove('translate-y-10', 'opacity-0');
   1117         });
   1118 
   1119         setTimeout(() => {
   1120             toast.classList.add('translate-y-10', 'opacity-0');
   1121             setTimeout(() => toast.remove(), 300);
   1122         }, 3000);
   1123     },
   1124 
   1125     renderSidebar: () => {
   1126         const list = document.getElementById('file-list');
   1127         list.innerHTML = '';
   1128         state.files.sort((a,b) => b.lastModified - a.lastModified).forEach(file => {
   1129             const isActive = file.id === state.activeFileId;
   1130             const el = document.createElement('div');
   1131             el.className = `flex items-center justify-between p-1 px-1.5 rounded-lg cursor-pointer group transition-colors ${isActive ? 'bg-veyrix-accent/20 text-white' : 'text-veyrix-text hover:bg-white/5 hover:text-white'}`;
   1132             el.innerHTML = `
   1133                 <div class="flex items-center gap-2 overflow-hidden flex-1 py-1 px-1" onclick="App.openFile('${file.id}')" role="button" tabindex="0">
   1134                     ${Utils.getFileIcon(file.ext, file.name)}
   1135                     <span class="text-sm truncate select-none">${Utils.getDisplayName(file)}</span>
   1136                     ${file.unsaved ? `<div class="w-1.5 h-1.5 rounded-full bg-veyrix-accent shrink-0 ml-1"></div>` : ''}
   1137                 </div>
   1138                 <div class="relative dd-trigger">
   1139                     <button onclick="UI.toggleFileDropdown('${file.id}', this, event)" class="p-1 rounded hover:bg-white/10 text-veyrix-muted hover:text-white transition-colors" title="Options">
   1140                         <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z"></path></svg>
   1141                     </button>
   1142                 </div>
   1143             `;
   1144             list.appendChild(el);
   1145         });
   1146     },
   1147 
   1148     updateTopBar: () => {
   1149         const file = state.files.find(f => f.id === state.activeFileId);
   1150         const placeholder = document.getElementById('editor-placeholder');
   1151         const activeFileContainer = document.getElementById('top-active-file');
   1152         const nameEl = document.getElementById('top-file-name');
   1153         const unsavedEl = document.getElementById('top-file-unsaved');
   1154 
   1155         if (file) {
   1156             placeholder.classList.add('hidden');
   1157             activeFileContainer.classList.remove('hidden');
   1158             activeFileContainer.classList.add('flex');
   1159             
   1160             let displayName = Utils.getDisplayName(file);
   1161             if (window.innerWidth <= 768) {
   1162                 displayName = Utils.getMobileShortName(file.name, file.ext);
   1163             }
   1164             
   1165             nameEl.textContent = displayName;
   1166             unsavedEl.classList.toggle('hidden', !file.unsaved);
   1167         } else {
   1168             placeholder.classList.remove('hidden');
   1169             activeFileContainer.classList.add('hidden');
   1170             activeFileContainer.classList.remove('flex');
   1171             nameEl.textContent = 'No file open';
   1172             unsavedEl.classList.add('hidden');
   1173             if(state.editor) {
   1174                 state.editor.setValue('', -1);
   1175                 state.editor.setReadOnly(true);
   1176             }
   1177         }
   1178     },
   1179 
   1180     switchShareTab: (tab) => {
   1181         document.getElementById('tab-local').classList.toggle('hidden', tab !== 'local');
   1182         document.getElementById('tab-cloud').classList.toggle('hidden', tab !== 'cloud');
   1183         
   1184         document.getElementById('tab-btn-local').className = `flex-1 py-3 text-sm font-medium border-b-2 transition-colors ${tab === 'local' ? 'border-veyrix-accent text-veyrix-accent' : 'border-transparent text-veyrix-muted hover:text-white'}`;
   1185         document.getElementById('tab-btn-cloud').className = `flex-1 py-3 text-sm font-medium border-b-2 transition-colors ${tab === 'cloud' ? 'border-veyrix-accent text-veyrix-accent' : 'border-transparent text-veyrix-muted hover:text-white'}`;
   1186     },
   1187 
   1188     copyToClipboard: (elementId) => {
   1189         const el = document.getElementById(elementId);
   1190         el.select();
   1191         document.execCommand('copy');
   1192         UI.toast('Link copied to clipboard');
   1193     }
   1194 };
   1195 
   1196 // Cloud & Firebase Features
   1197 const Cloud = {
   1198     firebaseLoaded: false,
   1199     loadFirebase: async () => {
   1200         if (Cloud.firebaseLoaded) return true;
   1201         try {
   1202             const [app, auth, firestore] = await Promise.all([
   1203                 import('https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js'),
   1204                 import('https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js'),
   1205                 import('https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js')
   1206             ]);
   1207             
   1208             const fbApp = app.initializeApp(firebaseConfig);
   1209             const fbAuth = auth.getAuth(fbApp);
   1210             const fbDb = firestore.getFirestore(fbApp);
   1211             
   1212             await auth.signInAnonymously(fbAuth);
   1213             
   1214             window.fbDb = fbDb;
   1215             window.fbFirestore = firestore;
   1216             Cloud.firebaseLoaded = true;
   1217             return true;
   1218         } catch (e) {
   1219             console.error(e);
   1220             return false;
   1221         }
   1222     },
   1223     generateLocalShare: () => {
   1224         const file = state.files.find(f => f.id === state.activeFileId);
   1225         if(!file) return UI.toast('No file to share', 'error');
   1226         
   1227         const compressed = LZString.compressToEncodedURIComponent(file.content);
   1228         const url = `${window.location.origin}${window.location.pathname}?ext=${file.ext}&code=${compressed}`;
   1229         
   1230         const resDiv = document.getElementById('local-share-result');
   1231         const urlInput = document.getElementById('local-share-url');
   1232         urlInput.value = url;
   1233         resDiv.classList.remove('hidden');
   1234     },
   1235     generateCloudShare: async () => {
   1236         const file = state.files.find(f => f.id === state.activeFileId);
   1237         if(!file) return UI.toast('No file to share', 'error');
   1238 
   1239         const btn = document.getElementById('btn-cloud-share');
   1240         const originalText = btn.innerHTML;
   1241         btn.innerHTML = `<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg> Sharing project...`;
   1242         btn.disabled = true;
   1243 
   1244         try {
   1245             if(await Cloud.loadFirebase()) {
   1246                 const pass = document.getElementById('cloud-password').value;
   1247                 const passHash = await Utils.hashPassword(pass);
   1248                 
   1249                 const data = {
   1250                     content: file.content,
   1251                     ext: file.ext,
   1252                     name: file.name,
   1253                     timestamp: window.fbFirestore.serverTimestamp(),
   1254                     protected: !!passHash,
   1255                     hash: passHash
   1256                 };
   1257 
   1258                 const docRef = await window.fbFirestore.addDoc(window.fbFirestore.collection(window.fbDb, "shared_snippets"), data);
   1259                 
   1260                 const url = `${window.location.origin}${window.location.pathname}?id=${docRef.id}`;
   1261                 const resDiv = document.getElementById('cloud-share-result');
   1262                 const urlInput = document.getElementById('cloud-share-url');
   1263                 urlInput.value = url;
   1264                 resDiv.classList.remove('hidden');
   1265                 UI.toast('Uploaded to secure cloud');
   1266             } else {
   1267                 throw new Error("Firebase failed to load");
   1268             }
   1269         } catch(e) {
   1270             console.error(e);
   1271             UI.toast('Cloud share failed', 'error');
   1272         } finally {
   1273             btn.innerHTML = originalText;
   1274             btn.disabled = false;
   1275         }
   1276     },
   1277     handleCloudLoad: async (id) => {
   1278         UI.toast('Loading from cloud...');
   1279         try {
   1280             if(await Cloud.loadFirebase()) {
   1281                 const docRef = window.fbFirestore.doc(window.fbDb, "shared_snippets", id);
   1282                 const docSnap = await window.fbFirestore.getDoc(docRef);
   1283                 
   1284                 if (docSnap.exists()) {
   1285                     const data = docSnap.data();
   1286                     if(data.protected) {
   1287                         Cloud.currentCloudDoc = data;
   1288                         UI.showModal('password-modal');
   1289                     } else {
   1290                         App.loadExternalContent(data.name || 'shared', data.ext, data.content);
   1291                     }
   1292                 } else {
   1293                     UI.toast('Cloud file not found', 'error');
   1294                 }
   1295             } else {
   1296                 throw new Error("Firebase failed to load");
   1297             }
   1298         } catch(e) {
   1299             console.error(e);
   1300             UI.toast('Failed to load cloud file', 'error');
   1301         }
   1302     },
   1303     verifyAndLoadCloudFile: async () => {
   1304         const pass = document.getElementById('access-password').value;
   1305         const hash = await Utils.hashPassword(pass);
   1306         if(hash === Cloud.currentCloudDoc.hash) {
   1307             UI.hideModal();
   1308             App.loadExternalContent(Cloud.currentCloudDoc.name || 'shared', Cloud.currentCloudDoc.ext, Cloud.currentCloudDoc.content);
   1309             Cloud.currentCloudDoc = null;
   1310             document.getElementById('access-password').value = '';
   1311         } else {
   1312             UI.toast('Incorrect password', 'error');
   1313         }
   1314     },
   1315     nativeShare: async () => {
   1316         const file = state.files.find(f => f.id === state.activeFileId);
   1317         if(!file) return UI.toast('No file to share', 'error');
   1318         try {
   1319             await navigator.share({
   1320                 title: Utils.getDisplayName(file),
   1321                 text: file.content,
   1322                 url: window.location.href
   1323             });
   1324         } catch (err) {
   1325             console.log('Share canceled or failed');
   1326         }
   1327     }
   1328 };
   1329 
   1330 // Core Application Logic
   1331 const App = {
   1332     init: async () => {
   1333         // Init Ace Editor
   1334         state.editor = ace.edit("editor");
   1335         state.editor.setTheme("ace/theme/tomorrow_night_eighties");
   1336         state.editor.setOptions({
   1337             fontSize: "14px",
   1338             showPrintMargin: false,
   1339             wrap: true,
   1340             enableBasicAutocompletion: true,
   1341             enableLiveAutocompletion: true,
   1342             fontFamily: "'Fira Code', 'Consolas', monospace"
   1343         });
   1344         
   1345         // Exact viewport fix for mobile keyboards
   1346         if (window.visualViewport) {
   1347             const adjustViewport = () => {
   1348                 const vh = window.visualViewport.height;
   1349                 const headerHeight = document.querySelector('header').offsetHeight;
   1350                 
   1351                 // Pin body to actual visual height
   1352                 document.body.style.height = vh + 'px';
   1353                 
   1354                 // Allow exact remainder height to the main workspace
   1355                 const mainArea = document.getElementById('main-workspace');
   1356                 if (mainArea) {
   1357                     mainArea.style.height = (vh - headerHeight) + 'px';
   1358                 }
   1359                 
   1360                 if (state.editor) {
   1361                     state.editor.resize();
   1362                 }
   1363             };
   1364             window.visualViewport.addEventListener('resize', adjustViewport);
   1365             adjustViewport();
   1366         }
   1367 
   1368         // Set version displays
   1369         const versionDisplay = document.getElementById('app-version-display');
   1370         if (versionDisplay) versionDisplay.textContent = `v${APP_VERSION}`;
   1371         
   1372         const settingsVersionDisplay = document.getElementById('settings-version-display');
   1373         if (settingsVersionDisplay) settingsVersionDisplay.textContent = `Version: v${APP_VERSION}`;
   1374 
   1375         // Check version for onboarding modal
   1376         const storedVersion = localStorage.getItem('veyrix_version');
   1377         if (storedVersion !== APP_VERSION) {
   1378             setTimeout(() => {
   1379                 UI.showModal('help-modal');
   1380                 localStorage.setItem('veyrix_version', APP_VERSION);
   1381             }, 500); // Small delay to let the UI finish rendering first
   1382         }
   1383 
   1384         // Feature Detect Native Share
   1385         if (navigator.share) {
   1386             const nativeShareSection = document.getElementById('native-share-section');
   1387             if(nativeShareSection) {
   1388                 nativeShareSection.classList.remove('hidden');
   1389             }
   1390         }
   1391 
   1392         // --- Dot-Space Auto Fix Implementation ---
   1393         const handleDotSpace = (e) => {
   1394             const el = e.target;
   1395             const val = el.value;
   1396             const selStart = el.selectionStart;
   1397 
   1398             // Detect if ". " exists exactly right before the cursor
   1399             if (selStart >= 2 && val.substring(selStart - 2, selStart) === '. ') {
   1400                 el.value = val.substring(0, selStart - 1) + val.substring(selStart);
   1401                 el.setSelectionRange(selStart - 1, selStart - 1);
   1402             }
   1403 
   1404             // Smart Extension Detection for Web Editor
   1405             if (el.id === 'new-file-input') {
   1406                 const recBox = document.getElementById('web-editor-recommendation');
   1407                 if (recBox) {
   1408                     const filename = el.value.trim().toLowerCase();
   1409                     const webExts = ['.html', '.htm', '.css', '.js', '.mjs', '.jsx', '.ts', '.tsx', '.json', '.xml', '.svg', '.yaml', '.yml', '.md'];
   1410                     
   1411                     const hasWebExt = webExts.some(ext => filename.endsWith(ext) && filename.length > ext.length);
   1412                     if (hasWebExt) {
   1413                         recBox.classList.remove('hidden');
   1414                     } else {
   1415                         recBox.classList.add('hidden');
   1416                     }
   1417                 }
   1418             }
   1419         };
   1420 
   1421         const newFileInput = document.getElementById('new-file-input');
   1422         if(newFileInput) newFileInput.addEventListener('input', handleDotSpace);
   1423 
   1424         const renameFileInput = document.getElementById('rename-file-input');
   1425         if(renameFileInput) renameFileInput.addEventListener('input', handleDotSpace);
   1426 
   1427         // --- Enter Key Listeners for Modals ---
   1428         newFileInput.addEventListener('keydown', (e) => {
   1429             if (e.key === 'Enter') {
   1430                 e.preventDefault();
   1431                 App.createNewFile();
   1432             }
   1433         });
   1434 
   1435         renameFileInput.addEventListener('keydown', (e) => {
   1436             if (e.key === 'Enter') {
   1437                 e.preventDefault();
   1438                 App.executeRename();
   1439             }
   1440         });
   1441 
   1442         // Global Event Delegation for Clicks (Closes sidebars & dropdowns)
   1443         document.addEventListener('click', (e) => {
   1444             // Dropdowns
   1445             if (!e.target.closest('#global-file-dropdown') && !e.target.closest('.dd-trigger')) {
   1446                 UI.closeAllDropdowns();
   1447             }
   1448 
   1449             // Outside Sidebar Click
   1450             const sidebar = document.getElementById('sidebar');
   1451             const toggleBtn = e.target.closest('.sidebar-toggle-btn');
   1452             const isSidebarClick = e.target.closest('#sidebar');
   1453             const isBackdropClick = e.target.id === 'sidebar-backdrop';
   1454             
   1455             if (!sidebar.classList.contains('sidebar-collapsed')) {
   1456                 // If it's a mobile backdrop click, OR an external desktop click
   1457                 if (isBackdropClick || (!isSidebarClick && !toggleBtn)) {
   1458                     UI.toggleSidebar(true);
   1459                 }
   1460             }
   1461         });
   1462 
   1463         // Window resize listeners
   1464         window.addEventListener('resize', Utils.debounce(() => {
   1465             UI.updateTopBar();
   1466         }, 200));
   1467 
   1468         // Swipe to open/close sidebar
   1469         let touchStartX = 0;
   1470         let touchStartY = 0;
   1471         document.addEventListener('touchstart', e => {
   1472             touchStartX = e.changedTouches[0].screenX;
   1473             touchStartY = e.changedTouches[0].screenY;
   1474         }, {passive: true});
   1475         
   1476         document.addEventListener('touchend', e => {
   1477             let touchEndX = e.changedTouches[0].screenX;
   1478             let touchEndY = e.changedTouches[0].screenY;
   1479             if (Math.abs(touchEndX - touchStartX) > Math.abs(touchEndY - touchStartY)) {
   1480                 if (touchEndX - touchStartX > 80 && touchStartX < 50) {
   1481                     const sb = document.getElementById('sidebar');
   1482                     if (sb.classList.contains('sidebar-collapsed')) UI.toggleSidebar(); // open
   1483                 } else if (touchStartX - touchEndX > 80) {
   1484                     const sb = document.getElementById('sidebar');
   1485                     if (!sb.classList.contains('sidebar-collapsed')) UI.toggleSidebar(true); // close
   1486                 }
   1487             }
   1488         }, {passive: true});
   1489         
   1490         state.editor.on("change", Utils.debounce(() => {
   1491             if(state.activeFileId) {
   1492                 const file = state.files.find(f => f.id === state.activeFileId);
   1493                 if(file && file.content !== state.editor.getValue()) {
   1494                     file.content = state.editor.getValue();
   1495                     if(!file.unsaved) {
   1496                         file.unsaved = true;
   1497                         UI.renderSidebar();
   1498                         UI.updateTopBar();
   1499                     }
   1500                 }
   1501             }
   1502         }, 500));
   1503 
   1504         // Keyboard Shortcuts
   1505         window.addEventListener('keydown', (e) => {
   1506             if (e.ctrlKey && e.key === 's') { e.preventDefault(); App.saveCurrentFile(); }
   1507             if (e.ctrlKey && e.key === 'o') { e.preventDefault(); document.getElementById('file-upload').click(); }
   1508             if (e.altKey && e.shiftKey && e.key.toLowerCase() === 'f') { e.preventDefault(); App.formatCode(); }
   1509             if (e.key === 'Escape') { 
   1510                 UI.hideModal(); 
   1511                 UI.closeAllDropdowns(); 
   1512                 const sb = document.getElementById('sidebar');
   1513                 if (sb && !sb.classList.contains('sidebar-collapsed')) {
   1514                     UI.toggleSidebar(true);
   1515                 }
   1516             }
   1517         });
   1518 
   1519         // Init DB & State Setup
   1520         try {
   1521             await DB.init();
   1522             state.files = await DB.getAll();
   1523             
   1524             // Apply Initial Sidebar State
   1525             const savedState = localStorage.getItem('veyrix_sidebar');
   1526             const sb = document.getElementById('sidebar');
   1527             const hamburger = document.querySelector('.hamburger');
   1528             if (savedState === 'closed') {
   1529                 sb.classList.add('sidebar-collapsed');
   1530                 if(hamburger) { hamburger.classList.remove('is-active'); hamburger.setAttribute('aria-expanded', 'false'); }
   1531             } else if (savedState === 'open') {
   1532                 sb.classList.remove('sidebar-collapsed');
   1533                 if(hamburger) { hamburger.classList.add('is-active'); hamburger.setAttribute('aria-expanded', 'true'); }
   1534             } else {
   1535                 // Default fallback: closed on mobile, open on desktop
   1536                 if (window.innerWidth <= 768) {
   1537                     sb.classList.add('sidebar-collapsed');
   1538                     if(hamburger) { hamburger.classList.remove('is-active'); hamburger.setAttribute('aria-expanded', 'false'); }
   1539                 } else {
   1540                     sb.classList.remove('sidebar-collapsed');
   1541                     if(hamburger) { hamburger.classList.add('is-active'); hamburger.setAttribute('aria-expanded', 'true'); }
   1542                 }
   1543             }
   1544 
   1545             UI.renderSidebar();
   1546             
   1547             const lastActive = localStorage.getItem('veyrix_active');
   1548             if(lastActive && state.files.find(f => f.id === lastActive)) {
   1549                 App.openFile(lastActive);
   1550             } else {
   1551                 UI.updateTopBar();
   1552             }
   1553         } catch(e) {
   1554             UI.toast('Failed to load local DB', 'error');
   1555         }
   1556 
   1557         // Handle URL Parameters (Sharing)
   1558         const params = new URLSearchParams(window.location.search);
   1559         if(params.has('ext') && params.has('code')) {
   1560             const ext = params.get('ext');
   1561             const code = LZString.decompressFromEncodedURIComponent(params.get('code'));
   1562             if(code !== null) {
   1563                 App.loadExternalContent('shared_snippet', ext, code);
   1564             }
   1565         } else if(params.has('id')) {
   1566             Cloud.handleCloudLoad(params.get('id'));
   1567         }
   1568     },
   1569 
   1570     installApp: async () => {
   1571         if (deferredPrompt) {
   1572             deferredPrompt.prompt();
   1573             const { outcome } = await deferredPrompt.userChoice;
   1574             if (outcome === 'accepted') {
   1575                 deferredPrompt = null;
   1576                 const btn = document.getElementById('install-app-btn');
   1577                 btn.classList.add('opacity-50', 'cursor-not-allowed');
   1578                 btn.disabled = true;
   1579             }
   1580         }
   1581     },
   1582     
   1583     openInWebEditor: () => {
   1584         const input = document.getElementById('new-file-input').value.trim();
   1585         if(!input) return;
   1586         localStorage.setItem('veyrix_pending_web_file', input);
   1587         UI.hideModal();
   1588         window.location.href = '/web';
   1589     },
   1590 
   1591     createNewFile: async () => {
   1592         const input = document.getElementById('new-file-input').value.trim();
   1593         if(!input) return UI.toast('Filename cannot be empty', 'error');
   1594         
   1595         let name = input;
   1596         let ext = '';
   1597         
   1598         const lastDotIdx = input.lastIndexOf('.');
   1599         if (lastDotIdx > 0 && lastDotIdx < input.length - 1) {
   1600             ext = input.substring(lastDotIdx + 1);
   1601             name = input.substring(0, lastDotIdx);
   1602         } else if (lastDotIdx === 0) {
   1603             name = input;
   1604             ext = '';
   1605         } else if (lastDotIdx === input.length - 1) {
   1606             name = input.substring(0, lastDotIdx);
   1607             ext = '';
   1608         }
   1609 
   1610         const newFile = {
   1611             id: Utils.generateId(),
   1612             name,
   1613             ext,
   1614             content: '',
   1615             unsaved: true
   1616         };
   1617 
   1618         state.files.push(newFile);
   1619         await DB.save(newFile);
   1620         UI.hideModal();
   1621         document.getElementById('new-file-input').value = '';
   1622         App.openFile(newFile.id);
   1623         UI.toast('File created');
   1624     },
   1625 
   1626     openFile: (id) => {
   1627         const file = state.files.find(f => f.id === id);
   1628         if(!file) return;
   1629 
   1630         state.activeFileId = id;
   1631         localStorage.setItem('veyrix_active', id);
   1632         
   1633         state.editor.setReadOnly(false);
   1634         state.editor.setValue(file.content, -1);
   1635         
   1636         const mode = Utils.getModeFromExt(file.ext, file.name);
   1637         state.editor.session.setMode(`ace/mode/${mode}`);
   1638         
   1639         UI.renderSidebar();
   1640         UI.updateTopBar();
   1641         
   1642         if(window.innerWidth <= 768) {
   1643             UI.toggleSidebar(true); // force close on mobile selection
   1644         }
   1645     },
   1646 
   1647     saveCurrentFile: async () => {
   1648         if(!state.activeFileId) return;
   1649         const file = state.files.find(f => f.id === state.activeFileId);
   1650         if(!file) return;
   1651 
   1652         file.content = state.editor.getValue();
   1653         file.unsaved = false;
   1654         await DB.save(file);
   1655         
   1656         UI.renderSidebar();
   1657         UI.updateTopBar();
   1658         UI.toast('File saved');
   1659     },
   1660 
   1661 triggerDownload: async (id = null, e = null, skipShareAttempt = false) => {
   1662         if(e) e.stopPropagation();
   1663         UI.closeAllDropdowns();
   1664         
   1665         const fileId = id || state.activeFileId;
   1666         if(!fileId) return UI.toast('No file to download', 'error');
   1667         
   1668         const file = state.files.find(f => f.id === fileId);
   1669         if(!file) return;
   1670 
   1671         const content = file.id === state.activeFileId ? state.editor.getValue() : file.content;
   1672         const fileName = Utils.getDisplayName(file);
   1673         
   1674         // Wrap the share attempt in the new flag
   1675         if (!skipShareAttempt && navigator.canShare) {
   1676             try {
   1677                 const fileObj = new File([content], fileName, { type: 'application/octet-stream' });
   1678                 if (navigator.canShare({ files: [fileObj] })) {
   1679                     await navigator.share({
   1680                         files: [fileObj],
   1681                         title: fileName
   1682                     });
   1683                     return;
   1684                 }
   1685             } catch (err) {
   1686                 console.log('Share canceled or failed, falling back to native download', err);
   1687             }
   1688         }
   1689 
   1690         const blob = new Blob([content], { type: 'application/octet-stream' });
   1691         const url = URL.createObjectURL(blob);
   1692         
   1693         const a = document.createElement('a');
   1694         a.href = url;
   1695         a.download = fileName;
   1696         document.body.appendChild(a);
   1697         a.click();
   1698         document.body.removeChild(a);
   1699         URL.revokeObjectURL(url);
   1700         
   1701         UI.toast('Download started');
   1702     },
   1703 
   1704     triggerSnapshot: () => {
   1705         if(!state.activeFileId) return UI.toast('No file open', 'error');
   1706         const file = state.files.find(f => f.id === state.activeFileId);
   1707         if(!file) return;
   1708 
   1709         file.snapshots = file.snapshots || [];
   1710         if(file.snapshots.length >= 5) {
   1711             const oldest = file.snapshots[0];
   1712             const date = new Date(oldest.ts).toLocaleString();
   1713             document.getElementById('snapshot-limit-oldest-info').textContent = `Version ${oldest.version || 1} - ${date}`;
   1714             UI.showModal('snapshot-limit-modal');
   1715         } else {
   1716             App.executeSnapshotSave(false);
   1717         }
   1718     },
   1719 
   1720     executeSnapshotSave: async (replaceOldest = false) => {
   1721         const file = state.files.find(f => f.id === state.activeFileId);
   1722         if(!file) return;
   1723 
   1724         file.snapshots = file.snapshots || [];
   1725         file.snapshotCounter = (file.snapshotCounter || file.snapshots.length) + 1;
   1726 
   1727         if (replaceOldest) {
   1728             file.snapshots.shift();
   1729             UI.hideModal();
   1730         }
   1731 
   1732         file.snapshots.push({
   1733             ts: Date.now(),
   1734             content: state.editor.getValue(),
   1735             version: file.snapshotCounter
   1736         });
   1737         
   1738         await DB.save(file);
   1739         UI.toast('Snapshot saved to history');
   1740     },
   1741 
   1742     triggerRestore: (index, e) => {
   1743         if(e) e.stopPropagation();
   1744         UI.showConfirm(
   1745             "Restore Snapshot", 
   1746             "Are you sure you want to restore this snapshot? Current unsaved changes will be lost.", 
   1747             "Restore", 
   1748             () => App.restoreSnapshot(index)
   1749         );
   1750     },
   1751 
   1752     restoreSnapshot: async (index) => {
   1753         const file = state.files.find(f => f.id === state.activeFileId);
   1754         if(!file || !file.snapshots || !file.snapshots[index]) return;
   1755         
   1756         state.editor.setValue(file.snapshots[index].content, -1);
   1757         UI.closeAllModals(); 
   1758         UI.toast('Restored previous version');
   1759         App.saveCurrentFile(); // auto save after restore
   1760     },
   1761 
   1762     triggerRename: async (id, e) => {
   1763         if(e) e.stopPropagation();
   1764         UI.closeAllDropdowns();
   1765         const file = state.files.find(f => f.id === id);
   1766         if(!file) return;
   1767 
   1768         state.renameTargetId = id;
   1769         const input = document.getElementById('rename-file-input');
   1770         input.value = Utils.getDisplayName(file);
   1771         UI.showModal('rename-file-modal');
   1772     },
   1773 
   1774     executeRename: async () => {
   1775         const id = state.renameTargetId;
   1776         const file = state.files.find(f => f.id === id);
   1777         if(!file) return;
   1778 
   1779         const input = document.getElementById('rename-file-input').value.trim();
   1780         if(!input) return UI.toast('Filename cannot be empty', 'error');
   1781 
   1782         const lastDotIdx = input.lastIndexOf('.');
   1783         
   1784         if (lastDotIdx > 0 && lastDotIdx < input.length - 1) {
   1785             file.ext = input.substring(lastDotIdx + 1);
   1786             file.name = input.substring(0, lastDotIdx);
   1787         } else if (lastDotIdx === 0) {
   1788             file.name = input;
   1789             file.ext = '';
   1790         } else if (lastDotIdx === input.length - 1) {
   1791             file.name = input.substring(0, lastDotIdx);
   1792             file.ext = '';
   1793         } else {
   1794             file.name = input;
   1795             file.ext = '';
   1796         }
   1797         
   1798         await DB.save(file);
   1799         UI.hideModal();
   1800         UI.renderSidebar();
   1801         UI.updateTopBar();
   1802         UI.toast('File renamed');
   1803     },
   1804 
   1805     triggerShare: async (id, e) => {
   1806         if(e) e.stopPropagation();
   1807         UI.closeAllDropdowns();
   1808         App.openFile(id);
   1809         UI.showModal('share-modal');
   1810     },
   1811 
   1812     triggerDelete: async (id, e) => {
   1813         if(e) e.stopPropagation();
   1814         UI.closeAllDropdowns();
   1815         
   1816         UI.showConfirm(
   1817             "Delete File", 
   1818             "Are you sure you want to delete this file? This action cannot be undone.", 
   1819             "Delete", 
   1820             async () => {
   1821                 await DB.delete(id);
   1822                 state.files = state.files.filter(f => f.id !== id);
   1823                 if(state.activeFileId === id) {
   1824                     state.activeFileId = null;
   1825                     localStorage.removeItem('veyrix_active');
   1826                     UI.updateTopBar();
   1827                 }
   1828                 UI.renderSidebar();
   1829                 UI.closeAllModals();
   1830                 UI.toast('File deleted');
   1831             }
   1832         );
   1833     },
   1834 
   1835     handleFileUpload: (e) => {
   1836         const file = e.target.files[0];
   1837         if(!file) return;
   1838 
   1839         // Validation for common non-text binaries (mobile native pickers might circumvent accept filter)
   1840         if(file.type.startsWith('image/') || file.type.startsWith('video/') || file.type.startsWith('audio/') || file.type === 'application/pdf' || file.name.match(/\.(exe|dll|bin|zip|rar|tar|gz|7z|apk|dmg|iso)$/i)) {
   1841             e.target.value = '';
   1842             return UI.toast('Invalid file type (Text/Code only)', 'error');
   1843         }
   1844 
   1845         const reader = new FileReader();
   1846         reader.onload = async (ev) => {
   1847             const input = file.name;
   1848             let name = input;
   1849             let ext = '';
   1850             
   1851             const lastDotIdx = input.lastIndexOf('.');
   1852             if (lastDotIdx > 0 && lastDotIdx < input.length - 1) {
   1853                 ext = input.substring(lastDotIdx + 1);
   1854                 name = input.substring(0, lastDotIdx);
   1855             } else if (lastDotIdx === 0) {
   1856                 name = input;
   1857                 ext = '';
   1858             } else if (lastDotIdx === input.length - 1) {
   1859                 name = input.substring(0, lastDotIdx);
   1860                 ext = '';
   1861             }
   1862 
   1863             const newFile = {
   1864                 id: Utils.generateId(),
   1865                 name,
   1866                 ext,
   1867                 content: ev.target.result,
   1868                 unsaved: false
   1869             };
   1870 
   1871             state.files.push(newFile);
   1872             await DB.save(newFile);
   1873             App.openFile(newFile.id);
   1874             UI.toast('File imported');
   1875         };
   1876         reader.readAsText(file);
   1877         e.target.value = ''; // reset
   1878     },
   1879 
   1880     loadExternalContent: (name, ext, content) => {
   1881         // Creates a temporary file that won't auto-save to DB unless specifically saved
   1882         const file = {
   1883             id: 'temp_' + Date.now(),
   1884             name,
   1885             ext,
   1886             content,
   1887             unsaved: true // implies it needs saving
   1888         };
   1889         state.files.push(file);
   1890         App.openFile(file.id);
   1891         document.getElementById('banner-warning').classList.remove('hidden');
   1892         window.history.replaceState({}, document.title, window.location.pathname);
   1893     },
   1894 
   1895     formatCode: () => {
   1896         if(!state.activeFileId) return;
   1897         const file = state.files.find(f => f.id === state.activeFileId);
   1898         if(!file) return;
   1899         
   1900         try {
   1901             const beautify = ace.require("ace/ext/beautify");
   1902             beautify.beautify(state.editor.session);
   1903             UI.toast('Document formatted');
   1904         } catch(e) {
   1905             UI.toast('Formatting not supported for this language', 'error');
   1906         }
   1907     },
   1908 
   1909     toggleFullscreen: () => {
   1910         if (!document.fullscreenElement) {
   1911             document.documentElement.requestFullscreen().catch(() => UI.toast('Fullscreen failed', 'error'));
   1912         } else {
   1913             if (document.exitFullscreen) document.exitFullscreen();
   1914         }
   1915     },
   1916 
   1917     factoryReset: async () => {
   1918         if(confirm('Are you sure? This will delete all local files and cannot be undone.')) {
   1919             const request = indexedDB.deleteDatabase('VeyrixFS');
   1920             request.onsuccess = () => {
   1921                 localStorage.clear();
   1922                 window.location.reload();
   1923             };
   1924         }
   1925     }
   1926 };
   1927 
   1928 // Start application
   1929 window.onload = App.init;
   1930 
   1931 // Register Service Worker for PWA
   1932 if ('serviceWorker' in navigator) {
   1933     window.addEventListener('load', () => {
   1934         navigator.serviceWorker.register('assets/sw.js').catch(err => {
   1935             console.log('SW registration failed: ', err);
   1936         });
   1937     });
   1938 }
   1939 </script>
   1940 </body>
   1941 </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