Code viewer for World: Python Environment [LATEST...

// Clone by Akshat Sen of:
// "Python Environment [LATEST]" by Aaqid Masoodi
// https://ancientbrain.com/world.php?world=8775112567
// Please leave this clone trail here.

document.write(`
<style>
:root{
  /* Light mode variables */
  --bg-gradient: linear-gradient(135deg, #fafafa, #f0f0f5);
  --panel:#ffffff;
  --muted:#64748b;
  --accent:#2563eb;
  --glass: rgba(0,0,0,0.04);
  --radius:12px;
  --shadow: 0 8px 24px rgba(16,24,40,0.06);
  --success:#16a34a;
  --danger:#fb7185;
  --terminal-bg: #020617; /* console stays dark */
  --text:#0f1724;
}

/* Dark theme overrides */
.dark {
  --bg-gradient: linear-gradient(135deg, #0a0a0f, #1b1b27);
  --panel:#0b1220;
  --muted:#94a3b8;
  --accent:#60a5fa;
  --glass: rgba(255,255,255,0.04);
  --shadow: 0 6px 18px rgba(2,6,23,0.6);
  --success:#4ade80;
  --danger:#fb7185;
  --terminal-bg: #020617; /* console stays dark */
  --text:#e6eef8;
}

*{box-sizing:border-box}
html,body{height:100%;margin:0;background:var(--bg-gradient);font-family:Inter,ui-sans-serif,system-ui,Segoe UI,Roboto,'Helvetica Neue',Arial;color:var(--text);}
.app {
  display:grid;
  grid-template-rows:64px 1fr;
  height:100vh;
  gap:12px;
  padding:12px;
}
/* Top bar */
.topbar{
  display:flex;align-items:center;gap:12px;padding:10px 14px;background:var(--panel);border-radius:var(--radius);box-shadow:var(--shadow);
}
.brand{font-weight:800;color:var(--accent);margin-right:6px;font-size:18px}
.top-actions{margin-left:auto;display:flex;gap:8px;align-items:center}
.btn{
  background:var(--glass);border:1px solid rgba(0,0,0,0.04);padding:8px 12px;border-radius:10px;color:var(--muted);cursor:pointer;font-weight:600;
}
.btn:hover{filter:brightness(0.98)}
.icon{opacity:0.9;margin-right:6px}
.small{padding:6px 8px;font-size:14px;border-radius:8px}
.toggle{display:flex;align-items:center;gap:6px;padding:6px 8px;background:transparent;border-radius:8px}

/* Main area split */
.main {
  display:flex;
  gap:12px;
  height:calc(100vh - 110px);
}

/* File explorer */
.explorer{
  width:220px; min-width:160px; max-width:520px; background:var(--panel); border-radius:12px;padding:10px; box-shadow:var(--shadow); display:flex;flex-direction:column;
}
.explorer h3{margin:6px 0 8px 0;color:var(--muted);font-size:13px}
.file-list{flex:1;overflow:auto;padding-right:4px}
.file-item{padding:8px;border-radius:8px;display:flex;justify-content:space-between;align-items:center;gap:8px;color:var(--muted);cursor:default}
.file-item:hover{background:var(--glass)}
.file-item.active{background:linear-gradient(90deg, rgba(37,99,235,0.08), transparent);color:var(--accent);font-weight:700}
.file-actions{display:flex;gap:6px;align-items:center}
.small-input{width:100%;padding:8px;border-radius:8px;border:1px solid rgba(0,0,0,0.04);background:transparent;color:var(--muted);}

/* Editor + console panel */
.workspace{flex:1;display:flex;flex-direction:column;gap:12px}
.top-editor{display:flex;gap:12px;align-items:center}
.tabs{display:flex;gap:6px;align-items:center;overflow:auto;padding-bottom:4px}
.tab{padding:6px 10px;border-radius:8px;background:var(--glass);color:var(--muted);cursor:pointer;display:flex;align-items:center;gap:8px}
.tab.active{background:linear-gradient(90deg, rgba(37,99,235,0.08), transparent);color:var(--accent);font-weight:700}
.tab .close{background:transparent;border:0;color:var(--muted);cursor:pointer;padding:2px 6px;border-radius:6px}
.tab .close:hover{background:rgba(0,0,0,0.03)}

/* editor area and resizable console */
.editor-area{flex:1;display:flex;gap:12px;align-items:stretch}
.code-panel{flex:1;background:var(--panel);padding:12px;border-radius:12px;box-shadow:var(--shadow);display:flex;flex-direction:column}
textarea.code{
  flex:1;background:transparent;border:0;outline:none;color:var(--text);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,monospace;font-size:14px;
  resize:none;padding:8px;border-radius:8px;
}
.right-panel{width:420px;display:flex;flex-direction:column;gap:8px}
.console-card{background:var(--terminal-bg);border-radius:10px;padding:12px;color:var(--success);font-family:ui-monospace,monospace;flex:1;overflow:auto;white-space:pre-wrap}
.console-controls{display:flex;gap:8px;align-items:center}
.console-line{padding:4px 6px;border-radius:6px;display:flex;gap:8px;align-items:center}
.console-line.err{color:var(--danger)}
.console-line.input{color:#ffd166}
.copy{opacity:0.6;font-size:13px;cursor:pointer}
.divider{width:6px;background:transparent;cursor:col-resize}

/* explorer divider (resizable) */
.explorer-divider{width:6px;background:transparent;cursor:col-resize;}

/* prompt modal */
#customPromptOverlay{position:fixed;inset:0;display:none;align-items:center;justify-content:center;z-index:99999}
#customPromptBox{background:var(--panel);padding:18px;border-radius:12px;box-shadow:var(--shadow);min-width:320px;color:var(--muted)}
#customPromptBox input{width:100%;padding:10px;border-radius:8px;border:1px solid rgba(0,0,0,0.04);margin-top:8px;background:transparent;color:var(--muted)}
.footer-note{font-size:12px;color:var(--muted);margin-top:6px}

/* small helpers */
.kbd{background:rgba(0,0,0,0.03);padding:4px 8px;border-radius:6px;font-weight:700;color:var(--muted);font-size:12px}
</style>

<div class="app">
  <div class="topbar">
    <div>
      <div class="brand">Ancient Brain Python</div>
      <div style="font-size:12px;color:var(--muted)">Designed &amp; Developed by Aaqid</div>
    </div>

    <div class="top-actions">
      <button id="runBtn" class="btn small">โ–ถ Run <span class="kbd" style="margin-left:8px">Ctrl/Cmd+Enter</span></button>
      <button id="saveBtn" class="btn small">๐Ÿ’พ Save</button>
      <button id="newBtn" class="btn small">๏ผ‹ New</button>
      <button id="clearConsoleBtn" class="btn small">๐Ÿงน Clear</button>
      <div class="toggle btn small" id="autoClearToggle" title="Auto-clear console on run">Auto-clear: <span id="autoClearState" style="margin-left:8px;color:var(--accent)">ON</span></div>
      <button id="themeToggle" class="btn small">๐ŸŒ— Theme</button>
    </div>
  </div>

  <div class="main">
    <div id="explorer" class="explorer">
      <h3>Files</h3>
      <div style="display:flex;gap:8px;margin-bottom:8px">
        <input id="newFileName" class="small-input" placeholder="new_file.py" />
        <button id="createFileBtn" class="btn small">Create</button>
      </div>
      <div class="file-list" id="fileList"></div>
      <div class="footer-note">Files saved in your browser (localStorage).</div>
    </div>

    <div id="explorerDivider" class="explorer-divider" title="Drag to resize file explorer"></div>

    <div class="workspace">
      <div class="top-editor">
        <div class="tabs" id="tabs"></div>
      </div>

      <div class="editor-area">
        <div class="code-panel">
          <textarea id="code" class="code" spellcheck="false"></textarea>
          <div style="display:flex;gap:8px;margin-top:8px">
            <div class="kbd">Ctrl/Cmd+Enter</div>
            <div style="flex:1"></div>
            <div id="status" style="color:var(--muted)">Ready</div>
          </div>
        </div>

        <div class="divider" id="divider" title="Drag to resize console"></div>

        <div class="right-panel">
          <div class="console-card" id="console"></div>
          <div class="console-controls">
            <button id="copyConsole" class="btn small">Copy</button>
            <button id="downloadConsole" class="btn small">Download</button>
            <div style="flex:1"></div>
            <div style="color:var(--muted);font-size:13px">Console</div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- Prompt Modal -->
<div id="customPromptOverlay">
  <div id="customPromptBox">
    <div id="customPromptMessage">Input:</div>
    <input id="customPromptInput" />
    <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:12px">
      <button class="btn small" id="customPromptCancel">Cancel</button>
      <button class="btn small" id="customPromptOK">OK</button>
    </div>
  </div>
</div>

<script src="https://cdn.jsdelivr.net/pyodide/v0.29.0/full/pyodide.js"></script>
<script>
/* ---------------------------
   Utilities & state
   --------------------------- */
const STORAGE_KEY = 'ancient_brain_python_v1';
let state = { files: {}, order: [], active: null, theme: 'light', autoClear: true };
const consoleEl = document.getElementById('console');
const codeEl = document.getElementById('code');
const fileListEl = document.getElementById('fileList');
const tabsEl = document.getElementById('tabs');
const statusEl = document.getElementById('status');
const autoClearStateEl = document.getElementById('autoClearState');
const explorerEl = document.getElementById('explorer');

function saveState(){ localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); }
function loadState(){
  const raw = localStorage.getItem(STORAGE_KEY);
  if(raw) state = JSON.parse(raw);
  if(!state.files || Object.keys(state.files).length===0){
    // seed with starter file
    state = {
      files: { 'main.py': '# Created by Ancient Brain Python\\nprint("Hello from Ancient Brain Python!")\\nname = input("Enter your name: ")\\nprint(f"Welcome, {name}")' },
      order: ['main.py'],
      active: 'main.py',
      theme: 'light',
      autoClear: true
    };
    saveState();
  }
}
loadState();
applyTheme();

/* ---------------------------
   UI rendering
   --------------------------- */
function renderFileList(){
  fileListEl.innerHTML = '';
  state.order.forEach(fname => {
    const div = document.createElement('div');
    div.className = 'file-item' + (fname===state.active ? ' active':'' );
    // clicking the name opens the file
    const nameSpan = document.createElement('div');
    nameSpan.textContent = fname;
    nameSpan.style.flex = '1';
    nameSpan.onclick = ()=> openFile(fname);
    // actions: rename, delete
    const actions = document.createElement('div');
    actions.className = 'file-actions';
    const renameBtn = document.createElement('button');
    renameBtn.className = 'btn small';
    renameBtn.textContent = 'Rename';
    renameBtn.onclick = (e)=>{ e.stopPropagation(); renameFilePrompt(fname); };
    const delBtn = document.createElement('button');
    delBtn.className = 'btn small';
    delBtn.textContent = 'Delete';
    delBtn.onclick = (e)=>{ e.stopPropagation(); deleteFilePrompt(fname); };
    actions.appendChild(renameBtn);
    actions.appendChild(delBtn);
    div.appendChild(nameSpan);
    div.appendChild(actions);
    fileListEl.appendChild(div);
  });
}
function renderTabs(){
  tabsEl.innerHTML = '';
  state.order.forEach(fname => {
    const t = document.createElement('div');
    t.className = 'tab' + (fname===state.active ? ' active':'');
    t.onclick = ()=>{ openFile(fname); };
    const span = document.createElement('span');
    span.textContent = fname;
    const close = document.createElement('button');
    close.className = 'close';
    close.textContent = 'โœ•';
    close.onclick = (e)=>{ e.stopPropagation(); closeTab(fname); };
    t.appendChild(span);
    t.appendChild(close);
    tabsEl.appendChild(t);
  });
}
function openFile(fname){
  if(!fname) return;
  state.active = fname;
  codeEl.value = state.files[fname] || '';
  renderTabs(); renderFileList();
  saveState();
}
function newFile(name){
  if(!name || !name.trim()) name = 'untitled.py';
  if(state.files[name]){ // unique
    let i=1; while(state.files[\`\${name.replace(/\\.py$/,'')}_\${i}.py\`]) i++;
    name = \`\${name.replace(/\\.py$/,'')}_\${i}.py\`;
  }
  state.files[name] = '# new file\\n';
  state.order.unshift(name);
  openFile(name);
  saveState();
}
function deleteFile(name){
  delete state.files[name];
  state.order = state.order.filter(f=>f!==name);
  if(state.active===name) state.active = state.order[0] || null;
  if(state.active){
    codeEl.value = state.files[state.active] || '';
  } else {
    codeEl.value = '# No file open.\\n# Create a new file to start coding!';
  }
  renderTabs(); renderFileList(); saveState();
}
function deleteFilePrompt(name){
  showPrompt('Type DELETE to confirm deletion of ' + name).then(v=>{
    if(v === 'DELETE') {
      deleteFile(name);
    } else {
      writeConsole('Delete cancelled.');
    }
  });
}
function renameFile(oldName,newName){
  if(!newName || !newName.trim()) return;
  if(state.files[newName] && newName !== oldName){
    showPrompt('Name exists. Type a different name:').then(v=>{ if(v) renameFile(oldName,v); });
    return;
  }
  state.files[newName] = state.files[oldName];
  delete state.files[oldName];
  const idx = state.order.indexOf(oldName);
  if(idx>=0) state.order[idx] = newName;
  if(state.active===oldName) state.active = newName;
  renderTabs(); renderFileList(); saveState();
}
function renameFilePrompt(oldName){
  showPrompt('Rename ' + oldName + ' to:').then(v=>{
    if(!v) return;
    // ensure extension .py
    let nm = v.trim();
    if(!/\\.py$/.test(nm)) nm = nm + '.py';
    renameFile(oldName, nm);
  });
}
function closeTab(name){
  const idx = state.order.indexOf(name);
  if(idx>=0) state.order.splice(idx,1);
  if(state.active===name) state.active = state.order[0] || null;
  if(state.active){
    codeEl.value = state.files[state.active] || '';
  } else {
    codeEl.value = '# No file open.\\n# Create a new file to start coding!';
  }
  renderTabs(); renderFileList(); saveState();
}
renderFileList(); renderTabs(); if(state.active) openFile(state.active);

/* create/delete buttons */
document.getElementById('createFileBtn').onclick = ()=>{
  const name = document.getElementById('newFileName').value.trim() || 'untitled.py';
  newFile(name);
  document.getElementById('newFileName').value='';
};
fileListEl.addEventListener('click', (e)=>{
  // clicking name handled inline; actions have their own handlers
});

/* editor autosave */
codeEl.addEventListener('input', ()=>{
  if(state.active){ state.files[state.active] = codeEl.value; saveState(); }
  statusEl.textContent = 'Editing...';
  clearTimeout(codeEl._idleTimer);
  codeEl._idleTimer = setTimeout(()=> statusEl.textContent = 'Ready', 800);
});

/* buttons */
document.getElementById('saveBtn').onclick = ()=>{ if(state.active){ state.files[state.active]=codeEl.value; saveState(); statusEl.textContent='Saved'; setTimeout(()=>statusEl.textContent='Ready',900);} };
document.getElementById('newBtn').onclick = ()=> newFile('new_file.py');
document.getElementById('clearConsoleBtn').onclick = ()=> { consoleEl.innerHTML=''; };

/* theme toggle */
document.getElementById('themeToggle').onclick = ()=>{
  state.theme = state.theme==='dark' ? 'light' : 'dark';
  applyTheme(); saveState();
};
function applyTheme(){ if(state.theme==='light') document.documentElement.classList.remove('dark'); else document.documentElement.classList.add('dark'); }

/* auto-clear toggle */
document.getElementById('autoClearToggle').onclick = ()=>{
  state.autoClear = !state.autoClear;
  autoClearStateEl.textContent = state.autoClear ? 'ON' : 'OFF';
  saveState();
};
autoClearStateEl.textContent = state.autoClear ? 'ON' : 'OFF';

/* copy/download console */
document.getElementById('copyConsole').onclick = ()=>{
  navigator.clipboard.writeText(consoleEl.textContent||'').then(()=>{ const b=document.getElementById('copyConsole'); b.textContent='Copied'; setTimeout(()=>b.textContent='Copy',900); }).catch(()=>{});
};
document.getElementById('downloadConsole').onclick = ()=>{
  const blob = new Blob([consoleEl.textContent],'text/plain');
  const a = document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='console.txt'; a.click();
};

/* explorer drag to resize */
const explorerDivider = document.getElementById('explorerDivider');
let explorerDragging = false;
explorerDivider.addEventListener('mousedown', e=>{ explorerDragging=true; document.body.style.userSelect='none'; });
window.addEventListener('mouseup', ()=>{ explorerDragging=false; document.body.style.userSelect='auto'; });
window.addEventListener('mousemove', e=>{
  if(!explorerDragging) return;
  const main = document.querySelector('.main');
  const rect = main.getBoundingClientRect();
  let x = e.clientX - rect.left;
  x = Math.max(160, Math.min(rect.width - 320, x));
  explorerEl.style.width = x + 'px';
});

/* divider drag to resize console */
const divider = document.getElementById('divider');
let dragging = false;
divider.addEventListener('mousedown', e=>{ dragging=true; document.body.style.userSelect='none'; });
window.addEventListener('mouseup', ()=>{ dragging=false; document.body.style.userSelect='auto'; });
window.addEventListener('mousemove', e=>{
  if(!dragging) return;
  const main = document.querySelector('.main');
  const rect = main.getBoundingClientRect();
  let x = e.clientX - rect.left;
  x = Math.max(320, Math.min(rect.width - 320, x));
  const right = document.querySelector('.right-panel');
  right.style.width = (rect.width - x - 12) + 'px';
});

/* keyboard shortcuts */
window.addEventListener('keydown', (e)=>{
  const S = (e.ctrlKey||e.metaKey) && e.key.toLowerCase()==='s';
  const EnterRun = (e.ctrlKey||e.metaKey) && e.key==='Enter';
  const New = (e.ctrlKey||e.metaKey) && e.key.toLowerCase()==='n';
  if(S){ e.preventDefault(); document.getElementById('saveBtn').click(); }
  if(EnterRun){ e.preventDefault(); document.getElementById('runBtn').click(); }
  if(New){ e.preventDefault(); newFile('new_file.py'); }
});

/* ---------------------------
   Console helpers (used by Python)
   --------------------------- */
function writeConsole(s, type='out'){
  if(typeof s !== 'string') s = String(s);
  const lines = s.split(/\\n/);
  lines.forEach(line=>{
    if(line==='') return;
    const div = document.createElement('div');
    div.className = 'console-line ' + (type==='err'?'err': type==='in'?'input':'');
    div.textContent = line;
    div.onclick = ()=>{ navigator.clipboard.writeText(line); div.style.opacity=0.6; setTimeout(()=>div.style.opacity=1,400); };
    consoleEl.appendChild(div);
  });
  consoleEl.scrollTop = consoleEl.scrollHeight;
}

/* ---------------------------
   Custom Prompt (modal) - reused for rename/confirm
   --------------------------- */
let customPromptResolve = null;
function showPrompt(message){
  const overlay = document.getElementById('customPromptOverlay');
  document.getElementById('customPromptMessage').textContent = message || '';
  document.getElementById('customPromptInput').value = '';
  overlay.style.display = 'flex';
  document.getElementById('customPromptInput').focus();
  return new Promise(resolve => customPromptResolve = resolve);
}
function hidePrompt(){ document.getElementById('customPromptOverlay').style.display='none'; }
document.getElementById('customPromptOK').onclick = ()=>{
  const v = document.getElementById('customPromptInput').value;
  hidePrompt(); if(customPromptResolve) customPromptResolve(v);
};
document.getElementById('customPromptCancel').onclick = ()=>{
  hidePrompt(); if(customPromptResolve) customPromptResolve(null);
};
window.customPrompt = showPrompt;

/* ---------------------------
   Pyodide integration
   --------------------------- */
let pyodide = null;
let pyReady = false;
let pyodideReadyPromise = loadPyodide().then(async p=>{
  pyodide = p;

  // Hook Python stdout/stderr -> JS writeConsole
  await pyodide.runPythonAsync(\`
import sys
from js import writeConsole as _writeConsole
class JSWriter:
    def write(self, s):
        if s is None:
            return
        if str(s).strip() == "":
            return
        _writeConsole(str(s), 'out')
    def flush(self):
        pass
sys.stdout = sys.stderr = JSWriter()
\`);

  // Hook Python input() to call JS customPrompt and await
  await pyodide.runPythonAsync(\`
import builtins
from js import customPrompt as _js_prompt
import asyncio
async def _async_input(msg=''):
    result = await _js_prompt(msg)
    return result
def _input(msg=''):
    # run async prompt in a synchronous context
    return asyncio.get_event_loop().run_until_complete(_async_input(msg))
builtins.input = _input
\`);

  pyReady = true;
  writeConsole('Pyodide ready โ€” Python environment initialized.');
  return pyodide;
});

/* ---------------------------
   Run button logic
   --------------------------- */
document.getElementById('runBtn').onclick = async ()=>{
  if(state.autoClear) consoleEl.innerHTML='';
  statusEl.textContent = 'Running...';
  const code = codeEl.value;
  if(state.active){ state.files[state.active] = code; saveState(); }
  try{
    await pyodideReadyPromise;
    await pyodide.runPythonAsync(code);
    statusEl.textContent = 'Done';
  }catch(err){
    writeConsole(String(err), 'err');
    statusEl.textContent = 'Error';
  }
  setTimeout(()=>statusEl.textContent='Ready', 800);
};

/* expose writeConsole to pyodide (for runPythonAsync's js import) */
window.writeConsole = writeConsole;

/* initialize editor content */
if(state.active) codeEl.value = state.files[state.active]; 
else codeEl.value = '# No file open.\\n# Create a new file to start coding!';

/* ensure auto-clear state UI */
autoClearStateEl.textContent = state.autoClear ? 'ON':'OFF';

/* quick load: if no files, seed main.py */
if(!state.active){
  const fn = Object.keys(state.files)[0];
  if(fn){ state.active = fn; openFile(fn); }
}
</script>
`);