🌐
AiX.html
Back
📝 Html ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <title>Ace Editor • AI + Settings + Save Placeholder + Search</title> <style> :root { --bar-h: 48px; --bg: #0f1115; --bar: #151823; --fg: #e8e8e8; --muted:#8a8f98; } * { box-sizing: border-box; } html, body { height: 100%; margin: 0; } body { background: var(--bg); color: var(--fg); font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; overflow: hidden; } /* Top bar - responsive layout */ .topbar { position: fixed; inset: 0 0 auto 0; height: var(--bar-h); display: grid; grid-template-columns: 1fr auto; align-items: center; padding: 0 10px; background: var(--bar); border-bottom: 1px solid #222837; z-index: 10; } /* Mobile: stack search below main toolbar */ @media (max-width: 768px) { .topbar { height: calc(var(--bar-h) * 2); grid-template-columns: 1fr; grid-template-rows: var(--bar-h) var(--bar-h); padding: 0; } .main-toolbar { grid-row: 1; display: flex; justify-content: space-between; align-items: center; padding: 0 10px; } .search-toolbar { grid-row: 2; display: flex; justify-content: center; align-items: center; padding: 0 10px; border-top: 1px solid #1a1f2d; } .search { width: 100%; max-width: none; } .search input { width: 100%; min-width: 0; } } /* Desktop: original layout */ @media (min-width: 769px) { .topbar { grid-template-columns: 1fr auto 1fr; grid-template-areas: "left center right"; } .main-toolbar { display: contents; } .search-toolbar { grid-area: center; justify-self: center; } } .bargroup { display: flex; align-items: center; gap: 8px; } .left { justify-self: start; } .right { justify-self: end; } .iconbtn { min-width: 40px; height: 34px; padding: 0 12px; border-radius: 10px; border: 1px solid #2a3144; background: #1a1f2d; color: #e6e6e6; display: grid; place-items: center; cursor: pointer; transition: transform .06s ease, background .15s ease, border-color .15s ease; font-size: 14px; line-height: 1; } .iconbtn:hover { background:#22283a; border-color:#3a4562; } .iconbtn:active { transform: scale(.98); } /* Save dropdown (placeholder) */ .dropdown { position: relative; } .menu { position: absolute; top: calc(100% + 6px); left: 0; min-width: 160px; background: #0f1422; border: 1px solid #2a3144; border-radius: 10px; padding: 6px; display: none; z-index: 20; box-shadow: 0 10px 30px rgba(0,0,0,.35); } .menu.open { display: block; } .menu button { width: 100%; text-align: left; padding: 8px 10px; border-radius: 8px; border: 1px solid transparent; background: transparent; color: #e7ebf7; cursor: pointer; font: 14px system-ui, -apple-system, Segoe UI, Roboto, sans-serif; } .menu button:hover { background: #17203a; border-color: #2e3a5a; } /* Search */ .search { display: flex; align-items: center; gap: 6px; background: #10162a; border: 1px solid #2a3144; border-radius: 10px; padding: 4px 6px; } .search input { background: transparent; border: 0; outline: none; color: #e6e9ee; width: 240px; font: 14px system-ui, -apple-system, Segoe UI, Roboto, sans-serif; } .search .sbtn { min-width: 34px; height: 28px; padding: 0 8px; border-radius: 8px; border: 1px solid #2a3144; background: #1a1f2d; color: #e6e6e6; cursor: pointer; font-size: 13px; } .search .sbtn:hover { background:#22283a; border-color:#3a4562; } /* Settings panel */ .panel { position: fixed; top: calc(var(--bar-h) + 8px); right: 10px; width: 320px; background: #111521; border: 1px solid #2a3144; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,.35); padding: 10px; z-index: 15; display: none; } @media (max-width: 768px) { .panel { top: calc(var(--bar-h) * 2 + 8px); right: 10px; left: 10px; width: auto; } } .panel.open { display: block; } .tabs { display: flex; gap: 6px; padding: 6px; background: #0e121c; border-radius: 10px; margin-bottom: 8px; } .tabbtn { flex: 1; text-align: center; padding: 8px 10px; border-radius: 8px; cursor: pointer; border: 1px solid #2a3144; background: #12182a; color: var(--fg); font-size: 13px; transition: background .15s ease, border-color .15s ease; } .tabbtn[aria-selected="true"] { background: #1a2238; border-color: #3a4562; } .tabpanel { display: none; } .tabpanel.active { display: block; } .row { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 8px; padding: 8px; border-radius: 8px; } .row + .row { margin-top: 4px; } .row label { color: var(--muted); font-size: 12px; } select, input[type="checkbox"], input[type="range"], input[type="text"] { accent-color: #8ab4ff; background: #0c0f18; color: #e6e9ee; border: 1px solid #2a3144; border-radius: 8px; padding: 6px 8px; font: inherit; min-height: 32px; } input[type="range"] { width: 140px; padding: 0; height: 28px; } /* Editor - adjust top margin for mobile */ #editor { position: absolute; top: var(--bar-h); left: 0; right: 0; bottom: 0; } @media (max-width: 768px) { #editor { top: calc(var(--bar-h) * 2); } } /* AI Overlay (prompt only) */ .overlay { position: fixed; inset: 0; display: none; z-index: 50; background: rgba(5,7,12,0.72); backdrop-filter: blur(6px); } .overlay.open { display: grid; grid-template-rows: auto 1fr auto; } .overlay-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; background: #0f1422; border-bottom: 1px solid #22283a; } .overlay-title { font-weight: 600; color: #dfe5f3; } .overlay-close { width: 34px; height: 34px; border-radius: 10px; border: 1px solid #2a3144; background: #1a1f2d; color: #e6e6e6; display: grid; place-items: center; cursor: pointer; } .overlay-body { padding: 10px; overflow: auto; } .messages { max-width: 1000px; margin: 0 auto; display: grid; gap: 10px; } .msg { background: #0f1524; border: 1px solid #232c45; border-radius: 12px; padding: 10px 12px; color: #e6ebf5; font-size: 14px; } .msg.user { background: #152038; border-color: #2a3a5a; } .msg.assistant { background: #101a2f; border-color: #243356; } .overlay-footer { border-top: 1px solid #22283a; background: #0f1422; padding: 10px; display: grid; } .promptbar { max-width: 1000px; margin: 0 auto; display: grid; grid-template-columns: 1fr auto; gap: 8px; align-items: end; } .prompt { min-height: 44px; max-height: 40vh; resize: none; overflow-y: auto; padding: 10px 12px; border-radius: 12px; background: #0c0f18; color: #e6e9ee; border: 1px solid #2a3144; font: 14px/1.4 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; } .askbtn { height: 44px; padding: 0 16px; border-radius: 12px; border: 1px solid #2a3144; background: #1b2a4a; color: #e6f0ff; cursor: pointer; font-weight: 600; } .askbtn:hover { background: #21335a; } .askbtn:active { transform: translateY(1px); } </style> </head> <body> <div class="topbar" role="toolbar" aria-label="Toolbar"> <div class="main-toolbar"> <!-- Left: Save dropdown (placeholders) --> <div class="bargroup left"> <div class="dropdown"> <button id="saveBtn" class="iconbtn" aria-haspopup="true" aria-expanded="false" aria-controls="saveMenu">💾 Save</button> <div id="saveMenu" class="menu" role="menu"> <button id="doSave" role="menuitem">Save</button> <button id="doArtifact" role="menuitem">Save Artifact</button> </div> </div> </div> <!-- Right: AI + Settings --> <div class="bargroup right"> <button id="aiBtn" class="iconbtn" aria-label="Open AI overlay">AI</button> <button id="settingsBtn" class="iconbtn" aria-label="Settings">⚙️</button> </div> </div> <!-- Search (center on desktop, full width on mobile) --> <div class="search-toolbar"> <div class="search" role="search"> <input id="searchInput" type="text" placeholder="Search…" aria-label="Search in file" /> <button id="searchPrev" class="sbtn" title="Previous (Shift+Enter)">↑</button> <button id="searchNext" class="sbtn" title="Next (Enter)">↓</button> </div> </div> </div> <!-- Settings panel with Editor/AI tabs --> <div id="panel" class="panel" role="dialog" aria-label="Settings"> <div class="tabs" role="tablist" aria-label="Settings tabs"> <button class="tabbtn" id="tab-editor" role="tab" aria-selected="true" aria-controls="panel-editor">Editor</button> <button class="tabbtn" id="tab-ai" role="tab" aria-selected="false" aria-controls="panel-ai">AI</button> </div> <section id="panel-editor" class="tabpanel active" role="tabpanel" aria-labelledby="tab-editor"> <div class="row"> <label for="theme">Theme</label> <select id="theme"> <option value="ace/theme/monokai">Monokai (dark)</option> </select> </div> <div class="row"> <label for="fontsize">Font size</label> <input id="fontsize" type="range" min="10" max="28" value="14" /> </div> <div class="row"> <label for="wrap">Soft wrap</label> <input id="wrap" type="checkbox" /> </div> <div class="row"> <label for="mode">Mode</label> <select id="mode"> <option value="ace/mode/html">HTML</option> <option value="ace/mode/javascript">JavaScript</option> <option value="ace/mode/php">PHP</option> </select> </div> </section> <section id="panel-ai" class="tabpanel" role="tabpanel" aria-labelledby="tab-ai"> <div class="row"> <label for="ai-provider">Provider</label> <select id="ai-provider"> <option value="none">None</option> <option value="openai">OpenAI</option> <option value="deepseek">DeepSeek</option> <option value="anthropic">Claude</option> </select> </div> <div class="row"> <label for="ai-model">Model</label> <input id="ai-model" type="text" placeholder="e.g. gpt-4o, deepseek-chat" /> </div> <div class="row"> <label for="ai-temp">Temperature</label> <input id="ai-temp" type="range" min="0" max="2" step="0.1" value="0.4" /> </div> <div class="row"> <label for="ai-key">API Key</label> <input id="ai-key" type="text" placeholder="sk-..." /> </div> </section> </div> <!-- Fullscreen AI prompt overlay --> <div id="aiOverlay" class="overlay" aria-modal="true" role="dialog" aria-labelledby="aiTitle"> <div class="overlay-header"> <div id="aiTitle" class="overlay-title">AI Assistant</div> <button id="overlayClose" class="overlay-close" aria-label="Close">✕</button> </div> <div class="overlay-body"> <div id="messages" class="messages" aria-live="polite" aria-relevant="additions"></div> </div> <div class="overlay-footer"> <form id="promptForm" class="promptbar"> <textarea id="prompt" class="prompt" placeholder="Ask anything about the code… (paste or type, it expands)" rows="1"></textarea> <button class="askbtn" id="askBtn" type="submit">Ask</button> </form> </div> </div> <div id="editor"></div> <!-- Ace (minimal) --> <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.6/ace.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.6/ext-language_tools.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.6/mode-html.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.6/mode-javascript.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.6/mode-php.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.6/theme-monokai.min.js"></script> <script> // ===== Editor ===== const editor = ace.edit("editor"); // Disable workers to avoid security errors in sandboxed environments editor.session.setUseWorker(false); editor.session.setMode("ace/mode/html"); editor.setTheme("ace/theme/monokai"); editor.setOptions({ tabSize: 2, useSoftTabs: true, showPrintMargin: false, wrap: false, enableBasicAutocompletion: true, enableLiveAutocompletion: true, enableSnippets: true, fontSize: "14px", }); const demo = `<!doctype html> <html> <head> <meta charset="utf-8"> <title>Hello</title> <style> body { font-family: system-ui; margin: 2rem } .hi { font-size: 1.5rem } </style> </head> <body> <h1>Hello, world! 👋</h1> <p class="hi">This was inserted as an example HTML+JS snippet.</p> <button id="btn">Click me</button> <script> document.getElementById('btn').addEventListener('click', () => { alert('Hello from JavaScript!'); console.log('Button clicked at', new Date().toISOString()); }); <\/script> </body> </html>`; editor.setValue(demo, -1); // ===== Helpers ===== const $ = (id) => document.getElementById(id); // ===== Save dropdown (placeholders) ===== const saveBtn = $("saveBtn"); const saveMenu = $("saveMenu"); saveBtn.addEventListener("click", (e) => { const open = saveMenu.classList.toggle("open"); saveBtn.setAttribute("aria-expanded", String(open)); e.stopPropagation(); }); document.addEventListener("click", (e) => { if (!saveMenu.contains(e.target) && !saveBtn.contains(e.target)) { saveMenu.classList.remove("open"); saveBtn.setAttribute("aria-expanded", "false"); } }); // Buttons exist but do nothing yet: $("doSave").addEventListener("click", () => { /* no-op for now */ saveMenu.classList.remove("open"); }); $("doArtifact").addEventListener("click", () => { /* no-op for now */ saveMenu.classList.remove("open"); }); // ===== Settings panel + tabs ===== const panel = $("panel"); const settingsBtn = $("settingsBtn"); settingsBtn.addEventListener("click", (e) => { panel.classList.toggle("open"); e.stopPropagation(); }); document.addEventListener("click", (e) => { if (!panel.contains(e.target) && !settingsBtn.contains(e.target)) panel.classList.remove("open"); }); const themeSel = $("theme"), wrapChk = $("wrap"), fontRange = $("fontsize"), modeSel = $("mode"); const tabEditor = $("tab-editor"), tabAI = $("tab-ai"); const panelEditor = $("panel-editor"), panelAI = $("panel-ai"); function selectTab(which) { const isEditor = which === "editor"; tabEditor.setAttribute("aria-selected", String(isEditor)); tabAI.setAttribute("aria-selected", String(!isEditor)); panelEditor.classList.toggle("active", isEditor); panelAI.classList.toggle("active", !isEditor); } tabEditor.addEventListener("click", () => selectTab("editor")); tabAI.addEventListener("click", () => selectTab("ai")); const aiProvider = $("ai-provider"), aiModel = $("ai-model"), aiTemp = $("ai-temp"), aiKey = $("ai-key"); // Safe localStorage access with fallback function getPrefs() { try { return JSON.parse(localStorage?.getItem?.("ace_min_prefs") || "{}"); } catch (e) { console.warn("localStorage not available, using defaults"); return {}; } } function savePrefs() { try { localStorage?.setItem?.("ace_min_prefs", JSON.stringify({ theme: editor.getTheme(), wrap: editor.session.getUseWrapMode(), fontSize: editor.getFontSize(), mode: editor.session.getMode().$id, ai: { provider: aiProvider.value, model: aiModel.value, temperature: parseFloat(aiTemp.value), key: aiKey.value } })); } catch (e) { console.warn("Could not save preferences:", e.message); } } const prefs = getPrefs(); if (prefs.theme) editor.setTheme(prefs.theme), (themeSel.value = prefs.theme); if (prefs.wrap != null) editor.session.setUseWrapMode(!!prefs.wrap), (wrapChk.checked = !!prefs.wrap); if (prefs.fontSize) editor.setFontSize(prefs.fontSize), (fontRange.value = parseInt(prefs.fontSize)); if (prefs.mode) editor.session.setMode(prefs.mode), (modeSel.value = prefs.mode); if (prefs.ai) { aiProvider.value = prefs.ai.provider ?? "none"; aiModel.value = prefs.ai.model ?? ""; aiTemp.value = prefs.ai.temperature ?? 0.4; aiKey.value = prefs.ai.key ?? ""; } themeSel.addEventListener("change", (e) => { editor.setTheme(e.target.value); savePrefs(); }); wrapChk.addEventListener("change", (e) => { editor.session.setUseWrapMode(e.target.checked); savePrefs(); }); fontRange.addEventListener("input", (e) => editor.setFontSize(e.target.value + "px")); fontRange.addEventListener("change", savePrefs); modeSel.addEventListener("change", (e) => { editor.session.setMode(e.target.value); savePrefs(); }); aiProvider.addEventListener("change", savePrefs); aiModel.addEventListener("change", savePrefs); aiTemp.addEventListener("input", savePrefs); aiKey.addEventListener("change", savePrefs); // ===== Search ===== const searchInput = $("searchInput"); const searchNext = $("searchNext"); const searchPrev = $("searchPrev"); function doFind(backwards=false) { const q = searchInput.value; if (!q) return; editor.find(q, { backwards, wrap: true, caseSensitive: false, wholeWord: false, regExp: false, preventScroll: false }); editor.focus(); } searchNext.addEventListener("click", () => doFind(false)); searchPrev.addEventListener("click", () => doFind(true)); searchInput.addEventListener("keydown", (e) => { if (e.key === "Enter") { e.preventDefault(); doFind(e.shiftKey); // Shift+Enter = previous } }); // ===== AI overlay (prompt only) ===== const aiBtn = $("aiBtn"); const aiOverlay = $("aiOverlay"); const overlayClose = $("overlayClose"); const promptForm = $("promptForm"); const prompt = $("prompt"); const messages = $("messages"); function openOverlay() { aiOverlay.classList.add("open"); setTimeout(() => { prompt.focus(); autoGrow(prompt); }, 0); } function closeOverlay() { aiOverlay.classList.remove("open"); aiBtn.focus(); } aiBtn.addEventListener("click", openOverlay); overlayClose.addEventListener("click", closeOverlay); window.addEventListener("keydown", (e) => { if (aiOverlay.classList.contains("open") && e.key === "Escape") closeOverlay(); }); function autoGrow(el) { el.style.height = "auto"; el.style.height = Math.min(el.scrollHeight, window.innerHeight * 0.4) + "px"; } prompt.addEventListener("input", () => autoGrow(prompt)); prompt.addEventListener("paste", () => setTimeout(() => autoGrow(prompt), 0)); // Stubbed ask handler (no API call yet) promptForm.addEventListener("submit", (e) => { e.preventDefault(); const text = prompt.value.trim(); if (!text) return; appendMsg("user", text); const selection = editor.getSelectedText(); const context = selection || editor.getValue().slice(0, 1500); const reply = `You asked: "${text}"\n\n(Stub) I would analyze ${ selection ? "the selected code" : "the first part of the current file" }:\n\n` + context; appendMsg("assistant", reply); prompt.value = ""; autoGrow(prompt); messages.parentElement.scrollTop = messages.parentElement.scrollHeight; }); function appendMsg(role, text) { const div = document.createElement("div"); div.className = "msg " + (role === "user" ? "user" : "assistant"); div.textContent = text; messages.appendChild(div); } </script> </body> </html>