| <!DOCTYPE html> |
|
|
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>CodeForge · AI Code Generator</title> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700;800&family=Space+Grotesk:wght@400;600&display=swap'); |
| |
| ``` |
| :root { |
| --accent: #ff8c00; |
| --accent-dim: rgba(255,140,0,0.15); |
| --accent-glow: rgba(255,140,0,0.5); |
| --bg: #09090e; |
| --surface: rgba(255,255,255,0.03); |
| --border: rgba(255,140,0,0.12); |
| --text: #d8d8d8; |
| --muted: rgba(255,255,255,0.25); |
| } |
| |
| * { margin: 0; padding: 0; box-sizing: border-box; } |
| |
| body { |
| background: var(--bg); |
| font-family: 'JetBrains Mono', monospace; |
| color: var(--text); |
| min-height: 100vh; |
| overflow-x: hidden; |
| } |
| |
| |
| .grid-bg { |
| position: fixed; |
| inset: 0; |
| background-image: |
| linear-gradient(rgba(255,140,0,0.025) 1px, transparent 1px), |
| linear-gradient(90deg, rgba(255,140,0,0.025) 1px, transparent 1px); |
| background-size: 48px 48px; |
| pointer-events: none; |
| z-index: 0; |
| } |
| .orb { |
| position: fixed; |
| border-radius: 50%; |
| pointer-events: none; |
| z-index: 0; |
| background: radial-gradient(circle, rgba(255,140,0,0.07) 0%, transparent 70%); |
| } |
| .orb1 { top: 10%; left: 5%; width: 500px; height: 500px; } |
| .orb2 { bottom: 10%; right: 5%; width: 400px; height: 400px; } |
| |
| |
| .shell { |
| position: relative; |
| z-index: 1; |
| display: flex; |
| flex-direction: column; |
| height: 100dvh; |
| max-width: 920px; |
| margin: 0 auto; |
| padding: 0 16px; |
| } |
| |
| |
| .header { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 14px 0 12px; |
| border-bottom: 1px solid var(--border); |
| flex-shrink: 0; |
| } |
| .header-left { display: flex; align-items: center; gap: 12px; } |
| .logo-icon { |
| font-size: 26px; |
| color: var(--accent); |
| animation: logoPulse 3s ease-in-out infinite; |
| line-height: 1; |
| } |
| @keyframes logoPulse { |
| 0%,100% { filter: drop-shadow(0 0 6px var(--accent-glow)); } |
| 50% { filter: drop-shadow(0 0 18px var(--accent-glow)); } |
| } |
| .header h1 { |
| font-size: 17px; |
| font-weight: 800; |
| color: var(--accent); |
| letter-spacing: 6px; |
| } |
| .header-right { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| .stat-pill { |
| background: var(--accent-dim); |
| border: 1px solid var(--border); |
| border-radius: 20px; |
| color: rgba(255,140,0,0.7); |
| font-size: 10px; |
| letter-spacing: 1px; |
| padding: 4px 10px; |
| } |
| |
| |
| button { |
| font-family: 'JetBrains Mono', monospace; |
| cursor: pointer; |
| border: none; |
| border-radius: 3px; |
| transition: all 0.18s; |
| } |
| .btn-primary { |
| background: var(--accent); |
| color: #0a0a0f; |
| font-weight: 800; |
| font-size: 11px; |
| letter-spacing: 2px; |
| padding: 10px 22px; |
| } |
| .btn-primary:hover:not(:disabled) { |
| background: #ffaa33; |
| box-shadow: 0 0 18px var(--accent-glow); |
| transform: translateY(-1px); |
| } |
| .btn-primary:disabled { opacity: 0.4; cursor: not-allowed; } |
| |
| .btn-ghost { |
| background: var(--surface); |
| border: 1px solid rgba(255,255,255,0.07); |
| color: var(--muted); |
| font-size: 10px; |
| letter-spacing: 1.5px; |
| padding: 8px 14px; |
| } |
| .btn-ghost:hover { background: rgba(255,255,255,0.07); color: rgba(255,255,255,0.6); } |
| |
| .btn-copy { |
| background: rgba(255,140,0,0.08); |
| border: 1px solid rgba(255,140,0,0.2); |
| color: rgba(255,140,0,0.7); |
| font-size: 9px; |
| letter-spacing: 1px; |
| padding: 4px 10px; |
| } |
| .btn-copy:hover { background: rgba(255,140,0,0.18); color: var(--accent); } |
| .btn-copy.copied { |
| background: rgba(80,220,100,0.1); |
| border-color: rgba(80,220,100,0.3); |
| color: rgba(80,220,100,0.9); |
| } |
| |
| |
| .lang-bar { |
| flex-shrink: 0; |
| padding: 10px 0; |
| border-bottom: 1px solid rgba(255,140,0,0.07); |
| overflow-x: auto; |
| -ms-overflow-style: none; |
| scrollbar-width: none; |
| } |
| .lang-bar::-webkit-scrollbar { display: none; } |
| .lang-inner { |
| display: flex; |
| gap: 6px; |
| white-space: nowrap; |
| } |
| .lang-btn { |
| background: var(--surface); |
| border: 1px solid rgba(255,255,255,0.06); |
| color: rgba(255,255,255,0.35); |
| font-size: 10px; |
| font-family: 'JetBrains Mono', monospace; |
| letter-spacing: 0.5px; |
| padding: 5px 13px; |
| border-radius: 2px; |
| transition: all 0.15s; |
| } |
| .lang-btn:hover { color: rgba(255,255,255,0.6); background: rgba(255,255,255,0.06); } |
| .lang-btn.active { |
| background: var(--accent-dim); |
| border-color: rgba(255,140,0,0.35); |
| color: var(--accent); |
| font-weight: 700; |
| } |
| |
| |
| .examples { |
| flex-shrink: 0; |
| padding: 10px 0; |
| overflow-x: auto; |
| -ms-overflow-style: none; |
| scrollbar-width: none; |
| } |
| .examples::-webkit-scrollbar { display: none; } |
| .examples-inner { |
| display: flex; |
| gap: 7px; |
| white-space: nowrap; |
| } |
| .ex-btn { |
| background: rgba(255,140,0,0.04); |
| border: 1px solid rgba(255,140,0,0.13); |
| color: rgba(255,140,0,0.55); |
| font-size: 10px; |
| font-family: 'JetBrains Mono', monospace; |
| padding: 6px 13px; |
| border-radius: 2px; |
| transition: all 0.15s; |
| } |
| .ex-btn:hover { background: rgba(255,140,0,0.1); color: var(--accent); } |
| |
| |
| .chat-area { |
| flex: 1; |
| overflow-y: auto; |
| padding: 16px 4px; |
| scroll-behavior: smooth; |
| } |
| .chat-area::-webkit-scrollbar { width: 4px; } |
| .chat-area::-webkit-scrollbar-thumb { background: rgba(255,140,0,0.25); border-radius: 4px; } |
| .chat-area::-webkit-scrollbar-track { background: transparent; } |
| |
| .empty-state { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| height: 100%; |
| gap: 12px; |
| color: rgba(255,255,255,0.1); |
| font-size: 12px; |
| letter-spacing: 2px; |
| text-align: center; |
| } |
| .empty-state .big { font-size: 40px; opacity: 0.15; } |
| |
| .message { |
| display: flex; |
| gap: 10px; |
| margin-bottom: 16px; |
| animation: msgIn 0.28s ease forwards; |
| } |
| @keyframes msgIn { |
| from { opacity: 0; transform: translateY(12px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| .message.user { flex-direction: row-reverse; } |
| |
| .avatar { |
| flex-shrink: 0; |
| width: 30px; |
| height: 30px; |
| border-radius: 3px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 13px; |
| font-weight: 700; |
| } |
| .message.user .avatar { |
| background: rgba(255,140,0,0.12); |
| border: 1px solid rgba(255,140,0,0.25); |
| color: var(--accent); |
| } |
| .message.assistant .avatar { |
| background: rgba(255,255,255,0.04); |
| border: 1px solid rgba(255,255,255,0.08); |
| color: rgba(255,255,255,0.4); |
| } |
| |
| .bubble { |
| max-width: 82%; |
| padding: 12px 14px; |
| border-radius: 4px; |
| font-size: 13px; |
| line-height: 1.65; |
| word-break: break-word; |
| } |
| .message.user .bubble { |
| background: rgba(255,140,0,0.08); |
| border: 1px solid rgba(255,140,0,0.18); |
| color: #ffb347; |
| border-radius: 6px 2px 6px 6px; |
| } |
| .message.assistant .bubble { |
| background: rgba(255,255,255,0.025); |
| border: 1px solid rgba(255,255,255,0.06); |
| color: #ccc; |
| border-radius: 2px 6px 6px 6px; |
| } |
| |
| |
| .bubble strong { color: #ffcc80; font-weight: 700; } |
| .bubble em { color: #aad4f5; font-style: italic; } |
| .bubble code { |
| background: rgba(0,0,0,0.5); |
| border: 1px solid rgba(255,255,255,0.08); |
| border-radius: 2px; |
| padding: 1px 5px; |
| font-size: 12px; |
| color: #ffb347; |
| font-family: 'JetBrains Mono', monospace; |
| } |
| |
| |
| .code-block { |
| margin: 12px 0; |
| border-radius: 4px; |
| overflow: hidden; |
| border: 1px solid rgba(255,140,0,0.14); |
| } |
| .code-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| background: rgba(0,0,0,0.5); |
| padding: 7px 12px; |
| border-bottom: 1px solid rgba(255,255,255,0.05); |
| } |
| .code-lang { |
| font-size: 10px; |
| font-weight: 700; |
| color: var(--accent); |
| letter-spacing: 2px; |
| text-transform: uppercase; |
| } |
| .code-block pre { |
| background: rgba(0,0,0,0.55); |
| padding: 14px 16px; |
| overflow-x: auto; |
| margin: 0; |
| } |
| .code-block pre code { |
| background: none; |
| border: none; |
| padding: 0; |
| color: #d4d4d4; |
| font-size: 12.5px; |
| line-height: 1.65; |
| } |
| |
| |
| .typing-wrap { |
| display: flex; |
| align-items: center; |
| gap: 5px; |
| padding: 4px 0; |
| } |
| .dot { |
| width: 6px; height: 6px; |
| background: var(--accent); |
| border-radius: 50%; |
| animation: dotPop 1.1s ease-in-out infinite; |
| } |
| .dot:nth-child(2) { animation-delay: 0.18s; } |
| .dot:nth-child(3) { animation-delay: 0.36s; } |
| @keyframes dotPop { |
| 0%,100% { opacity: 0.2; transform: scale(0.75); } |
| 50% { opacity: 1; transform: scale(1.25); } |
| } |
| .typing-label { |
| font-size: 10px; |
| color: rgba(255,140,0,0.45); |
| letter-spacing: 1px; |
| margin-left: 4px; |
| } |
| |
| |
| .input-area { |
| flex-shrink: 0; |
| padding: 12px 0 16px; |
| border-top: 1px solid rgba(255,140,0,0.09); |
| } |
| textarea { |
| width: 100%; |
| padding: 12px 14px; |
| background: rgba(255,255,255,0.035); |
| border: 1px solid rgba(255,140,0,0.15); |
| border-radius: 4px; |
| color: #e0e0e0; |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 13px; |
| line-height: 1.6; |
| resize: none; |
| outline: none; |
| transition: border-color 0.2s, box-shadow 0.2s; |
| min-height: 60px; |
| max-height: 160px; |
| overflow-y: auto; |
| } |
| textarea:focus { |
| border-color: rgba(255,140,0,0.4); |
| box-shadow: 0 0 0 3px rgba(255,140,0,0.04); |
| } |
| .btn-row { |
| display: flex; |
| gap: 8px; |
| margin-top: 9px; |
| align-items: center; |
| } |
| .btn-row .btn-primary { flex: 1; } |
| .hint { |
| font-size: 9px; |
| color: rgba(255,255,255,0.13); |
| letter-spacing: 1px; |
| text-align: right; |
| margin-top: 5px; |
| } |
| |
| |
| .gate { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| min-height: 100dvh; |
| gap: 0; |
| position: relative; |
| z-index: 1; |
| } |
| .gate-card { |
| background: rgba(255,140,0,0.03); |
| border: 1px solid var(--border); |
| border-radius: 6px; |
| padding: 40px; |
| max-width: 380px; |
| width: 90%; |
| text-align: center; |
| backdrop-filter: blur(12px); |
| } |
| .gate-icon { |
| font-size: 52px; |
| color: var(--accent); |
| animation: logoPulse 3s ease-in-out infinite; |
| margin-bottom: 12px; |
| display: block; |
| } |
| .gate-title { |
| font-size: 26px; |
| font-weight: 800; |
| color: var(--accent); |
| letter-spacing: 8px; |
| margin-bottom: 4px; |
| } |
| .gate-sub { |
| font-size: 10px; |
| color: rgba(255,140,0,0.35); |
| letter-spacing: 4px; |
| margin-bottom: 28px; |
| } |
| .gate-label { |
| font-size: 10px; |
| color: rgba(255,140,0,0.4); |
| letter-spacing: 3px; |
| margin-bottom: 10px; |
| } |
| .gate-input { |
| width: 100%; |
| padding: 13px 16px; |
| background: rgba(0,0,0,0.55); |
| border: 1px solid rgba(255,140,0,0.2); |
| border-radius: 3px; |
| color: var(--accent); |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 14px; |
| letter-spacing: 4px; |
| text-align: center; |
| outline: none; |
| transition: all 0.2s; |
| margin-bottom: 18px; |
| } |
| .gate-input:focus { |
| border-color: rgba(255,140,0,0.55); |
| box-shadow: 0 0 14px rgba(255,140,0,0.1); |
| } |
| .gate-error { |
| color: #ff6b6b; |
| font-size: 11px; |
| letter-spacing: 1px; |
| margin-top: 10px; |
| min-height: 16px; |
| } |
| |
| |
| ::-webkit-scrollbar { width: 4px; height: 4px; } |
| ::-webkit-scrollbar-track { background: transparent; } |
| ::-webkit-scrollbar-thumb { background: rgba(255,140,0,0.2); border-radius: 4px; } |
| |
| @media (max-width: 600px) { |
| .header h1 { font-size: 13px; letter-spacing: 4px; } |
| .bubble { max-width: 92%; font-size: 12px; } |
| .btn-primary { font-size: 10px; padding: 9px 14px; } |
| } |
| </style> |
| ``` |
|
|
| </head> |
| <body> |
| <div class="grid-bg"></div> |
| <div class="orb orb1"></div> |
| <div class="orb orb2"></div> |
|
|
| <div id="root"></div> |
|
|
| <script> |
| |
| |
| |
| const PASSWORD = "CodeForge#2026"; |
| const MODEL = "claude-sonnet-4-20250514"; |
| const API_URL = "https://api.anthropic.com/v1/messages"; |
| |
| const SYSTEM_PROMPT = `You are CodeForge, an elite AI code generator. When a user asks for code: |
| 1. Briefly explain what you're building (1-2 sentences, plain text). |
| 2. Output the complete, working code inside a fenced code block with the correct language tag. |
| 3. After the block, add one short tip or note if relevant. |
| Keep explanations concise. Prioritise clean, well-commented, production-ready code. |
| If the user is following up or asking for modifications, continue naturally.`; |
| |
| const LANGUAGES = [ |
| { id:"auto", label:"✦ Auto" }, |
| { id:"javascript", label:"JS" }, |
| { id:"typescript", label:"TS" }, |
| { id:"python", label:"Python" }, |
| { id:"html", label:"HTML/CSS" }, |
| { id:"react", label:"⚛ React" }, |
| { id:"sql", label:"SQL" }, |
| { id:"bash", label:"Bash" }, |
| { id:"rust", label:"🦀 Rust" }, |
| { id:"go", label:"Go" }, |
| { id:"java", label:"Java" }, |
| { id:"cpp", label:"C++" }, |
| ]; |
| |
| const EXAMPLES = [ |
| "Build a REST API with JWT auth", |
| "Responsive navbar with hamburger", |
| "Binary search algorithm", |
| "SQL user analytics query", |
| "Python web scraper", |
| "React counter with history", |
| "Debounce utility function", |
| "CSS glassmorphism card", |
| ]; |
| |
| |
| |
| |
| const state = { |
| unlocked: false, |
| messages: [], |
| lang: "auto", |
| count: 0, |
| loading: false, |
| }; |
| |
| |
| |
| |
| async function callClaude(userPrompt) { |
| const langNote = state.lang !== "auto" |
| ? ` [Preferred language: ${state.lang}]` |
| : ""; |
| |
| |
| const history = state.messages.map(m => ({ role: m.role, content: m.content })); |
| history.push({ role: "user", content: userPrompt + langNote }); |
| |
| const res = await fetch(API_URL, { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ |
| model: MODEL, |
| max_tokens: 2048, |
| system: SYSTEM_PROMPT, |
| messages: history, |
| }), |
| }); |
| |
| if (!res.ok) { |
| const err = await res.json().catch(() => ({})); |
| throw new Error(err?.error?.message || `HTTP ${res.status}`); |
| } |
| |
| const data = await res.json(); |
| return data.content?.find(b => b.type === "text")?.text ?? "No response."; |
| } |
| |
| |
| |
| |
| function escHtml(s) { |
| return s.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""); |
| } |
| |
| function renderMarkdown(text) { |
| |
| const codeBlocks = []; |
| const placeholder = "\x00CB\x00"; |
| |
| let result = text.replace(/```(\w*)\n?([\s\S]*?)```/g, (_, lang, code) => { |
| const id = codeBlocks.length; |
| const langLabel = lang || "code"; |
| const safeCode = escHtml(code.trim()); |
| codeBlocks.push( |
| `<div class="code-block">` + |
| `<div class="code-header"><span class="code-lang">${langLabel}</span>` + |
| `<button class="btn-copy" onclick="copyCode(this)">⎘ COPY</button></div>` + |
| `<pre><code>${safeCode}</code></pre></div>` |
| |
| ``` |
| ); |
| return placeholder + id + placeholder; |
| }); |
| |
| // Inline code (before escaping remaining text) |
| result = result.replace(/`([^`]+)`/g, (_, c) => `<code>${escHtml(c)}</code>`); |
| |
| |
| |
| const parts = result.split(new RegExp(placeholder.replace(/\x00/g,"\\x00") + "(\\d+)" + placeholder.replace(/\x00/g,"\\x00"))); |
| let final = ""; |
| for (let i = 0; i < parts.length; i++) { |
| if (i % 2 === 0) { |
| |
| let seg = escHtml(parts[i]); |
| seg = seg.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>"); |
| seg = seg.replace(/\*(.+?)\*/g, "<em>$1</em>"); |
| seg = seg.replace(/\n/g, "<br>"); |
| final += seg; |
| } else { |
| |
| final += codeBlocks[parseInt(parts[i])]; |
| } |
| } |
| return final; |
| ``` |
| |
| } |
| |
| // ───────────────────────────────────────────── |
| // COPY CODE |
| // ───────────────────────────────────────────── |
| window.copyCode = async (btn) => { |
| const pre = btn.closest(”.code-block”).querySelector(“pre code”); |
| try { |
| await navigator.clipboard.writeText(pre.innerText); |
| btn.textContent = “✓ COPIED”; |
| btn.classList.add(“copied”); |
| setTimeout(() => { btn.textContent = “⎘ COPY”; btn.classList.remove(“copied”); }, 1800); |
| } catch { |
| btn.textContent = “ERR”; |
| } |
| }; |
| |
| // ───────────────────────────────────────────── |
| // RENDER |
| // ───────────────────────────────────────────── |
| const root = document.getElementById(“root”); |
| |
| function renderGate() { |
| root.innerHTML = ` <div class="gate"> <div class="gate-card"> <span class="gate-icon">⟨/⟩</span> <div class="gate-title">CODEFORGE</div> <div class="gate-sub">AI · CODE · GENERATOR</div> <div class="gate-label">🔐 ENTER ACCESS CODE</div> <input class="gate-input" id="pwInput" type="password" placeholder="••••••••••••••" autofocus onkeydown="if(event.key==='Enter') tryUnlock()"> <button class="btn-primary" style="width:100%" onclick="tryUnlock()">UNLOCK ACCESS →</button> <div class="gate-error" id="gateErr"></div> </div> </div>`; |
| document.getElementById(“pwInput”).focus(); |
| } |
| |
| function renderApp() { |
| // Build messages |
| let msgHtml = “”; |
| for (const m of state.messages) { |
| const isUser = m.role === “user”; |
| msgHtml += ` <div class="message ${isUser ? "user" : "assistant"}"> <div class="avatar">${isUser ? "U" : "⟨/⟩"}</div> <div class="bubble">${isUser ? escHtml(m.content).replace(/\n/g,"<br>") : renderMarkdown(m.content)}</div> </div>`; |
| } |
| |
| ``` |
| if (state.loading) { |
| msgHtml += ` |
| <div class="message assistant"> |
| <div class="avatar">⟨/⟩</div> |
| <div class="bubble"> |
| <div class="typing-wrap"> |
| <div class="dot"></div><div class="dot"></div><div class="dot"></div> |
| <span class="typing-label">generating...</span> |
| </div> |
| </div> |
| </div>`; |
| } |
| |
| const langBtns = LANGUAGES.map(l => |
| `<button class="lang-btn ${state.lang === l.id ? "active" : ""}" onclick="setLang('${l.id}')">${l.label}</button>` |
| ).join(""); |
| |
| const exBtns = EXAMPLES.map(e => |
| `<button class="ex-btn" onclick="useExample(this)">▸ ${escHtml(e)}</button>` |
| ).join(""); |
| |
| root.innerHTML = ` |
| <div class="shell"> |
| <div class="header"> |
| <div class="header-left"> |
| <span class="logo-icon">⟨/⟩</span> |
| <h1>CODEFORGE</h1> |
| </div> |
| <div class="header-right"> |
| <span class="stat-pill">⚡ ${state.count} prompts</span> |
| <button class="btn-ghost" onclick="lockApp()">🔒 Lock</button> |
| <button class="btn-ghost" onclick="clearChat()">🗑 Clear</button> |
| </div> |
| </div> |
| |
| <div class="lang-bar"> |
| <div class="lang-inner">${langBtns}</div> |
| </div> |
| |
| <div class="examples"> |
| <div class="examples-inner">${exBtns}</div> |
| </div> |
| |
| <div class="chat-area" id="chatArea"> |
| ${msgHtml || `<div class="empty-state"><div class="big">⟨/⟩</div><div>Describe the code you need to get started</div></div>`} |
| </div> |
| |
| <div class="input-area"> |
| <textarea id="userInput" rows="2" |
| placeholder="Describe what you want to build…" |
| onkeydown="handleKey(event)"></textarea> |
| <div class="btn-row"> |
| <button class="btn-primary" id="sendBtn" onclick="sendMessage()" ${state.loading ? "disabled" : ""}>▶ GENERATE</button> |
| </div> |
| <div class="hint">↵ SEND · ⇧↵ NEW LINE</div> |
| </div> |
| </div>`; |
| |
| |
| const chat = document.getElementById("chatArea"); |
| if (chat) chat.scrollTop = chat.scrollHeight; |
| |
| |
| const inp = document.getElementById("userInput"); |
| if (inp) inp.focus(); |
| ``` |
| |
| } |
| |
| function render() { |
| state.unlocked ? renderApp() : renderGate(); |
| } |
| |
| // ───────────────────────────────────────────── |
| // EVENT HANDLERS (on window so inline onclick works) |
| // ───────────────────────────────────────────── |
| window.tryUnlock = () => { |
| const v = document.getElementById(“pwInput”).value; |
| if (v === PASSWORD) { |
| state.unlocked = true; |
| render(); |
| } else { |
| const err = document.getElementById(“gateErr”); |
| err.textContent = “❌ Invalid access code. Try again.”; |
| const inp = document.getElementById(“pwInput”); |
| inp.value = “”; |
| inp.focus(); |
| // Shake animation |
| inp.style.animation = “none”; |
| inp.style.borderColor = “rgba(255,80,80,0.6)”; |
| setTimeout(() => { inp.style.borderColor = “”; }, 800); |
| } |
| }; |
| |
| window.setLang = (id) => { |
| state.lang = id; |
| // Update buttons without full re-render |
| document.querySelectorAll(”.lang-btn”).forEach(b => { |
| const label = LANGUAGES.find(l => l.id === id)?.label; |
| b.classList.toggle(“active”, b.textContent.trim() === label || |
| (id === “auto” && b.textContent.trim() === “✦ Auto”)); |
| }); |
| // Re-render just lang bar cleanly |
| document.querySelectorAll(”.lang-btn”).forEach(b => b.classList.remove(“active”)); |
| document.querySelectorAll(”.lang-btn”).forEach(b => { |
| const match = LANGUAGES.find(l => l.id === id); |
| if (b.textContent.trim() === match?.label) b.classList.add(“active”); |
| }); |
| }; |
| |
| window.useExample = (btn) => { |
| const text = btn.textContent.replace(/^▸\s*/, “”); |
| const inp = document.getElementById(“userInput”); |
| if (inp) { inp.value = text; inp.focus(); } |
| }; |
| |
| window.handleKey = (e) => { |
| if (e.key === “Enter” && !e.shiftKey) { e.preventDefault(); sendMessage(); } |
| }; |
| |
| window.sendMessage = async () => { |
| const inp = document.getElementById(“userInput”); |
| const text = (inp?.value || “”).trim(); |
| if (!text || state.loading) return; |
| inp.value = “”; |
| |
| ``` |
| state.messages.push({ role: "user", content: text }); |
| state.count++; |
| state.loading = true; |
| render(); |
| |
| try { |
| const reply = await callClaude(text); |
| state.messages.push({ role: "assistant", content: reply }); |
| } catch (err) { |
| state.messages.push({ |
| role: "assistant", |
| content: `⚠️ **Error**: ${err.message}\n\nPlease try again.` |
| }); |
| } finally { |
| state.loading = false; |
| render(); |
| } |
| ``` |
| |
| }; |
| |
| window.clearChat = () => { |
| if (state.messages.length === 0) return; |
| if (confirm(“Clear all messages?”)) { |
| state.messages = []; |
| state.count = 0; |
| render(); |
| } |
| }; |
| |
| window.lockApp = () => { |
| if (confirm(“Lock CodeForge? You’ll need the password again.”)) { |
| state.unlocked = false; |
| state.messages = []; |
| state.count = 0; |
| render(); |
| } |
| }; |
| |
| // ───────────────────────────────────────────── |
| // BOOT |
| // ───────────────────────────────────────────── |
| render(); |
| </script> |
|
|
| </body> |
| </html> |